Skip to content

Commit 92cbdc5

Browse files
committed
Simplify OIDC redirect URL handling and validation
- Remove custom URL building logic in favor of direct URI usage - Make validate_redirect_url take ownership of the URL string - Remove now-unnecessary test cases for removed functionality - Add logging for redirect targets after successful login
1 parent b5d4f4b commit 92cbdc5

File tree

1 file changed

+8
-111
lines changed

1 file changed

+8
-111
lines changed

src/webserver/oidc.rs

Lines changed: 8 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,9 @@ async fn process_oidc_callback(
323323
let token_response = exchange_code_for_token(oidc_client, http_client, params).await?;
324324
log::debug!("Received token response: {token_response:?}");
325325

326-
// Validate the redirect URL is safe before using it
327-
let redirect_url = validate_redirect_url(&state.initial_url);
328-
let mut response = build_redirect_response(redirect_url);
326+
let redirect_target = validate_redirect_url(state.initial_url);
327+
log::info!("Redirecting to {redirect_target} after a successful login");
328+
let mut response = build_redirect_response(redirect_target);
329329
set_auth_cookie(&mut response, &token_response, oidc_client)?;
330330
Ok(response)
331331
}
@@ -644,30 +644,12 @@ fn nonce_matches(id_token_nonce: &Nonce, state_nonce: &Nonce) -> Result<(), Stri
644644

645645
impl OidcLoginState {
646646
fn new(request: &ServiceRequest, auth_url: AuthUrlParams) -> Self {
647-
// Capture the full path with query string for proper redirect after auth
648-
let initial_url = Self::build_safe_redirect_url(request);
649-
650647
Self {
651-
initial_url,
648+
initial_url: request.uri().to_string(),
652649
csrf_token: auth_url.csrf_token,
653650
nonce: auth_url.nonce,
654651
}
655652
}
656-
657-
/// Build a safe redirect URL that preserves query parameters but ensures security
658-
fn build_safe_redirect_url(request: &ServiceRequest) -> String {
659-
let path = request.path();
660-
let query = request.query_string();
661-
662-
// Ensure the path starts with '/' for security (prevent open redirects)
663-
let safe_path = if path.starts_with('/') { path } else { "/" };
664-
665-
if query.is_empty() {
666-
safe_path.to_string()
667-
} else {
668-
format!("{safe_path}?{query}")
669-
}
670-
}
671653
}
672654

673655
fn create_state_cookie(request: &ServiceRequest, auth_url: AuthUrlParams) -> Cookie<'_> {
@@ -690,95 +672,10 @@ fn get_state_from_cookie(request: &ServiceRequest) -> anyhow::Result<OidcLoginSt
690672
}
691673

692674
/// Validate that a redirect URL is safe to use (prevents open redirect attacks)
693-
fn validate_redirect_url(url: &str) -> String {
694-
// Only allow relative URLs that start with '/' to prevent open redirects
675+
fn validate_redirect_url(url: String) -> String {
695676
if url.starts_with('/') && !url.starts_with("//") {
696-
url.to_string()
697-
} else {
698-
log::warn!("Invalid redirect URL '{url}', redirecting to root instead");
699-
"/".to_string()
700-
}
701-
}
702-
703-
#[cfg(test)]
704-
mod tests {
705-
use super::{validate_redirect_url, AuthUrlParams, OidcLoginState};
706-
use actix_web::test;
707-
use openidconnect::{CsrfToken, Nonce};
708-
709-
#[test]
710-
async fn test_build_safe_redirect_url_with_query_params() {
711-
let req = test::TestRequest::with_uri("/page.sql?param=1&param2=value").to_srv_request();
712-
713-
let result = OidcLoginState::build_safe_redirect_url(&req);
714-
assert_eq!(result, "/page.sql?param=1&param2=value");
715-
}
716-
717-
#[test]
718-
async fn test_build_safe_redirect_url_without_query_params() {
719-
let req = test::TestRequest::with_uri("/page.sql").to_srv_request();
720-
721-
let result = OidcLoginState::build_safe_redirect_url(&req);
722-
assert_eq!(result, "/page.sql");
723-
}
724-
725-
#[test]
726-
async fn test_build_safe_redirect_url_with_special_characters() {
727-
let req = test::TestRequest::with_uri("/page.sql?param=hello%20world&special=%26%3D")
728-
.to_srv_request();
729-
730-
let result = OidcLoginState::build_safe_redirect_url(&req);
731-
assert_eq!(result, "/page.sql?param=hello%20world&special=%26%3D");
732-
}
733-
734-
#[test]
735-
async fn test_build_safe_redirect_url_handles_root_path() {
736-
// TestRequest with invalid relative path defaults to "/"
737-
let req = test::TestRequest::with_uri("page.sql").to_srv_request();
738-
739-
let result = OidcLoginState::build_safe_redirect_url(&req);
740-
// TestRequest normalizes invalid URI to root path
741-
assert_eq!(result, "/");
742-
}
743-
744-
#[test]
745-
async fn test_validate_redirect_url_valid_paths() {
746-
assert_eq!(validate_redirect_url("/page.sql"), "/page.sql");
747-
assert_eq!(
748-
validate_redirect_url("/page.sql?param=1"),
749-
"/page.sql?param=1"
750-
);
751-
assert_eq!(validate_redirect_url("/"), "/");
752-
assert_eq!(validate_redirect_url("/some/deep/path"), "/some/deep/path");
753-
}
754-
755-
#[test]
756-
async fn test_validate_redirect_url_invalid_paths() {
757-
// Protocol-relative URLs are dangerous
758-
assert_eq!(validate_redirect_url("//evil.com/path"), "/");
759-
760-
// Absolute URLs are dangerous
761-
assert_eq!(validate_redirect_url("http://evil.com"), "/");
762-
assert_eq!(validate_redirect_url("https://evil.com"), "/");
763-
764-
// Relative URLs without leading slash
765-
assert_eq!(validate_redirect_url("page.sql"), "/");
766-
}
767-
768-
#[test]
769-
async fn test_oidc_login_state_preserves_query_parameters() {
770-
let req = test::TestRequest::with_uri("/dashboard.sql?user_id=123&filter=active")
771-
.to_srv_request();
772-
773-
let auth_params = AuthUrlParams {
774-
csrf_token: CsrfToken::new_random(),
775-
nonce: Nonce::new_random(),
776-
};
777-
778-
let state = OidcLoginState::new(&req, auth_params);
779-
assert_eq!(
780-
state.initial_url,
781-
"/dashboard.sql?user_id=123&filter=active"
782-
);
677+
return url;
783678
}
679+
log::warn!("Refusing to redirect to {url}");
680+
'/'.to_string()
784681
}

0 commit comments

Comments
 (0)