mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 12:06:56 +08:00
refactor(router): Refactored Authentication (#327)
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
mod client;
|
||||
pub(crate) mod request;
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap, fmt::Debug, future::Future, str, time::Instant};
|
||||
use std::{collections::HashMap, fmt::Debug, future::Future, str, time::Instant};
|
||||
|
||||
use actix_web::{body, HttpRequest, HttpResponse, Responder};
|
||||
use bytes::Bytes;
|
||||
@ -15,18 +15,18 @@ pub use self::request::{Method, Request, RequestBuilder};
|
||||
use crate::{
|
||||
configs::settings::Connectors,
|
||||
core::{
|
||||
errors::{self, CustomResult, RouterResponse, RouterResult, StorageErrorExt},
|
||||
errors::{self, CustomResult, RouterResponse, RouterResult},
|
||||
payments,
|
||||
},
|
||||
db::StorageInterface,
|
||||
logger,
|
||||
routes::AppState,
|
||||
services::authentication as auth,
|
||||
types::{
|
||||
self, api,
|
||||
storage::{self, enums},
|
||||
storage::{self},
|
||||
ErrorResponse,
|
||||
},
|
||||
utils::{self, OptionExt},
|
||||
};
|
||||
|
||||
pub type BoxedConnectorIntegration<'a, T, Req, Resp> =
|
||||
@ -363,104 +363,49 @@ impl RedirectForm {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ApiAuthentication<'a> {
|
||||
Merchant(MerchantAuthentication<'a>),
|
||||
Connector(ConnectorAuthentication<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MerchantAuthentication<'a> {
|
||||
ApiKey,
|
||||
MerchantId(Cow<'a, str>),
|
||||
AdminApiKey,
|
||||
PublishableKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConnectorAuthentication<'a> {
|
||||
MerchantId(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> From<MerchantAuthentication<'a>> for ApiAuthentication<'a> {
|
||||
fn from(merchant_auth: MerchantAuthentication<'a>) -> Self {
|
||||
ApiAuthentication::Merchant(merchant_auth)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ConnectorAuthentication<'a>> for ApiAuthentication<'a> {
|
||||
fn from(connector_auth: ConnectorAuthentication<'a>) -> Self {
|
||||
ApiAuthentication::Connector(connector_auth)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AuthFlow {
|
||||
Client,
|
||||
Merchant,
|
||||
}
|
||||
|
||||
pub fn get_auth_flow(auth_type: &MerchantAuthentication<'_>) -> AuthFlow {
|
||||
match auth_type {
|
||||
MerchantAuthentication::ApiKey => AuthFlow::Merchant,
|
||||
_ => AuthFlow::Client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_auth_type(request: &HttpRequest) -> RouterResult<MerchantAuthentication<'_>> {
|
||||
let api_key = get_api_key(request).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
||||
if api_key.starts_with("pk_") {
|
||||
Ok(MerchantAuthentication::PublishableKey)
|
||||
} else {
|
||||
Ok(MerchantAuthentication::ApiKey)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(request, payload, state, func))]
|
||||
pub async fn server_wrap_util<'a, 'b, T, Q, F, Fut>(
|
||||
#[instrument(skip(request, payload, state, func, api_auth))]
|
||||
pub async fn server_wrap_util<'a, 'b, U, T, Q, F, Fut>(
|
||||
state: &'b AppState,
|
||||
request: &'a HttpRequest,
|
||||
payload: T,
|
||||
func: F,
|
||||
api_authentication: ApiAuthentication<'a>,
|
||||
api_auth: &dyn auth::AuthenticateAndFetch<U>,
|
||||
) -> RouterResult<BachResponse<Q>>
|
||||
where
|
||||
F: Fn(&'b AppState, storage::MerchantAccount, T) -> Fut,
|
||||
F: Fn(&'b AppState, U, T) -> Fut,
|
||||
Fut: Future<Output = RouterResponse<Q>>,
|
||||
Q: Serialize + Debug + 'a,
|
||||
T: Debug,
|
||||
{
|
||||
let merchant_account = match api_authentication {
|
||||
ApiAuthentication::Merchant(merchant_auth) => {
|
||||
authenticate_merchant(request, state, merchant_auth).await?
|
||||
}
|
||||
ApiAuthentication::Connector(connector_auth) => {
|
||||
authenticate_connector(request, &*state.store, connector_auth).await?
|
||||
}
|
||||
};
|
||||
logger::debug!(request=?payload);
|
||||
func(state, merchant_account, payload).await
|
||||
let auth_out = api_auth
|
||||
.authenticate_and_fetch(request.headers(), state)
|
||||
.await?;
|
||||
func(state, auth_out, payload).await
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
skip(request, payload, state, func),
|
||||
skip(request, payload, state, func, api_auth),
|
||||
fields(request_method, request_url_path)
|
||||
)]
|
||||
pub async fn server_wrap<'a, 'b, A, T, Q, F, Fut>(
|
||||
pub async fn server_wrap<'a, 'b, T, U, Q, F, Fut>(
|
||||
state: &'b AppState,
|
||||
request: &'a HttpRequest,
|
||||
payload: T,
|
||||
func: F,
|
||||
api_authentication: A,
|
||||
api_auth: &dyn auth::AuthenticateAndFetch<U>,
|
||||
) -> HttpResponse
|
||||
where
|
||||
A: Into<ApiAuthentication<'a>> + Debug,
|
||||
F: Fn(&'b AppState, storage::MerchantAccount, T) -> Fut,
|
||||
F: Fn(&'b AppState, U, T) -> Fut,
|
||||
Fut: Future<Output = RouterResult<BachResponse<Q>>>,
|
||||
Q: Serialize + Debug + 'a,
|
||||
T: Debug,
|
||||
{
|
||||
let api_authentication = api_authentication.into();
|
||||
let request_method = request.method().as_str();
|
||||
let url_path = request.path();
|
||||
tracing::Span::current().record("request_method", request_method);
|
||||
@ -469,7 +414,7 @@ where
|
||||
let start_instant = Instant::now();
|
||||
logger::info!(tag = ?Tag::BeginRequest);
|
||||
|
||||
let res = match server_wrap_util(state, request, payload, func, api_authentication).await {
|
||||
let res = match server_wrap_util(state, request, payload, func, api_auth).await {
|
||||
Ok(BachResponse::Json(response)) => match serde_json::to_string(&response) {
|
||||
Ok(res) => http_response_json(res),
|
||||
Err(_) => http_response_err(
|
||||
@ -519,120 +464,6 @@ where
|
||||
error.current_context().error_response()
|
||||
}
|
||||
|
||||
pub async fn authenticate_merchant<'a>(
|
||||
request: &HttpRequest,
|
||||
state: &AppState,
|
||||
merchant_authentication: MerchantAuthentication<'a>,
|
||||
) -> RouterResult<storage::MerchantAccount> {
|
||||
match merchant_authentication {
|
||||
MerchantAuthentication::ApiKey => {
|
||||
let api_key =
|
||||
get_api_key(request).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
||||
authenticate_by_api_key(&*state.store, api_key).await
|
||||
}
|
||||
|
||||
MerchantAuthentication::MerchantId(merchant_id) => (*state.store)
|
||||
.find_merchant_account_by_merchant_id(&merchant_id)
|
||||
.await
|
||||
.map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::Unauthorized)),
|
||||
|
||||
MerchantAuthentication::AdminApiKey => {
|
||||
let admin_api_key =
|
||||
get_api_key(request).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
||||
utils::when(admin_api_key != state.conf.keys.admin_api_key, || {
|
||||
Err(errors::ApiErrorResponse::Unauthorized)
|
||||
.into_report()
|
||||
.attach_printable("Admin Authentication Failure")
|
||||
})?;
|
||||
|
||||
Ok(storage::MerchantAccount {
|
||||
id: -1,
|
||||
merchant_id: String::from("juspay"),
|
||||
merchant_name: None,
|
||||
api_key: None,
|
||||
merchant_details: None,
|
||||
return_url: None,
|
||||
webhook_details: None,
|
||||
routing_algorithm: None,
|
||||
custom_routing_rules: None,
|
||||
sub_merchants_enabled: None,
|
||||
parent_merchant_id: None,
|
||||
enable_payment_response_hash: false,
|
||||
payment_response_hash_key: None,
|
||||
redirect_to_merchant_with_http_post: false,
|
||||
publishable_key: None,
|
||||
storage_scheme: enums::MerchantStorageScheme::PostgresOnly,
|
||||
locker_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
MerchantAuthentication::PublishableKey => {
|
||||
let api_key =
|
||||
get_api_key(request).change_context(errors::ApiErrorResponse::Unauthorized)?;
|
||||
authenticate_by_publishable_key(&*state.store, api_key).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn authenticate_connector<'a>(
|
||||
_request: &HttpRequest,
|
||||
store: &dyn StorageInterface,
|
||||
connector_authentication: ConnectorAuthentication<'a>,
|
||||
) -> RouterResult<storage::MerchantAccount> {
|
||||
match connector_authentication {
|
||||
ConnectorAuthentication::MerchantId(merchant_id) => store
|
||||
.find_merchant_account_by_merchant_id(merchant_id)
|
||||
.await
|
||||
.map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::Unauthorized)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_auth_type_and_check_client_secret<P>(
|
||||
req: &HttpRequest,
|
||||
payload: P,
|
||||
) -> RouterResult<(P, MerchantAuthentication<'_>)>
|
||||
where
|
||||
P: Authenticate,
|
||||
{
|
||||
let auth_type = get_auth_type(req)?;
|
||||
Ok((
|
||||
payments::helpers::client_secret_auth(payload, &auth_type)?,
|
||||
auth_type,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn authenticate_eph_key<'a>(
|
||||
req: &'a HttpRequest,
|
||||
store: &dyn StorageInterface,
|
||||
customer_id: String,
|
||||
) -> RouterResult<MerchantAuthentication<'a>> {
|
||||
let api_key = get_api_key(req)?;
|
||||
if api_key.starts_with("epk") {
|
||||
let ek = store
|
||||
.get_ephemeral_key(api_key)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::Unauthorized)?;
|
||||
utils::when(ek.customer_id.ne(&customer_id), || {
|
||||
Err(report!(errors::ApiErrorResponse::InvalidEphermeralKey))
|
||||
})?;
|
||||
Ok(MerchantAuthentication::MerchantId(Cow::Owned(
|
||||
ek.merchant_id,
|
||||
)))
|
||||
} else {
|
||||
Ok(MerchantAuthentication::ApiKey)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_api_key(req: &HttpRequest) -> RouterResult<&str> {
|
||||
req.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")
|
||||
}
|
||||
|
||||
pub async fn authenticate_by_api_key(
|
||||
store: &dyn StorageInterface,
|
||||
api_key: &str,
|
||||
@ -644,17 +475,6 @@ pub async fn authenticate_by_api_key(
|
||||
.attach_printable("Merchant not authenticated")
|
||||
}
|
||||
|
||||
async fn authenticate_by_publishable_key(
|
||||
store: &dyn StorageInterface,
|
||||
publishable_key: &str,
|
||||
) -> RouterResult<storage::MerchantAccount> {
|
||||
store
|
||||
.find_merchant_account_by_publishable_key(publishable_key)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::Unauthorized)
|
||||
.attach_printable("Merchant not authenticated")
|
||||
}
|
||||
|
||||
pub fn http_response_json<T: body::MessageBody + 'static>(response: T) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
|
||||
Reference in New Issue
Block a user