refactor(router): Refactored Authentication (#327)

This commit is contained in:
Rachit Naithani
2023-01-10 15:06:34 +05:30
committed by GitHub
parent 98816c05bd
commit 59573efe91
17 changed files with 363 additions and 384 deletions

View File

@ -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")