@@ -34,6 +34,7 @@ use openidconnect::{
3434use serde:: { Deserialize , Serialize } ;
3535use tokio:: sync:: { RwLock , RwLockReadGuard } ;
3636
37+ use super :: error:: anyhow_err_to_actix_resp;
3738use super :: http_client:: make_http_client;
3839
3940type LocalBoxFuture < T > = Pin < Box < dyn Future < Output = T > + ' static > > ;
@@ -44,6 +45,8 @@ const SQLPAGE_NONCE_COOKIE_NAME: &str = "sqlpage_oidc_nonce";
4445const SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX : & str = "sqlpage_oidc_state_" ;
4546const OIDC_CLIENT_MAX_REFRESH_INTERVAL : Duration = Duration :: from_secs ( 60 * 60 ) ;
4647const OIDC_CLIENT_MIN_REFRESH_INTERVAL : Duration = Duration :: from_secs ( 5 ) ;
48+ const SQLPAGE_OIDC_REDIRECT_COUNT_COOKIE : & str = "sqlpage_oidc_redirect_count" ;
49+ const MAX_OIDC_REDIRECTS : u8 = 3 ;
4750const AUTH_COOKIE_EXPIRATION : awc:: cookie:: time:: Duration =
4851 actix_web:: cookie:: time:: Duration :: days ( 7 ) ;
4952const LOGIN_FLOW_STATE_COOKIE_EXPIRATION : awc:: cookie:: time:: Duration =
@@ -374,11 +377,24 @@ async fn handle_unauthenticated_request(
374377
375378async fn handle_oidc_callback ( oidc_state : & OidcState , request : ServiceRequest ) -> ServiceResponse {
376379 match process_oidc_callback ( oidc_state, & request) . await {
377- Ok ( response) => request. into_response ( response) ,
380+ Ok ( mut response) => {
381+ clear_redirect_count_cookie ( & mut response) ;
382+ request. into_response ( response)
383+ }
378384 Err ( e) => {
379- log:: error!( "Failed to process OIDC callback. Refreshing oidc provider metadata, then redirecting to home page: {e:#}" ) ;
385+ let redirect_count = get_redirect_count ( & request) ;
386+ if redirect_count >= MAX_OIDC_REDIRECTS {
387+ log:: error!(
388+ "Failed to process OIDC callback after {redirect_count} attempts. \
389+ Stopping to avoid infinite redirections: {e:#}"
390+ ) ;
391+ let resp = build_oidc_error_response ( & request, & e) ;
392+ return request. into_response ( resp) ;
393+ }
394+ log:: error!( "Failed to process OIDC callback (attempt {redirect_count}). Refreshing oidc provider metadata, then redirecting to home page: {e:#}" ) ;
380395 oidc_state. refresh_on_error ( & request) . await ;
381- let resp = build_auth_provider_redirect_response ( oidc_state, "/" ) . await ;
396+ let mut resp = build_auth_provider_redirect_response ( oidc_state, "/" ) . await ;
397+ set_redirect_count_cookie ( & mut resp, redirect_count + 1 ) ;
382398 request. into_response ( resp)
383399 }
384400 }
@@ -500,6 +516,38 @@ fn build_redirect_response(target_url: String) -> HttpResponse {
500516 . body ( "Redirecting..." )
501517}
502518
519+ fn get_redirect_count ( request : & ServiceRequest ) -> u8 {
520+ request
521+ . cookie ( SQLPAGE_OIDC_REDIRECT_COUNT_COOKIE )
522+ . and_then ( |c| c. value ( ) . parse ( ) . ok ( ) )
523+ . unwrap_or ( 0 )
524+ }
525+
526+ fn set_redirect_count_cookie ( response : & mut HttpResponse , count : u8 ) {
527+ let cookie = Cookie :: build ( SQLPAGE_OIDC_REDIRECT_COUNT_COOKIE , count. to_string ( ) )
528+ . path ( "/" )
529+ . http_only ( true )
530+ . same_site ( actix_web:: cookie:: SameSite :: Lax )
531+ . max_age ( LOGIN_FLOW_STATE_COOKIE_EXPIRATION )
532+ . finish ( ) ;
533+ response. add_cookie ( & cookie) . ok ( ) ;
534+ }
535+
536+ fn clear_redirect_count_cookie ( response : & mut HttpResponse ) {
537+ let cookie = Cookie :: build ( SQLPAGE_OIDC_REDIRECT_COUNT_COOKIE , "" )
538+ . path ( "/" )
539+ . finish ( )
540+ . into_owned ( ) ;
541+ response. add_removal_cookie ( & cookie) . ok ( ) ;
542+ }
543+
544+ fn build_oidc_error_response ( request : & ServiceRequest , e : & anyhow:: Error ) -> HttpResponse {
545+ request. app_data :: < web:: Data < AppState > > ( ) . map_or_else (
546+ || HttpResponse :: InternalServerError ( ) . body ( format ! ( "Authentication error: {e}" ) ) ,
547+ |state| anyhow_err_to_actix_resp ( e, state) ,
548+ )
549+ }
550+
503551/// Returns the claims from the ID token in the `SQLPage` auth cookie.
504552async fn get_authenticated_user_info (
505553 oidc_state : & OidcState ,
0 commit comments