@@ -41,11 +41,13 @@ type LocalBoxFuture<T> = Pin<Box<dyn Future<Output = T> + 'static>>;
4141const SQLPAGE_AUTH_COOKIE_NAME : & str = "sqlpage_auth" ;
4242const SQLPAGE_REDIRECT_URI : & str = "/sqlpage/oidc_callback" ;
4343const SQLPAGE_NONCE_COOKIE_NAME : & str = "sqlpage_oidc_nonce" ;
44- const SQLPAGE_REDIRECT_URL_COOKIE_PREFIX : & str = "sqlpage_oidc_redirect_url_ " ;
44+ const SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX : & str = "sqlpage_oidc_state_ " ;
4545const OIDC_CLIENT_MAX_REFRESH_INTERVAL : Duration = Duration :: from_secs ( 60 * 60 ) ;
4646const OIDC_CLIENT_MIN_REFRESH_INTERVAL : Duration = Duration :: from_secs ( 5 ) ;
4747const AUTH_COOKIE_EXPIRATION : awc:: cookie:: time:: Duration =
4848 actix_web:: cookie:: time:: Duration :: days ( 7 ) ;
49+ const LOGIN_FLOW_STATE_COOKIE_EXPIRATION : awc:: cookie:: time:: Duration =
50+ actix_web:: cookie:: time:: Duration :: minutes ( 10 ) ;
4951
5052#[ derive( Clone , Debug , Serialize , Deserialize ) ]
5153#[ serde( transparent) ]
@@ -413,26 +415,29 @@ async fn process_oidc_callback(
413415 . with_context ( || format ! ( "{SQLPAGE_REDIRECT_URI}: invalid url parameters" ) ) ?
414416 . into_inner ( ) ;
415417 log:: debug!( "Processing OIDC callback with params: {params:?}. Requesting token..." ) ;
416- let mut redirect_url_cookie = get_redirect_url_cookie ( request, & params. state ) ?;
418+ let mut tmp_login_flow_state_cookie = get_tmp_login_flow_state_cookie ( request, & params. state ) ?;
417419 let client = oidc_state. get_client ( ) . await ;
418420 let http_client = get_http_client_from_appdata ( request) ?;
419421 let id_token = exchange_code_for_token ( & client, http_client, params. clone ( ) ) . await ?;
420422 log:: debug!( "Received token response: {id_token:?}" ) ;
421- let nonce = get_nonce_from_cookie ( request) ?;
422- let redirect_target = validate_redirect_url ( redirect_url_cookie. value ( ) . to_string ( ) ) ;
423+ let LoginFlowState {
424+ nonce,
425+ redirect_target,
426+ } = parse_login_flow_state ( & tmp_login_flow_state_cookie) ?;
427+ let redirect_target = validate_redirect_url ( redirect_target. to_string ( ) ) ;
423428
424429 log:: info!( "Redirecting to {redirect_target} after a successful login" ) ;
425430 let mut response = build_redirect_response ( redirect_target) ;
426- set_auth_cookie ( & mut response, & id_token) . context ( "Failed to set auth cookie" ) ? ;
431+ set_auth_cookie ( & mut response, & id_token) ;
427432 let claims = oidc_state
428433 . get_token_claims ( id_token, & nonce)
429434 . await
430435 . context ( "The identity provider returned an invalid ID token" ) ?;
431436 log:: debug!( "{} successfully logged in" , claims. subject( ) . as_str( ) ) ;
432- let nonce_cookie = create_nonce_cookie ( & nonce) ;
437+ let nonce_cookie = create_final_nonce_cookie ( & nonce) ;
433438 response. add_cookie ( & nonce_cookie) ?;
434- redirect_url_cookie . set_path ( "/" ) ; // Required to clean up the cookie
435- response. add_removal_cookie ( & redirect_url_cookie ) ?;
439+ tmp_login_flow_state_cookie . set_path ( "/" ) ; // Required to clean up the cookie
440+ response. add_removal_cookie ( & tmp_login_flow_state_cookie ) ?;
436441 Ok ( response)
437442}
438443
@@ -456,7 +461,7 @@ async fn exchange_code_for_token(
456461 Ok ( id_token. clone ( ) )
457462}
458463
459- fn set_auth_cookie ( response : & mut HttpResponse , id_token : & OidcToken ) -> anyhow :: Result < ( ) > {
464+ fn set_auth_cookie ( response : & mut HttpResponse , id_token : & OidcToken ) {
460465 let id_token_str = id_token. to_string ( ) ;
461466 log:: trace!( "Setting auth cookie: {SQLPAGE_AUTH_COOKIE_NAME}=\" {id_token_str}\" " ) ;
462467 let id_token_size_kb = id_token_str. len ( ) / 1024 ;
@@ -475,20 +480,17 @@ fn set_auth_cookie(response: &mut HttpResponse, id_token: &OidcToken) -> anyhow:
475480 . finish ( ) ;
476481
477482 response. add_cookie ( & cookie) . unwrap ( ) ;
478- Ok ( ( ) )
479483}
480484
481485async fn build_auth_provider_redirect_response (
482486 oidc_state : & OidcState ,
483487 initial_url : & str ,
484488) -> HttpResponse {
485489 let AuthUrl { url, params } = build_auth_url ( oidc_state) . await ;
486- let nonce_cookie = create_nonce_cookie ( & params. nonce ) ;
487- let redirect_cookie = create_redirect_cookie ( & params. csrf_token , initial_url) ;
490+ let tmp_login_flow_state_cookie = create_tmp_login_flow_state_cookie ( & params, initial_url) ;
488491 HttpResponse :: TemporaryRedirect ( )
489492 . append_header ( ( header:: LOCATION , url. to_string ( ) ) )
490- . cookie ( nonce_cookie)
491- . cookie ( redirect_cookie)
493+ . cookie ( tmp_login_flow_state_cookie)
492494 . body ( "Redirecting..." )
493495}
494496
@@ -510,7 +512,7 @@ async fn get_authenticated_user_info(
510512 let id_token = OidcToken :: from_str ( & cookie_value)
511513 . with_context ( || format ! ( "Invalid SQLPage auth cookie: {cookie_value:?}" ) ) ?;
512514
513- let nonce = get_nonce_from_cookie ( request) ?;
515+ let nonce = get_final_nonce_from_cookie ( request) ?;
514516 log:: debug!( "Verifying id token: {id_token:?}" ) ;
515517 let claims = oidc_state. get_token_claims ( id_token, & nonce) . await ?;
516518 log:: debug!( "The current user is: {claims:?}" ) ;
@@ -742,7 +744,7 @@ fn nonce_matches(id_token_nonce: &Nonce, state_nonce: &Nonce) -> Result<(), Stri
742744 Ok ( ( ) )
743745}
744746
745- fn create_nonce_cookie ( nonce : & Nonce ) -> Cookie < ' _ > {
747+ fn create_final_nonce_cookie ( nonce : & Nonce ) -> Cookie < ' _ > {
746748 Cookie :: build ( SQLPAGE_NONCE_COOKIE_NAME , nonce. secret ( ) )
747749 . secure ( true )
748750 . http_only ( true )
@@ -752,34 +754,55 @@ fn create_nonce_cookie(nonce: &Nonce) -> Cookie<'_> {
752754 . finish ( )
753755}
754756
755- fn create_redirect_cookie < ' a > ( csrf_token : & CsrfToken , initial_url : & ' a str ) -> Cookie < ' a > {
756- let cookie_name = SQLPAGE_REDIRECT_URL_COOKIE_PREFIX . to_owned ( ) + csrf_token. secret ( ) ;
757- Cookie :: build ( cookie_name, initial_url)
757+ fn create_tmp_login_flow_state_cookie < ' a > (
758+ params : & ' a AuthUrlParams ,
759+ initial_url : & ' a str ,
760+ ) -> Cookie < ' a > {
761+ let csrf_token = & params. csrf_token ;
762+ let cookie_name = SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX . to_owned ( ) + csrf_token. secret ( ) ;
763+ let cookie_value = serde_json:: to_string ( & LoginFlowState {
764+ nonce : params. nonce . clone ( ) ,
765+ redirect_target : initial_url,
766+ } )
767+ . expect ( "login flow state is always serializable" ) ;
768+ Cookie :: build ( cookie_name, cookie_value)
758769 . secure ( true )
759770 . http_only ( true )
760771 . same_site ( actix_web:: cookie:: SameSite :: Lax )
761772 . path ( "/" )
762- . max_age ( actix_web :: cookie :: time :: Duration :: minutes ( 10 ) )
773+ . max_age ( LOGIN_FLOW_STATE_COOKIE_EXPIRATION )
763774 . finish ( )
764775}
765776
766- fn get_nonce_from_cookie ( request : & ServiceRequest ) -> anyhow:: Result < Nonce > {
777+ fn get_final_nonce_from_cookie ( request : & ServiceRequest ) -> anyhow:: Result < Nonce > {
767778 let cookie = request
768779 . cookie ( SQLPAGE_NONCE_COOKIE_NAME )
769780 . with_context ( || format ! ( "No {SQLPAGE_NONCE_COOKIE_NAME} cookie found" ) ) ?;
770781 Ok ( Nonce :: new ( cookie. value ( ) . to_string ( ) ) )
771782}
772783
773- fn get_redirect_url_cookie (
784+ fn get_tmp_login_flow_state_cookie (
774785 request : & ServiceRequest ,
775786 csrf_token : & CsrfToken ,
776787) -> anyhow:: Result < Cookie < ' static > > {
777- let cookie_name = SQLPAGE_REDIRECT_URL_COOKIE_PREFIX . to_owned ( ) + csrf_token. secret ( ) ;
788+ let cookie_name = SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX . to_owned ( ) + csrf_token. secret ( ) ;
778789 request
779790 . cookie ( & cookie_name)
780791 . with_context ( || format ! ( "No {cookie_name} cookie found" ) )
781792}
782793
794+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
795+ struct LoginFlowState < ' a > {
796+ #[ serde( rename = "n" ) ]
797+ nonce : Nonce ,
798+ #[ serde( rename = "r" ) ]
799+ redirect_target : & ' a str ,
800+ }
801+
802+ fn parse_login_flow_state < ' a > ( cookie : & ' a Cookie < ' _ > ) -> anyhow:: Result < LoginFlowState < ' a > > {
803+ Ok ( serde_json:: from_str ( cookie. value ( ) ) ?)
804+ }
805+
783806/// Given an audience, verify if it is trusted. The `client_id` is always trusted, independently of this function.
784807#[ derive( Clone , Debug ) ]
785808pub struct AudienceVerifier ( Option < HashSet < String > > ) ;
0 commit comments