mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
294 lines
8.5 KiB
Rust
294 lines
8.5 KiB
Rust
use actix_web::http::header::HeaderMap;
|
|
use api_models::{payment_methods::ListPaymentMethodRequest, payments::PaymentsRequest};
|
|
use async_trait::async_trait;
|
|
use error_stack::{report, IntoReport, ResultExt};
|
|
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
|
|
|
use crate::{
|
|
core::errors::{self, RouterResult, StorageErrorExt},
|
|
db::StorageInterface,
|
|
routes::{app::AppStateInfo, AppState},
|
|
services::api,
|
|
types::storage,
|
|
utils::OptionExt,
|
|
};
|
|
|
|
#[async_trait]
|
|
pub trait AuthenticateAndFetch<T, A>
|
|
where
|
|
A: AppStateInfo,
|
|
{
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
request_headers: &HeaderMap,
|
|
state: &A,
|
|
) -> RouterResult<T>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ApiKeyAuth;
|
|
|
|
#[async_trait]
|
|
impl AuthenticateAndFetch<storage::MerchantAccount, AppState> for ApiKeyAuth {
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
request_headers: &HeaderMap,
|
|
state: &AppState,
|
|
) -> RouterResult<storage::MerchantAccount> {
|
|
let api_key =
|
|
get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
|
state
|
|
.store
|
|
.find_merchant_account_by_api_key(api_key)
|
|
.await
|
|
.change_context(errors::ApiErrorResponse::Unauthorized)
|
|
.attach_printable("Merchant not authenticated")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AdminApiAuth;
|
|
|
|
#[async_trait]
|
|
impl AuthenticateAndFetch<(), AppState> for AdminApiAuth {
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
request_headers: &HeaderMap,
|
|
state: &AppState,
|
|
) -> RouterResult<()> {
|
|
let admin_api_key =
|
|
get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
|
if admin_api_key != state.conf.secrets.admin_api_key {
|
|
Err(report!(errors::ApiErrorResponse::Unauthorized)
|
|
.attach_printable("Admin Authentication Failure"))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct MerchantIdAuth(pub String);
|
|
|
|
#[async_trait]
|
|
impl AuthenticateAndFetch<storage::MerchantAccount, AppState> for MerchantIdAuth {
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
_request_headers: &HeaderMap,
|
|
state: &AppState,
|
|
) -> RouterResult<storage::MerchantAccount> {
|
|
state
|
|
.store
|
|
.find_merchant_account_by_merchant_id(self.0.as_ref())
|
|
.await
|
|
.map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::Unauthorized))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PublishableKeyAuth;
|
|
|
|
#[async_trait]
|
|
impl AuthenticateAndFetch<storage::MerchantAccount, AppState> for PublishableKeyAuth {
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
request_headers: &HeaderMap,
|
|
state: &AppState,
|
|
) -> RouterResult<storage::MerchantAccount> {
|
|
let publishable_key =
|
|
get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
|
state
|
|
.store
|
|
.find_merchant_account_by_publishable_key(publishable_key)
|
|
.await
|
|
.change_context(errors::ApiErrorResponse::Unauthorized)
|
|
.attach_printable("Merchant not authenticated")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct JWTAuth;
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct JwtAuthPayloadFetchUnit {
|
|
#[serde(rename(deserialize = "exp"))]
|
|
_exp: u64,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl AuthenticateAndFetch<(), AppState> for JWTAuth {
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
request_headers: &HeaderMap,
|
|
state: &AppState,
|
|
) -> RouterResult<()> {
|
|
let mut token = get_jwt(request_headers)?;
|
|
token = strip_jwt_token(token)?;
|
|
decode_jwt::<JwtAuthPayloadFetchUnit>(token, state).map(|_| ())
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct JwtAuthPayloadFetchMerchantAccount {
|
|
merchant_id: String,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl AuthenticateAndFetch<storage::MerchantAccount, AppState> for JWTAuth {
|
|
async fn authenticate_and_fetch(
|
|
&self,
|
|
request_headers: &HeaderMap,
|
|
state: &AppState,
|
|
) -> RouterResult<storage::MerchantAccount> {
|
|
let mut token = get_jwt(request_headers)?;
|
|
token = strip_jwt_token(token)?;
|
|
let payload = decode_jwt::<JwtAuthPayloadFetchMerchantAccount>(token, state)?;
|
|
state
|
|
.store
|
|
.find_merchant_account_by_merchant_id(&payload.merchant_id)
|
|
.await
|
|
.change_context(errors::ApiErrorResponse::InvalidJwtToken)
|
|
}
|
|
}
|
|
|
|
pub trait ClientSecretFetch {
|
|
fn get_client_secret(&self) -> Option<&String>;
|
|
}
|
|
|
|
impl ClientSecretFetch for PaymentsRequest {
|
|
fn get_client_secret(&self) -> Option<&String> {
|
|
self.client_secret.as_ref()
|
|
}
|
|
}
|
|
|
|
impl ClientSecretFetch for ListPaymentMethodRequest {
|
|
fn get_client_secret(&self) -> Option<&String> {
|
|
self.client_secret.as_ref()
|
|
}
|
|
}
|
|
|
|
pub fn jwt_auth_or<'a, T, A: AppStateInfo>(
|
|
default_auth: &'a dyn AuthenticateAndFetch<T, A>,
|
|
headers: &HeaderMap,
|
|
) -> Box<&'a dyn AuthenticateAndFetch<T, A>>
|
|
where
|
|
JWTAuth: AuthenticateAndFetch<T, A>,
|
|
{
|
|
if is_jwt_auth(headers) {
|
|
return Box::new(&JWTAuth);
|
|
}
|
|
Box::new(default_auth)
|
|
}
|
|
|
|
pub fn get_auth_type_and_flow(
|
|
headers: &HeaderMap,
|
|
) -> RouterResult<(
|
|
Box<dyn AuthenticateAndFetch<storage::MerchantAccount, AppState>>,
|
|
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<T>(
|
|
headers: &HeaderMap,
|
|
payload: &impl ClientSecretFetch,
|
|
) -> RouterResult<(
|
|
Box<dyn AuthenticateAndFetch<storage::MerchantAccount, T>>,
|
|
api::AuthFlow,
|
|
)>
|
|
where
|
|
T: AppStateInfo,
|
|
ApiKeyAuth: AuthenticateAndFetch<storage::MerchantAccount, T>,
|
|
PublishableKeyAuth: AuthenticateAndFetch<storage::MerchantAccount, T>,
|
|
{
|
|
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<Box<dyn AuthenticateAndFetch<storage::MerchantAccount, AppState>>> {
|
|
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)))
|
|
}
|
|
|
|
fn is_jwt_auth(headers: &HeaderMap) -> bool {
|
|
headers.get(crate::headers::AUTHORIZATION).is_some()
|
|
}
|
|
|
|
pub fn decode_jwt<T>(token: &str, state: &AppState) -> RouterResult<T>
|
|
where
|
|
T: serde::de::DeserializeOwned,
|
|
{
|
|
let secret = state.conf.secrets.jwt_secret.as_bytes();
|
|
let key = DecodingKey::from_secret(secret);
|
|
decode::<T>(token, &key, &Validation::new(Algorithm::HS256))
|
|
.map(|decoded| decoded.claims)
|
|
.into_report()
|
|
.change_context(errors::ApiErrorResponse::InvalidJwtToken)
|
|
}
|
|
|
|
fn get_api_key(headers: &HeaderMap) -> RouterResult<&str> {
|
|
headers
|
|
.get("api-key")
|
|
.get_required_value("api-key")?
|
|
.to_str()
|
|
.into_report()
|
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
|
.attach_printable("Failed to convert API key to string")
|
|
}
|
|
|
|
fn get_jwt(headers: &HeaderMap) -> RouterResult<&str> {
|
|
headers
|
|
.get(crate::headers::AUTHORIZATION)
|
|
.get_required_value(crate::headers::AUTHORIZATION)?
|
|
.to_str()
|
|
.into_report()
|
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
|
.attach_printable("Failed to convert JWT token to string")
|
|
}
|
|
|
|
fn strip_jwt_token(token: &str) -> RouterResult<&str> {
|
|
token
|
|
.strip_prefix("Bearer ")
|
|
.ok_or_else(|| errors::ApiErrorResponse::InvalidJwtToken.into())
|
|
}
|