use actix_web::http::header::HeaderMap; use api_models::{ payment_methods::{PaymentMethodCreate, PaymentMethodListRequest}, payments, }; use async_trait::async_trait; use common_utils::date_time; use error_stack::{report, ResultExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use masking::PeekInterface; use router_env::logger; use serde::Serialize; use self::blacklist::BlackList; use super::authorization::{self, permissions::Permission}; #[cfg(feature = "olap")] use super::jwt; #[cfg(feature = "recon")] use super::recon::ReconToken; #[cfg(feature = "olap")] use crate::configs::Settings; #[cfg(feature = "olap")] use crate::consts; #[cfg(feature = "olap")] use crate::core::errors::UserResult; #[cfg(feature = "recon")] use crate::routes::AppState; use crate::{ core::{ api_keys, errors::{self, utils::StorageErrorExt, RouterResult}, }, db::StorageInterface, routes::app::AppStateInfo, services::api, types::domain, utils::OptionExt, }; pub mod blacklist; pub mod cookies; #[derive(Clone, Debug)] pub struct AuthenticationData { pub merchant_account: domain::MerchantAccount, pub key_store: domain::MerchantKeyStore, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[serde( tag = "api_auth_type", content = "authentication_data", rename_all = "snake_case" )] pub enum AuthenticationType { ApiKey { merchant_id: String, key_id: String, }, AdminApiKey, MerchantJwt { merchant_id: String, user_id: Option, }, UserJwt { user_id: String, }, SinglePurposeJWT { user_id: String, purpose: Purpose, }, MerchantId { merchant_id: String, }, PublishableKey { merchant_id: String, }, WebhookAuth { merchant_id: String, }, NoAuth, } impl events::EventInfo for AuthenticationType { type Data = Self; fn data(&self) -> error_stack::Result { Ok(self.clone()) } fn key(&self) -> String { "auth_info".to_string() } } impl AuthenticationType { pub fn get_merchant_id(&self) -> Option<&str> { match self { Self::ApiKey { merchant_id, key_id: _, } | Self::MerchantId { merchant_id } | Self::PublishableKey { merchant_id } | Self::MerchantJwt { merchant_id, user_id: _, } | Self::WebhookAuth { merchant_id } => Some(merchant_id.as_ref()), Self::AdminApiKey | Self::UserJwt { .. } | Self::SinglePurposeJWT { .. } | Self::NoAuth => None, } } } #[derive(Clone, Debug)] pub struct UserFromSinglePurposeToken { pub user_id: String, } #[derive(serde::Serialize, serde::Deserialize)] pub struct SinglePurposeToken { pub user_id: String, pub purpose: Purpose, pub exp: u64, } #[derive(Debug, Clone, PartialEq, Eq, strum::Display, serde::Deserialize, serde::Serialize)] pub enum Purpose { AcceptInvite, } #[cfg(feature = "olap")] impl SinglePurposeToken { pub async fn new_token( user_id: String, purpose: Purpose, settings: &Settings, ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::SINGLE_PURPOSE_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(exp_duration)?.as_secs(); let token_payload = Self { user_id, purpose, exp, }; jwt::generate_jwt(&token_payload, settings).await } } // TODO: This has to be removed once single purpose token is used as a intermediate token #[derive(Clone, Debug)] pub struct UserWithoutMerchantFromToken { pub user_id: String, } #[derive(serde::Serialize, serde::Deserialize)] pub struct UserAuthToken { pub user_id: String, pub exp: u64, } #[cfg(feature = "olap")] impl UserAuthToken { pub async fn new_token(user_id: String, settings: &Settings) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(exp_duration)?.as_secs(); let token_payload = Self { user_id, exp }; jwt::generate_jwt(&token_payload, settings).await } } #[derive(serde::Serialize, serde::Deserialize)] pub struct AuthToken { pub user_id: String, pub merchant_id: String, pub role_id: String, pub exp: u64, pub org_id: String, } #[cfg(feature = "olap")] impl AuthToken { pub async fn new_token( user_id: String, merchant_id: String, role_id: String, settings: &Settings, org_id: String, ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(exp_duration)?.as_secs(); let token_payload = Self { user_id, merchant_id, role_id, exp, org_id, }; jwt::generate_jwt(&token_payload, settings).await } } #[derive(Clone)] pub struct UserFromToken { pub user_id: String, pub merchant_id: String, pub role_id: String, pub org_id: String, } pub trait AuthInfo { fn get_merchant_id(&self) -> Option<&str>; } impl AuthInfo for () { fn get_merchant_id(&self) -> Option<&str> { None } } impl AuthInfo for AuthenticationData { fn get_merchant_id(&self) -> Option<&str> { Some(&self.merchant_account.merchant_id) } } #[async_trait] pub trait AuthenticateAndFetch where A: AppStateInfo, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(T, AuthenticationType)>; } #[derive(Debug)] pub struct ApiKeyAuth; pub struct NoAuth; #[async_trait] impl AuthenticateAndFetch<(), A> for NoAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, _request_headers: &HeaderMap, _state: &A, ) -> RouterResult<((), AuthenticationType)> { Ok(((), AuthenticationType::NoAuth)) } } #[async_trait] impl AuthenticateAndFetch for ApiKeyAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let api_key = get_api_key(request_headers) .change_context(errors::ApiErrorResponse::Unauthorized)? .trim(); if api_key.is_empty() { return Err(errors::ApiErrorResponse::Unauthorized) .attach_printable("API key is empty"); } let api_key = api_keys::PlaintextApiKey::from(api_key); let hash_key = { let config = state.conf(); config.api_keys.get_inner().get_hash_key()? }; let hashed_api_key = api_key.keyed_hash(hash_key.peek()); let stored_api_key = state .store() .find_api_key_by_hash_optional(hashed_api_key.into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed .attach_printable("Failed to retrieve API key")? .ok_or(report!(errors::ApiErrorResponse::Unauthorized)) // If retrieve returned `None` .attach_printable("Merchant not authenticated")?; if stored_api_key .expires_at .map(|expires_at| expires_at < date_time::now()) .unwrap_or(false) { return Err(report!(errors::ApiErrorResponse::Unauthorized)) .attach_printable("API key has expired"); } let key_store = state .store() .get_merchant_key_store_by_merchant_id( &stored_api_key.merchant_id, &state.store().get_master_key().to_vec().into(), ) .await .change_context(errors::ApiErrorResponse::Unauthorized) .attach_printable("Failed to fetch merchant key store for the merchant id")?; let merchant = state .store() .find_merchant_account_by_merchant_id(&stored_api_key.merchant_id, &key_store) .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let auth = AuthenticationData { merchant_account: merchant, key_store, }; Ok(( auth.clone(), AuthenticationType::ApiKey { merchant_id: auth.merchant_account.merchant_id.clone(), key_id: stored_api_key.key_id, }, )) } } #[derive(Debug)] pub struct UserWithoutMerchantJWTAuth; #[cfg(feature = "olap")] #[async_trait] impl AuthenticateAndFetch for UserWithoutMerchantJWTAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } Ok(( UserWithoutMerchantFromToken { user_id: payload.user_id.clone(), }, AuthenticationType::UserJwt { user_id: payload.user_id, }, )) } } #[allow(dead_code)] #[derive(Debug)] pub(crate) struct SinglePurposeJWTAuth(pub Purpose); #[cfg(feature = "olap")] #[async_trait] impl AuthenticateAndFetch for SinglePurposeJWTAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(UserFromSinglePurposeToken, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } if self.0 != payload.purpose { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } Ok(( UserFromSinglePurposeToken { user_id: payload.user_id.clone(), }, AuthenticationType::SinglePurposeJWT { user_id: payload.user_id, purpose: payload.purpose, }, )) } } #[derive(Debug)] pub struct AdminApiAuth; #[async_trait] impl AuthenticateAndFetch<(), A> for AdminApiAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { let request_admin_api_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; let conf = state.conf(); let admin_api_key = &conf.secrets.get_inner().admin_api_key; if request_admin_api_key != admin_api_key.peek() { Err(report!(errors::ApiErrorResponse::Unauthorized) .attach_printable("Admin Authentication Failure"))?; } Ok(((), AuthenticationType::AdminApiKey)) } } #[derive(Debug)] pub struct MerchantIdAuth(pub String); #[async_trait] impl AuthenticateAndFetch for MerchantIdAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, _request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let key_store = state .store() .get_merchant_key_store_by_merchant_id( self.0.as_ref(), &state.store().get_master_key().to_vec().into(), ) .await .map_err(|e| { if e.current_context().is_db_not_found() { e.change_context(errors::ApiErrorResponse::Unauthorized) } else { e.change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to fetch merchant key store for the merchant id") } })?; let merchant = state .store() .find_merchant_account_by_merchant_id(self.0.as_ref(), &key_store) .await .map_err(|e| { if e.current_context().is_db_not_found() { e.change_context(errors::ApiErrorResponse::Unauthorized) } else { e.change_context(errors::ApiErrorResponse::InternalServerError) } })?; let auth = AuthenticationData { merchant_account: merchant, key_store, }; Ok(( auth.clone(), AuthenticationType::MerchantId { merchant_id: auth.merchant_account.merchant_id.clone(), }, )) } } #[derive(Debug)] pub struct PublishableKeyAuth; #[async_trait] impl AuthenticateAndFetch for PublishableKeyAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let publishable_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; state .store() .find_merchant_account_by_publishable_key(publishable_key) .await .map_err(|e| { if e.current_context().is_db_not_found() { e.change_context(errors::ApiErrorResponse::Unauthorized) } else { e.change_context(errors::ApiErrorResponse::InternalServerError) } }) .map(|auth| { ( auth.clone(), AuthenticationType::PublishableKey { merchant_id: auth.merchant_account.merchant_id.clone(), }, ) }) } } #[derive(Debug)] pub(crate) struct JWTAuth(pub Permission); #[derive(serde::Deserialize)] struct JwtAuthPayloadFetchUnit { #[serde(rename(deserialize = "exp"))] _exp: u64, } #[async_trait] impl AuthenticateAndFetch<(), A> for JWTAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let permissions = authorization::get_permissions(state, &payload).await?; authorization::check_authorization(&self.0, &permissions)?; Ok(( (), AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), }, )) } } #[cfg(feature = "olap")] #[async_trait] impl AuthenticateAndFetch for JWTAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(UserFromToken, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let permissions = authorization::get_permissions(state, &payload).await?; authorization::check_authorization(&self.0, &permissions)?; Ok(( UserFromToken { user_id: payload.user_id.clone(), merchant_id: payload.merchant_id.clone(), org_id: payload.org_id, role_id: payload.role_id, }, AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), }, )) } } pub struct JWTAuthMerchantFromRoute { pub merchant_id: String, pub required_permission: Permission, } #[async_trait] impl AuthenticateAndFetch<(), A> for JWTAuthMerchantFromRoute where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let permissions = authorization::get_permissions(state, &payload).await?; authorization::check_authorization(&self.required_permission, &permissions)?; // Check if token has access to MerchantId that has been requested through query param if payload.merchant_id != self.merchant_id { return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); } Ok(( (), AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), }, )) } } pub struct JWTAuthMerchantOrProfileFromRoute { pub merchant_id_or_profile_id: String, pub required_permission: Permission, } #[async_trait] impl AuthenticateAndFetch<(), A> for JWTAuthMerchantOrProfileFromRoute where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let permissions = authorization::get_permissions(state, &payload).await?; authorization::check_authorization(&self.required_permission, &permissions)?; // Check if token has access to MerchantId that has been requested through path or query param if payload.merchant_id == self.merchant_id_or_profile_id { return Ok(( (), AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), }, )); } // Route did not contain the merchant ID in present JWT, check if it corresponds to a // business profile let business_profile = state .store() .find_business_profile_by_profile_id(&self.merchant_id_or_profile_id) .await // Return access forbidden if business profile not found .to_not_found_response(errors::ApiErrorResponse::AccessForbidden { resource: self.merchant_id_or_profile_id.clone(), }) .attach_printable("Could not find business profile specified in route")?; // Check if merchant (from JWT) has access to business profile that has been requested // through path or query param if payload.merchant_id == business_profile.merchant_id { Ok(( (), AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), }, )) } else { Err(report!(errors::ApiErrorResponse::InvalidJwtToken)) } } } pub async fn parse_jwt_payload(headers: &HeaderMap, state: &A) -> RouterResult where T: serde::de::DeserializeOwned, A: AppStateInfo + Sync, { let token = match get_cookie_from_header(headers).and_then(cookies::parse_cookie) { Ok(cookies) => cookies, Err(e) => { let token = get_jwt_from_authorization_header(headers); if token.is_err() { logger::error!(?e); } token?.to_owned() } }; decode_jwt(&token, state).await } #[async_trait] impl AuthenticateAndFetch for JWTAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let permissions = authorization::get_permissions(state, &payload).await?; authorization::check_authorization(&self.0, &permissions)?; let key_store = state .store() .get_merchant_key_store_by_merchant_id( &payload.merchant_id, &state.store().get_master_key().to_vec().into(), ) .await .change_context(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant key store for the merchant id")?; let merchant = state .store() .find_merchant_account_by_merchant_id(&payload.merchant_id, &key_store) .await .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; let auth = AuthenticationData { merchant_account: merchant, key_store, }; Ok(( auth.clone(), AuthenticationType::MerchantJwt { merchant_id: auth.merchant_account.merchant_id.clone(), user_id: None, }, )) } } pub type AuthenticationDataWithUserId = (AuthenticationData, String); #[async_trait] impl AuthenticateAndFetch for JWTAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationDataWithUserId, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let permissions = authorization::get_permissions(state, &payload).await?; authorization::check_authorization(&self.0, &permissions)?; let key_store = state .store() .get_merchant_key_store_by_merchant_id( &payload.merchant_id, &state.store().get_master_key().to_vec().into(), ) .await .change_context(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant key store for the merchant id")?; let merchant = state .store() .find_merchant_account_by_merchant_id(&payload.merchant_id, &key_store) .await .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; let auth = AuthenticationData { merchant_account: merchant, key_store, }; Ok(( (auth.clone(), payload.user_id.clone()), AuthenticationType::MerchantJwt { merchant_id: auth.merchant_account.merchant_id.clone(), user_id: None, }, )) } } pub struct DashboardNoPermissionAuth; #[cfg(feature = "olap")] #[async_trait] impl AuthenticateAndFetch for DashboardNoPermissionAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(UserFromToken, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } Ok(( UserFromToken { user_id: payload.user_id.clone(), merchant_id: payload.merchant_id.clone(), org_id: payload.org_id, role_id: payload.role_id, }, AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), }, )) } } #[cfg(feature = "olap")] #[async_trait] impl AuthenticateAndFetch<(), A> for DashboardNoPermissionAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } Ok(((), AuthenticationType::NoAuth)) } } #[async_trait] impl AuthenticateAndFetch for DashboardNoPermissionAuth where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; let key_store = state .store() .get_merchant_key_store_by_merchant_id( &payload.merchant_id, &state.store().get_master_key().to_vec().into(), ) .await .change_context(errors::ApiErrorResponse::Unauthorized) .attach_printable("Failed to fetch merchant key store for the merchant id")?; let merchant = state .store() .find_merchant_account_by_merchant_id(&payload.merchant_id, &key_store) .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let auth = AuthenticationData { merchant_account: merchant, key_store, }; Ok(( auth.clone(), AuthenticationType::MerchantJwt { merchant_id: auth.merchant_account.merchant_id.clone(), user_id: Some(payload.user_id), }, )) } } pub trait ClientSecretFetch { fn get_client_secret(&self) -> Option<&String>; } impl ClientSecretFetch for payments::PaymentsRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for PaymentMethodListRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for PaymentMethodCreate { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for api_models::payments::PaymentsRetrieveRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for api_models::payments::RetrievePaymentLinkRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for api_models::pm_auth::LinkTokenCreateRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for api_models::pm_auth::ExchangeTokenCreateRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } impl ClientSecretFetch for api_models::payment_methods::PaymentMethodUpdate { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } pub fn get_auth_type_and_flow( headers: &HeaderMap, ) -> RouterResult<( Box>, api::AuthFlow, )> { let api_key = get_api_key(headers)?; if api_key.starts_with("pk_") { return Ok((Box::new(PublishableKeyAuth), api::AuthFlow::Client)); } Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant)) } pub fn check_client_secret_and_get_auth( headers: &HeaderMap, payload: &impl ClientSecretFetch, ) -> RouterResult<( Box>, api::AuthFlow, )> where T: AppStateInfo, ApiKeyAuth: AuthenticateAndFetch, PublishableKeyAuth: AuthenticateAndFetch, { let api_key = get_api_key(headers)?; if api_key.starts_with("pk_") { payload .get_client_secret() .check_value_present("client_secret") .map_err(|_| errors::ApiErrorResponse::MissingRequiredField { field_name: "client_secret", })?; return Ok((Box::new(PublishableKeyAuth), api::AuthFlow::Client)); } if payload.get_client_secret().is_some() { return Err(errors::ApiErrorResponse::InvalidRequestData { message: "client_secret is not a valid parameter".to_owned(), } .into()); } Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant)) } pub async fn is_ephemeral_auth( headers: &HeaderMap, db: &dyn StorageInterface, customer_id: &str, ) -> RouterResult>> { let api_key = get_api_key(headers)?; if !api_key.starts_with("epk") { return Ok(Box::new(ApiKeyAuth)); } let ephemeral_key = db .get_ephemeral_key(api_key) .await .change_context(errors::ApiErrorResponse::Unauthorized)?; if ephemeral_key.customer_id.ne(customer_id) { return Err(report!(errors::ApiErrorResponse::InvalidEphemeralKey)); } Ok(Box::new(MerchantIdAuth(ephemeral_key.merchant_id))) } pub fn is_jwt_auth(headers: &HeaderMap) -> bool { headers.get(crate::headers::AUTHORIZATION).is_some() || get_cookie_from_header(headers) .and_then(cookies::parse_cookie) .is_ok() } pub async fn decode_jwt(token: &str, state: &impl AppStateInfo) -> RouterResult where T: serde::de::DeserializeOwned, { let conf = state.conf(); let secret = conf.secrets.get_inner().jwt_secret.peek().as_bytes(); let key = DecodingKey::from_secret(secret); decode::(token, &key, &Validation::new(Algorithm::HS256)) .map(|decoded| decoded.claims) .change_context(errors::ApiErrorResponse::InvalidJwtToken) } pub fn get_api_key(headers: &HeaderMap) -> RouterResult<&str> { get_header_value_by_key("api-key".into(), headers)?.get_required_value("api_key") } pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult> { headers .get(&key) .map(|source_str| { source_str .to_str() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable(format!( "Failed to convert header value to string for header key: {}", key )) }) .transpose() } pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&str> { headers .get(crate::headers::AUTHORIZATION) .get_required_value(crate::headers::AUTHORIZATION)? .to_str() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert JWT token to string")? .strip_prefix("Bearer ") .ok_or(errors::ApiErrorResponse::InvalidJwtToken.into()) } pub fn get_cookie_from_header(headers: &HeaderMap) -> RouterResult<&str> { headers .get(cookies::get_cookie_header()) .and_then(|header_value| header_value.to_str().ok()) .ok_or(errors::ApiErrorResponse::InvalidCookie.into()) } pub fn strip_jwt_token(token: &str) -> RouterResult<&str> { token .strip_prefix("Bearer ") .ok_or_else(|| errors::ApiErrorResponse::InvalidJwtToken.into()) } pub fn auth_type<'a, T, A>( default_auth: &'a dyn AuthenticateAndFetch, jwt_auth_type: &'a dyn AuthenticateAndFetch, headers: &HeaderMap, ) -> &'a dyn AuthenticateAndFetch where { if is_jwt_auth(headers) { return jwt_auth_type; } default_auth } #[cfg(feature = "recon")] pub struct ReconAdmin; #[async_trait] #[cfg(feature = "recon")] impl AuthenticateAndFetch<(), A> for ReconAdmin where A: AppStateInfo + Sync, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, ) -> RouterResult<((), AuthenticationType)> { let request_admin_api_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; let conf = state.conf(); let admin_api_key = conf.secrets.get_inner().recon_admin_api_key.peek(); if request_admin_api_key != admin_api_key { Err(report!(errors::ApiErrorResponse::Unauthorized) .attach_printable("Recon Admin Authentication Failure"))?; } Ok(((), AuthenticationType::NoAuth)) } } #[cfg(feature = "recon")] pub struct ReconJWT; #[cfg(feature = "recon")] pub struct ReconUser { pub user_id: String, } #[cfg(feature = "recon")] impl AuthInfo for ReconUser { fn get_merchant_id(&self) -> Option<&str> { None } } #[cfg(all(feature = "olap", feature = "recon"))] #[async_trait] impl AuthenticateAndFetch for ReconJWT { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &AppState, ) -> RouterResult<(ReconUser, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; Ok(( ReconUser { user_id: payload.user_id, }, AuthenticationType::NoAuth, )) } }