diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 4f5651e0a3c7..b357a3389d9d 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -10,16 +10,17 @@ use crate::user::{ dashboard_metadata::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, }, - AcceptInviteFromEmailRequest, AuthorizeResponse, BeginTotpResponse, ChangePasswordRequest, - ConnectAccountRequest, CreateInternalUserRequest, CreateUserAuthenticationMethodRequest, - DashboardEntryResponse, ForgotPasswordRequest, GetSsoAuthUrlRequest, - GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest, - GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, ReInviteUserRequest, - RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest, - SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SsoSignInRequest, - SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse, TwoFactorAuthStatusResponse, - UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, - UserMerchantCreate, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, + AcceptInviteFromEmailRequest, AuthSelectRequest, AuthorizeResponse, BeginTotpResponse, + ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, + CreateUserAuthenticationMethodRequest, DashboardEntryResponse, ForgotPasswordRequest, + GetSsoAuthUrlRequest, GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, + GetUserRoleDetailsRequest, GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, + ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, + SendVerifyEmailRequest, SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, + SsoSignInRequest, SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse, + TwoFactorAuthStatusResponse, UpdateUserAccountDetailsRequest, + UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate, + VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, }; impl ApiEventMetric for DashboardEntryResponse { @@ -83,7 +84,8 @@ common_utils::impl_misc_api_event_type!( CreateUserAuthenticationMethodRequest, UpdateUserAuthenticationMethodRequest, GetSsoAuthUrlRequest, - SsoSignInRequest + SsoSignInRequest, + AuthSelectRequest ); #[cfg(feature = "dummy_connector")] diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index b2ed491b6775..02a4a09dc940 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -372,3 +372,8 @@ pub struct SsoSignInRequest { pub struct AuthIdQueryParam { pub auth_id: Option, } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct AuthSelectRequest { + pub id: String, +} diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index c4260a929484..3e9145c2d33d 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -2312,3 +2312,41 @@ pub async fn sso_sign( auth::cookies::set_cookie_response(response, token) } + +pub async fn terminate_auth_select( + state: SessionState, + user_token: auth::UserFromSinglePurposeToken, + req: user_api::AuthSelectRequest, +) -> UserResponse { + let user_from_db: domain::UserFromStorage = state + .global_store + .find_user_by_id(&user_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + let user_authentication_method = state + .store + .get_user_authentication_method_by_id(&req.id) + .await + .change_context(UserErrors::InternalServerError)?; + + let current_flow = domain::CurrentFlow::new(user_token, domain::SPTFlow::AuthSelect.into())?; + let mut next_flow = current_flow.next(user_from_db.clone(), &state).await?; + + // Skip SSO if continue with password(TOTP) + if next_flow.get_flow() == domain::UserFlow::SPTFlow(domain::SPTFlow::SSO) + && user_authentication_method.auth_type != common_enums::UserAuthType::OpenIdConnect + { + next_flow = next_flow.skip(user_from_db, &state).await?; + } + let token = next_flow.get_token(&state).await?; + + auth::cookies::set_cookie_response( + user_api::TokenResponse { + token: token.clone(), + token_type: next_flow.get_flow().into(), + }, + token, + ) +} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 80157c476b38..0cf7de4d33c2 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1417,7 +1417,8 @@ impl User { .service( web::resource("/list").route(web::get().to(list_user_authentication_methods)), ) - .service(web::resource("/url").route(web::get().to(get_sso_auth_url))), + .service(web::resource("/url").route(web::get().to(get_sso_auth_url))) + .service(web::resource("/select").route(web::post().to(terminate_auth_select))), ); #[cfg(feature = "email")] diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 115bcf6b9a05..03d12386918a 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -232,7 +232,8 @@ impl From for ApiIdentifier { | Flow::UpdateUserAuthenticationMethod | Flow::ListUserAuthenticationMethods | Flow::GetSsoAuthUrl - | Flow::SignInWithSso => Self::User, + | Flow::SignInWithSso + | Flow::AuthSelect => Self::User, Flow::ListRoles | Flow::GetRole diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index dae78d31bf01..7e55393cc575 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -876,3 +876,22 @@ pub async fn list_user_authentication_methods( )) .await } + +pub async fn terminate_auth_select( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::AuthSelect; + + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + json_payload.into_inner(), + |state, user, req, _| user_core::terminate_auth_select(state, user, req), + &auth::SinglePurposeJWTAuth(TokenPurpose::AuthSelect), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index ef73b88015fd..97fb69074a60 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -51,9 +51,8 @@ impl SPTFlow { ) -> UserResult { match self { // Auth - // AuthSelect and SSO flow are not enabled, once the terminate SSO API is ready, we can enable these flows - Self::AuthSelect => Ok(false), - Self::SSO => Ok(false), + Self::AuthSelect => Ok(true), + Self::SSO => Ok(true), // TOTP Self::TOTP => Ok(!path.contains(&TokenPurpose::SSO)), // Main email APIs @@ -311,6 +310,26 @@ impl NextFlow { } } } + + pub async fn skip(self, user: UserFromStorage, state: &SessionState) -> UserResult { + let flows = self.origin.get_flows(); + let index = flows + .iter() + .position(|flow| flow == &self.get_flow()) + .ok_or(UserErrors::InternalServerError)?; + let remaining_flows = flows.iter().skip(index + 1); + for flow in remaining_flows { + if flow.is_required(&user, &self.path, state).await? { + return Ok(Self { + origin: self.origin.clone(), + next_flow: *flow, + user, + path: self.path, + }); + } + } + Err(UserErrors::InternalServerError.into()) + } } impl From for TokenPurpose { diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 023cd2a79441..0b8f7f184da9 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -155,7 +155,7 @@ pub enum Flow { PaymentsStart, /// Payments list flow. PaymentsList, - // Payments filters flow + /// Payments filters flow PaymentsFilters, #[cfg(feature = "payouts")] /// Payouts create flow @@ -412,7 +412,7 @@ pub enum Flow { UserFromEmail, /// Begin TOTP TotpBegin, - // Reset TOTP + /// Reset TOTP TotpReset, /// Verify TOTP TotpVerify, @@ -422,20 +422,22 @@ pub enum Flow { RecoveryCodeVerify, /// Generate or Regenerate recovery codes RecoveryCodesGenerate, - // Terminate two factor authentication + /// Terminate two factor authentication TerminateTwoFactorAuth, - // Check 2FA status + /// Check 2FA status TwoFactorAuthStatus, - // Create user authentication method + /// Create user authentication method CreateUserAuthenticationMethod, - // Update user authentication method + /// Update user authentication method UpdateUserAuthenticationMethod, - // List user authentication methods + /// List user authentication methods ListUserAuthenticationMethods, /// Get sso auth url GetSsoAuthUrl, /// Signin with SSO SignInWithSso, + /// Auth Select + AuthSelect, /// List initial webhook delivery attempts WebhookEventInitialDeliveryAttemptList, /// List delivery attempts for a webhook event