@@ -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
645645impl 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
673655fn 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¶m2=value" ) . to_srv_request ( ) ;
712-
713- let result = OidcLoginState :: build_safe_redirect_url ( & req) ;
714- assert_eq ! ( result, "/page.sql?param=1¶m2=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