diff --git a/config/config.example.toml b/config/config.example.toml index 0436cea6b484..4bdcdcf79dfb 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -403,6 +403,7 @@ two_factor_auth_expiry_in_secs = 300 # Number of seconds after which 2FA should totp_issuer_name = "Hyperswitch" # Name of the issuer for TOTP base_url = "" # Base url used for user specific redirects and emails force_two_factor_auth = false # Whether to force two factor authentication for all users +force_cookies = true # Whether to use only cookies for JWT extraction and authentication #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index ce6f38d84a46..a4e1b1e9b13f 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -145,6 +145,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Integ" base_url = "https://integ.hyperswitch.io" force_two_factor_auth = false +force_cookies = true [frm] enabled = true diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 81985e83bcc0..a859d08ac4ab 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -152,6 +152,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Production" base_url = "https://live.hyperswitch.io" force_two_factor_auth = true +force_cookies = false [frm] enabled = false diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 4f98dc1ef093..070a32ef87b6 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -152,6 +152,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Sandbox" base_url = "https://app.hyperswitch.io" force_two_factor_auth = false +force_cookies = false [frm] enabled = true diff --git a/config/development.toml b/config/development.toml index 4cf2e1d4a805..2388607a4898 100644 --- a/config/development.toml +++ b/config/development.toml @@ -329,6 +329,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Dev" base_url = "http://localhost:8080" force_two_factor_auth = false +force_cookies = true [bank_config.eps] stripe = { banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index bf7779863ca5..d72141d9c372 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -57,6 +57,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" base_url = "http://localhost:8080" force_two_factor_auth = false +force_cookies = true [locker] host = "" diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 7b212ec6d1dc..4e559a261b9e 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -557,6 +557,7 @@ pub struct UserSettings { pub totp_issuer_name: String, pub base_url: String, pub force_two_factor_auth: bool, + pub force_cookies: bool, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index f01f6c5d7496..c6501dac3bd7 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -294,7 +294,7 @@ pub async fn connect_account( pub async fn signout( state: SessionState, - user_from_token: auth::UserFromToken, + user_from_token: auth::UserIdFromAuth, ) -> UserResponse<()> { tfa_utils::delete_totp_from_redis(&state, &user_from_token.user_id).await?; tfa_utils::delete_recovery_code_from_redis(&state, &user_from_token.user_id).await?; diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 068c2f30c795..8fc0dad452a9 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -130,7 +130,7 @@ pub async fn signout(state: web::Data, http_req: HttpRequest) -> HttpR &http_req, (), |state, user, _, _| user_core::signout(state, user), - &auth::DashboardNoPermissionAuth, + &auth::AnyPurposeOrLoginTokenAuth, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 2f5f55b84345..c05e4514aaa3 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -871,6 +871,47 @@ where } } +#[cfg(feature = "olap")] +#[derive(Debug)] +pub struct AnyPurposeOrLoginTokenAuth; + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for AnyPurposeOrLoginTokenAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserIdFromAuth, AuthenticationType)> { + let payload = + parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + + let purpose_exists = payload.purpose.is_some(); + let role_id_exists = payload.role_id.is_some(); + + if purpose_exists ^ role_id_exists { + Ok(( + UserIdFromAuth { + user_id: payload.user_id.clone(), + }, + AuthenticationType::SinglePurposeOrLoginJwt { + user_id: payload.user_id, + purpose: payload.purpose, + role_id: payload.role_id, + }, + )) + } else { + Err(errors::ApiErrorResponse::InvalidJwtToken.into()) + } + } +} + #[derive(Debug, Default)] pub struct AdminApiAuth; @@ -2504,17 +2545,27 @@ where T: serde::de::DeserializeOwned, A: SessionStateInfo + Sync, { - let token = match get_cookie_from_header(headers).and_then(cookies::parse_cookie) { - Ok(cookies) => cookies, - Err(error) => { - let token = get_jwt_from_authorization_header(headers); - if token.is_err() { - logger::error!(?error); - } - token?.to_owned() - } + let cookie_token_result = get_cookie_from_header(headers).and_then(cookies::parse_cookie); + let auth_header_token_result = get_jwt_from_authorization_header(headers); + let force_cookie = state.conf().user.force_cookies; + + logger::info!( + user_agent = ?headers.get(headers::USER_AGENT), + header_names = ?headers.keys().collect::>(), + is_token_equal = + auth_header_token_result.as_deref().ok() == cookie_token_result.as_deref().ok(), + cookie_error = ?cookie_token_result.as_ref().err(), + token_error = ?auth_header_token_result.as_ref().err(), + force_cookie, + ); + + let final_token = if force_cookie { + cookie_token_result? + } else { + auth_header_token_result?.to_owned() }; - decode_jwt(&token, state).await + + decode_jwt(&final_token, state).await } #[cfg(feature = "v1")] diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index dab85eb3cdd7..a3ac1159ddb0 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -36,6 +36,7 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" force_two_factor_auth = false +force_cookies = true [locker] host = ""