refactor(router): Introduce ApiKeyId id type (#6324)

This commit is contained in:
Anurag Thakur
2024-10-21 19:19:31 +05:30
committed by GitHub
parent 58296ffae6
commit b3ce373f8e
14 changed files with 166 additions and 75 deletions

View File

@ -1,5 +1,8 @@
[default] [default]
check-filename = true check-filename = true
extend-ignore-identifiers-re = [
"UE_[0-9]{3,4}", # Unified error codes
]
[default.extend-identifiers] [default.extend-identifiers]
ABD = "ABD" # Aberdeenshire, UK ISO 3166-2 code ABD = "ABD" # Aberdeenshire, UK ISO 3166-2 code
@ -38,7 +41,6 @@ ws2ipdef = "ws2ipdef" # WinSock Extension
ws2tcpip = "ws2tcpip" # WinSock Extension ws2tcpip = "ws2tcpip" # WinSock Extension
ZAR = "ZAR" # South African Rand currency code ZAR = "ZAR" # South African Rand currency code
JOD = "JOD" # Jordan currency code JOD = "JOD" # Jordan currency code
UE_000 = "UE_000" #default unified error code
[default.extend-words] [default.extend-words]

View File

@ -29,8 +29,8 @@ pub struct CreateApiKeyRequest {
#[derive(Debug, Serialize, ToSchema)] #[derive(Debug, Serialize, ToSchema)]
pub struct CreateApiKeyResponse { pub struct CreateApiKeyResponse {
/// The identifier for the API Key. /// The identifier for the API Key.
#[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")] #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)]
pub key_id: String, pub key_id: common_utils::id_type::ApiKeyId,
/// The identifier for the Merchant Account. /// The identifier for the Merchant Account.
#[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)] #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)]
@ -72,8 +72,8 @@ pub struct CreateApiKeyResponse {
#[derive(Debug, Serialize, ToSchema)] #[derive(Debug, Serialize, ToSchema)]
pub struct RetrieveApiKeyResponse { pub struct RetrieveApiKeyResponse {
/// The identifier for the API Key. /// The identifier for the API Key.
#[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")] #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)]
pub key_id: String, pub key_id: common_utils::id_type::ApiKeyId,
/// The identifier for the Merchant Account. /// The identifier for the Merchant Account.
#[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)] #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)]
@ -131,7 +131,8 @@ pub struct UpdateApiKeyRequest {
pub expiration: Option<ApiKeyExpiration>, pub expiration: Option<ApiKeyExpiration>,
#[serde(skip_deserializing)] #[serde(skip_deserializing)]
pub key_id: String, #[schema(value_type = String)]
pub key_id: common_utils::id_type::ApiKeyId,
#[serde(skip_deserializing)] #[serde(skip_deserializing)]
#[schema(value_type = String)] #[schema(value_type = String)]
@ -146,8 +147,8 @@ pub struct RevokeApiKeyResponse {
pub merchant_id: common_utils::id_type::MerchantId, pub merchant_id: common_utils::id_type::MerchantId,
/// The identifier for the API Key. /// The identifier for the API Key.
#[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")] #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)]
pub key_id: String, pub key_id: common_utils::id_type::ApiKeyId,
/// Indicates whether the API key was revoked or not. /// Indicates whether the API key was revoked or not.
#[schema(example = "true")] #[schema(example = "true")]
pub revoked: bool, pub revoked: bool,

View File

@ -45,6 +45,9 @@ pub enum ApiEventsType {
BusinessProfile { BusinessProfile {
profile_id: id_type::ProfileId, profile_id: id_type::ProfileId,
}, },
ApiKey {
key_id: id_type::ApiKeyId,
},
User { User {
user_id: String, user_id: String,
}, },
@ -130,10 +133,6 @@ impl_api_event_type!(
( (
String, String,
id_type::MerchantId, id_type::MerchantId,
(id_type::MerchantId, String),
(id_type::MerchantId, &String),
(&id_type::MerchantId, &String),
(&String, &String),
(Option<i64>, Option<i64>, String), (Option<i64>, Option<i64>, String),
(Option<i64>, Option<i64>, id_type::MerchantId), (Option<i64>, Option<i64>, id_type::MerchantId),
bool bool

View File

@ -3,6 +3,7 @@
use std::{borrow::Cow, fmt::Debug}; use std::{borrow::Cow, fmt::Debug};
mod api_key;
mod customer; mod customer;
mod merchant; mod merchant;
mod merchant_connector_account; mod merchant_connector_account;
@ -14,6 +15,7 @@ mod routing;
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
mod global_id; mod global_id;
pub use api_key::ApiKeyId;
pub use customer::CustomerId; pub use customer::CustomerId;
use diesel::{ use diesel::{
backend::Backend, backend::Backend,

View File

@ -0,0 +1,46 @@
crate::id_type!(
ApiKeyId,
"A type for key_id that can be used for API key IDs"
);
crate::impl_id_type_methods!(ApiKeyId, "key_id");
// This is to display the `ApiKeyId` as ApiKeyId(abcd)
crate::impl_debug_id_type!(ApiKeyId);
crate::impl_try_from_cow_str_id_type!(ApiKeyId, "key_id");
crate::impl_serializable_secret_id_type!(ApiKeyId);
crate::impl_queryable_id_type!(ApiKeyId);
crate::impl_to_sql_from_sql_id_type!(ApiKeyId);
impl ApiKeyId {
/// Generate Api Key Id from prefix
pub fn generate_key_id(prefix: &'static str) -> Self {
Self(crate::generate_ref_id_with_default_length(prefix))
}
}
impl crate::events::ApiEventMetric for ApiKeyId {
fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
Some(crate::events::ApiEventsType::ApiKey {
key_id: self.clone(),
})
}
}
impl crate::events::ApiEventMetric for (super::MerchantId, ApiKeyId) {
fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
Some(crate::events::ApiEventsType::ApiKey {
key_id: self.1.clone(),
})
}
}
impl crate::events::ApiEventMetric for (&super::MerchantId, &ApiKeyId) {
fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
Some(crate::events::ApiEventsType::ApiKey {
key_id: self.1.clone(),
})
}
}
crate::impl_default_id_type!(ApiKeyId, "key");

View File

@ -9,7 +9,7 @@ use crate::schema::api_keys;
)] )]
#[diesel(table_name = api_keys, primary_key(key_id), check_for_backend(diesel::pg::Pg))] #[diesel(table_name = api_keys, primary_key(key_id), check_for_backend(diesel::pg::Pg))]
pub struct ApiKey { pub struct ApiKey {
pub key_id: String, pub key_id: common_utils::id_type::ApiKeyId,
pub merchant_id: common_utils::id_type::MerchantId, pub merchant_id: common_utils::id_type::MerchantId,
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
@ -23,7 +23,7 @@ pub struct ApiKey {
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = api_keys)] #[diesel(table_name = api_keys)]
pub struct ApiKeyNew { pub struct ApiKeyNew {
pub key_id: String, pub key_id: common_utils::id_type::ApiKeyId,
pub merchant_id: common_utils::id_type::MerchantId, pub merchant_id: common_utils::id_type::MerchantId,
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
@ -141,7 +141,7 @@ mod diesel_impl {
// Tracking data by process_tracker // Tracking data by process_tracker
#[derive(Default, Debug, Deserialize, Serialize, Clone)] #[derive(Default, Debug, Deserialize, Serialize, Clone)]
pub struct ApiKeyExpiryTrackingData { pub struct ApiKeyExpiryTrackingData {
pub key_id: String, pub key_id: common_utils::id_type::ApiKeyId,
pub merchant_id: common_utils::id_type::MerchantId, pub merchant_id: common_utils::id_type::MerchantId,
pub api_key_name: String, pub api_key_name: String,
pub prefix: String, pub prefix: String,

View File

@ -18,7 +18,7 @@ impl ApiKey {
pub async fn update_by_merchant_id_key_id( pub async fn update_by_merchant_id_key_id(
conn: &PgPooledConn, conn: &PgPooledConn,
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: String, key_id: common_utils::id_type::ApiKeyId,
api_key_update: ApiKeyUpdate, api_key_update: ApiKeyUpdate,
) -> StorageResult<Self> { ) -> StorageResult<Self> {
match generics::generic_update_with_unique_predicate_get_result::< match generics::generic_update_with_unique_predicate_get_result::<
@ -57,7 +57,7 @@ impl ApiKey {
pub async fn revoke_by_merchant_id_key_id( pub async fn revoke_by_merchant_id_key_id(
conn: &PgPooledConn, conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> StorageResult<bool> { ) -> StorageResult<bool> {
generics::generic_delete::<<Self as HasTable>::Table, _>( generics::generic_delete::<<Self as HasTable>::Table, _>(
conn, conn,
@ -71,7 +71,7 @@ impl ApiKey {
pub async fn find_optional_by_merchant_id_key_id( pub async fn find_optional_by_merchant_id_key_id(
conn: &PgPooledConn, conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> StorageResult<Option<Self>> { ) -> StorageResult<Option<Self>> {
generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>( generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
conn, conn,

View File

@ -13,7 +13,6 @@ use crate::{
routes::{metrics, SessionState}, routes::{metrics, SessionState},
services::{authentication, ApplicationResponse}, services::{authentication, ApplicationResponse},
types::{api, storage, transformers::ForeignInto}, types::{api, storage, transformers::ForeignInto},
utils,
}; };
#[cfg(feature = "email")] #[cfg(feature = "email")]
@ -63,9 +62,9 @@ impl PlaintextApiKey {
Self(format!("{env}_{key}").into()) Self(format!("{env}_{key}").into())
} }
pub fn new_key_id() -> String { pub fn new_key_id() -> common_utils::id_type::ApiKeyId {
let env = router_env::env::prefix_for_env(); let env = router_env::env::prefix_for_env();
utils::generate_id(consts::ID_LENGTH, env) common_utils::id_type::ApiKeyId::generate_key_id(env)
} }
pub fn prefix(&self) -> String { pub fn prefix(&self) -> String {
@ -223,7 +222,7 @@ pub async fn add_api_key_expiry_task(
expiry_reminder_days: expiry_reminder_days.clone(), expiry_reminder_days: expiry_reminder_days.clone(),
}; };
let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(api_key.key_id.as_str()); let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id);
let process_tracker_entry = storage::ProcessTrackerNew::new( let process_tracker_entry = storage::ProcessTrackerNew::new(
process_tracker_id, process_tracker_id,
API_KEY_EXPIRY_NAME, API_KEY_EXPIRY_NAME,
@ -241,7 +240,7 @@ pub async fn add_api_key_expiry_task(
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| { .attach_printable_lazy(|| {
format!( format!(
"Failed while inserting API key expiry reminder to process_tracker: api_key_id: {}", "Failed while inserting API key expiry reminder to process_tracker: {:?}",
api_key.key_id api_key.key_id
) )
})?; })?;
@ -258,11 +257,11 @@ pub async fn add_api_key_expiry_task(
pub async fn retrieve_api_key( pub async fn retrieve_api_key(
state: SessionState, state: SessionState,
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: &str, key_id: common_utils::id_type::ApiKeyId,
) -> RouterResponse<api::RetrieveApiKeyResponse> { ) -> RouterResponse<api::RetrieveApiKeyResponse> {
let store = state.store.as_ref(); let store = state.store.as_ref();
let api_key = store let api_key = store
.find_api_key_by_merchant_id_key_id_optional(&merchant_id, key_id) .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
.attach_printable("Failed to retrieve API key")? .attach_printable("Failed to retrieve API key")?
@ -388,7 +387,7 @@ pub async fn update_api_key_expiry_task(
} }
} }
let task_id = generate_task_id_for_api_key_expiry_workflow(api_key.key_id.as_str()); let task_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id);
let task_ids = vec![task_id.clone()]; let task_ids = vec![task_id.clone()];
@ -430,7 +429,7 @@ pub async fn update_api_key_expiry_task(
pub async fn revoke_api_key( pub async fn revoke_api_key(
state: SessionState, state: SessionState,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> RouterResponse<api::RevokeApiKeyResponse> { ) -> RouterResponse<api::RevokeApiKeyResponse> {
let store = state.store.as_ref(); let store = state.store.as_ref();
@ -496,7 +495,7 @@ pub async fn revoke_api_key(
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn revoke_api_key_expiry_task( pub async fn revoke_api_key_expiry_task(
store: &dyn crate::db::StorageInterface, store: &dyn crate::db::StorageInterface,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> Result<(), errors::ProcessTrackerError> { ) -> Result<(), errors::ProcessTrackerError> {
let task_id = generate_task_id_for_api_key_expiry_workflow(key_id); let task_id = generate_task_id_for_api_key_expiry_workflow(key_id);
let task_ids = vec![task_id]; let task_ids = vec![task_id];
@ -535,8 +534,13 @@ pub async fn list_api_keys(
} }
#[cfg(feature = "email")] #[cfg(feature = "email")]
fn generate_task_id_for_api_key_expiry_workflow(key_id: &str) -> String { fn generate_task_id_for_api_key_expiry_workflow(
format!("{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{key_id}") key_id: &common_utils::id_type::ApiKeyId,
) -> String {
format!(
"{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{}",
key_id.get_string_repr()
)
} }
impl From<&str> for PlaintextApiKey { impl From<&str> for PlaintextApiKey {

View File

@ -20,20 +20,20 @@ pub trait ApiKeyInterface {
async fn update_api_key( async fn update_api_key(
&self, &self,
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: String, key_id: common_utils::id_type::ApiKeyId,
api_key: storage::ApiKeyUpdate, api_key: storage::ApiKeyUpdate,
) -> CustomResult<storage::ApiKey, errors::StorageError>; ) -> CustomResult<storage::ApiKey, errors::StorageError>;
async fn revoke_api_key( async fn revoke_api_key(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> CustomResult<bool, errors::StorageError>; ) -> CustomResult<bool, errors::StorageError>;
async fn find_api_key_by_merchant_id_key_id_optional( async fn find_api_key_by_merchant_id_key_id_optional(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>; ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError>;
async fn find_api_key_by_hash_optional( async fn find_api_key_by_hash_optional(
@ -67,7 +67,7 @@ impl ApiKeyInterface for Store {
async fn update_api_key( async fn update_api_key(
&self, &self,
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: String, key_id: common_utils::id_type::ApiKeyId,
api_key: storage::ApiKeyUpdate, api_key: storage::ApiKeyUpdate,
) -> CustomResult<storage::ApiKey, errors::StorageError> { ) -> CustomResult<storage::ApiKey, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?; let conn = connection::pg_connection_write(self).await?;
@ -99,7 +99,8 @@ impl ApiKeyInterface for Store {
.await .await
.map_err(|error| report!(errors::StorageError::from(error)))? .map_err(|error| report!(errors::StorageError::from(error)))?
.ok_or(report!(errors::StorageError::ValueNotFound(format!( .ok_or(report!(errors::StorageError::ValueNotFound(format!(
"ApiKey of {_key_id} not found" "ApiKey of {} not found",
_key_id.get_string_repr()
))))?; ))))?;
cache::publish_and_redact( cache::publish_and_redact(
@ -115,7 +116,7 @@ impl ApiKeyInterface for Store {
async fn revoke_api_key( async fn revoke_api_key(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> CustomResult<bool, errors::StorageError> { ) -> CustomResult<bool, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?; let conn = connection::pg_connection_write(self).await?;
let delete_call = || async { let delete_call = || async {
@ -141,7 +142,8 @@ impl ApiKeyInterface for Store {
.await .await
.map_err(|error| report!(errors::StorageError::from(error)))? .map_err(|error| report!(errors::StorageError::from(error)))?
.ok_or(report!(errors::StorageError::ValueNotFound(format!( .ok_or(report!(errors::StorageError::ValueNotFound(format!(
"ApiKey of {key_id} not found" "ApiKey of {} not found",
key_id.get_string_repr()
))))?; ))))?;
cache::publish_and_redact( cache::publish_and_redact(
@ -157,7 +159,7 @@ impl ApiKeyInterface for Store {
async fn find_api_key_by_merchant_id_key_id_optional( async fn find_api_key_by_merchant_id_key_id_optional(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> { ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?; let conn = connection::pg_connection_read(self).await?;
storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id) storage::ApiKey::find_optional_by_merchant_id_key_id(&conn, merchant_id, key_id)
@ -240,7 +242,7 @@ impl ApiKeyInterface for MockDb {
async fn update_api_key( async fn update_api_key(
&self, &self,
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: String, key_id: common_utils::id_type::ApiKeyId,
api_key: storage::ApiKeyUpdate, api_key: storage::ApiKeyUpdate,
) -> CustomResult<storage::ApiKey, errors::StorageError> { ) -> CustomResult<storage::ApiKey, errors::StorageError> {
let mut locked_api_keys = self.api_keys.lock().await; let mut locked_api_keys = self.api_keys.lock().await;
@ -282,13 +284,13 @@ impl ApiKeyInterface for MockDb {
async fn revoke_api_key( async fn revoke_api_key(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> CustomResult<bool, errors::StorageError> { ) -> CustomResult<bool, errors::StorageError> {
let mut locked_api_keys = self.api_keys.lock().await; let mut locked_api_keys = self.api_keys.lock().await;
// find the key to remove, if it exists // find the key to remove, if it exists
if let Some(pos) = locked_api_keys if let Some(pos) = locked_api_keys
.iter() .iter()
.position(|k| k.merchant_id == *merchant_id && k.key_id == key_id) .position(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
{ {
// use `remove` instead of `swap_remove` so we have a consistent order, which might // use `remove` instead of `swap_remove` so we have a consistent order, which might
// matter to someone using limit/offset in `list_api_keys_by_merchant_id` // matter to someone using limit/offset in `list_api_keys_by_merchant_id`
@ -302,14 +304,14 @@ impl ApiKeyInterface for MockDb {
async fn find_api_key_by_merchant_id_key_id_optional( async fn find_api_key_by_merchant_id_key_id_optional(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
key_id: &str, key_id: &common_utils::id_type::ApiKeyId,
) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> { ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
Ok(self Ok(self
.api_keys .api_keys
.lock() .lock()
.await .await
.iter() .iter()
.find(|k| k.merchant_id == *merchant_id && k.key_id == key_id) .find(|k| k.merchant_id == *merchant_id && k.key_id == *key_id)
.cloned()) .cloned())
} }
@ -397,9 +399,16 @@ mod tests {
let merchant_id = let merchant_id =
common_utils::id_type::MerchantId::try_from(Cow::from("merchant1")).unwrap(); common_utils::id_type::MerchantId::try_from(Cow::from("merchant1")).unwrap();
let key_id1 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id1")).unwrap();
let key_id2 = common_utils::id_type::ApiKeyId::try_from(Cow::from("key_id2")).unwrap();
let non_existent_key_id =
common_utils::id_type::ApiKeyId::try_from(Cow::from("does_not_exist")).unwrap();
let key1 = mockdb let key1 = mockdb
.insert_api_key(storage::ApiKeyNew { .insert_api_key(storage::ApiKeyNew {
key_id: "key_id1".into(), key_id: key_id1.clone(),
merchant_id: merchant_id.clone(), merchant_id: merchant_id.clone(),
name: "Key 1".into(), name: "Key 1".into(),
description: None, description: None,
@ -414,7 +423,7 @@ mod tests {
mockdb mockdb
.insert_api_key(storage::ApiKeyNew { .insert_api_key(storage::ApiKeyNew {
key_id: "key_id2".into(), key_id: key_id2.clone(),
merchant_id: merchant_id.clone(), merchant_id: merchant_id.clone(),
name: "Key 2".into(), name: "Key 2".into(),
description: None, description: None,
@ -428,13 +437,13 @@ mod tests {
.unwrap(); .unwrap();
let found_key1 = mockdb let found_key1 = mockdb
.find_api_key_by_merchant_id_key_id_optional(&merchant_id, "key_id1") .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
.await .await
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert_eq!(found_key1.key_id, key1.key_id); assert_eq!(found_key1.key_id, key1.key_id);
assert!(mockdb assert!(mockdb
.find_api_key_by_merchant_id_key_id_optional(&merchant_id, "does_not_exist") .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &non_existent_key_id)
.await .await
.unwrap() .unwrap()
.is_none()); .is_none());
@ -442,7 +451,7 @@ mod tests {
mockdb mockdb
.update_api_key( .update_api_key(
merchant_id.clone(), merchant_id.clone(),
"key_id1".into(), key_id1.clone(),
storage::ApiKeyUpdate::LastUsedUpdate { storage::ApiKeyUpdate::LastUsedUpdate {
last_used: datetime!(2023-02-04 1:11), last_used: datetime!(2023-02-04 1:11),
}, },
@ -450,7 +459,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let updated_key1 = mockdb let updated_key1 = mockdb
.find_api_key_by_merchant_id_key_id_optional(&merchant_id, "key_id1") .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id1)
.await .await
.unwrap() .unwrap()
.unwrap(); .unwrap();
@ -464,10 +473,7 @@ mod tests {
.len(), .len(),
2 2
); );
mockdb mockdb.revoke_api_key(&merchant_id, &key_id1).await.unwrap();
.revoke_api_key(&merchant_id, "key_id1")
.await
.unwrap();
assert_eq!( assert_eq!(
mockdb mockdb
.list_api_keys_by_merchant_id(&merchant_id, None, None) .list_api_keys_by_merchant_id(&merchant_id, None, None)
@ -495,8 +501,10 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let test_key = common_utils::id_type::ApiKeyId::try_from(Cow::from("test_ey")).unwrap();
let api = storage::ApiKeyNew { let api = storage::ApiKeyNew {
key_id: "test_key".into(), key_id: test_key.clone(),
merchant_id: merchant_id.clone(), merchant_id: merchant_id.clone(),
name: "My test key".into(), name: "My test key".into(),
description: None, description: None,

View File

@ -226,7 +226,7 @@ impl ApiKeyInterface for KafkaStore {
async fn update_api_key( async fn update_api_key(
&self, &self,
merchant_id: id_type::MerchantId, merchant_id: id_type::MerchantId,
key_id: String, key_id: id_type::ApiKeyId,
api_key: storage::ApiKeyUpdate, api_key: storage::ApiKeyUpdate,
) -> CustomResult<storage::ApiKey, errors::StorageError> { ) -> CustomResult<storage::ApiKey, errors::StorageError> {
self.diesel_store self.diesel_store
@ -237,7 +237,7 @@ impl ApiKeyInterface for KafkaStore {
async fn revoke_api_key( async fn revoke_api_key(
&self, &self,
merchant_id: &id_type::MerchantId, merchant_id: &id_type::MerchantId,
key_id: &str, key_id: &id_type::ApiKeyId,
) -> CustomResult<bool, errors::StorageError> { ) -> CustomResult<bool, errors::StorageError> {
self.diesel_store.revoke_api_key(merchant_id, key_id).await self.diesel_store.revoke_api_key(merchant_id, key_id).await
} }
@ -245,7 +245,7 @@ impl ApiKeyInterface for KafkaStore {
async fn find_api_key_by_merchant_id_key_id_optional( async fn find_api_key_by_merchant_id_key_id_optional(
&self, &self,
merchant_id: &id_type::MerchantId, merchant_id: &id_type::MerchantId,
key_id: &str, key_id: &id_type::ApiKeyId,
) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> { ) -> CustomResult<Option<storage::ApiKey>, errors::StorageError> {
self.diesel_store self.diesel_store
.find_api_key_by_merchant_id_key_id_optional(merchant_id, key_id) .find_api_key_by_merchant_id_key_id_optional(merchant_id, key_id)

View File

@ -79,7 +79,7 @@ pub async fn api_key_create(
pub async fn api_key_retrieve( pub async fn api_key_retrieve(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
path: web::Path<String>, path: web::Path<common_utils::id_type::ApiKeyId>,
) -> impl Responder { ) -> impl Responder {
let flow = Flow::ApiKeyRetrieve; let flow = Flow::ApiKeyRetrieve;
let key_id = path.into_inner(); let key_id = path.into_inner();
@ -93,7 +93,7 @@ pub async fn api_key_retrieve(
api_keys::retrieve_api_key( api_keys::retrieve_api_key(
state, state,
auth_data.merchant_account.get_id().to_owned(), auth_data.merchant_account.get_id().to_owned(),
key_id, key_id.to_owned(),
) )
}, },
auth::auth_type( auth::auth_type(
@ -114,7 +114,10 @@ pub async fn api_key_retrieve(
pub async fn api_key_retrieve( pub async fn api_key_retrieve(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
path: web::Path<(common_utils::id_type::MerchantId, String)>, path: web::Path<(
common_utils::id_type::MerchantId,
common_utils::id_type::ApiKeyId,
)>,
) -> impl Responder { ) -> impl Responder {
let flow = Flow::ApiKeyRetrieve; let flow = Flow::ApiKeyRetrieve;
let (merchant_id, key_id) = path.into_inner(); let (merchant_id, key_id) = path.into_inner();
@ -123,7 +126,7 @@ pub async fn api_key_retrieve(
flow, flow,
state, state,
&req, &req,
(merchant_id.clone(), &key_id), (merchant_id.clone(), key_id.clone()),
|state, _, (merchant_id, key_id), _| api_keys::retrieve_api_key(state, merchant_id, key_id), |state, _, (merchant_id, key_id), _| api_keys::retrieve_api_key(state, merchant_id, key_id),
auth::auth_type( auth::auth_type(
&auth::AdminApiAuth, &auth::AdminApiAuth,
@ -144,7 +147,10 @@ pub async fn api_key_retrieve(
pub async fn api_key_update( pub async fn api_key_update(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
path: web::Path<(common_utils::id_type::MerchantId, String)>, path: web::Path<(
common_utils::id_type::MerchantId,
common_utils::id_type::ApiKeyId,
)>,
json_payload: web::Json<api_types::UpdateApiKeyRequest>, json_payload: web::Json<api_types::UpdateApiKeyRequest>,
) -> impl Responder { ) -> impl Responder {
let flow = Flow::ApiKeyUpdate; let flow = Flow::ApiKeyUpdate;
@ -177,7 +183,7 @@ pub async fn api_key_update(
pub async fn api_key_update( pub async fn api_key_update(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
key_id: web::Path<String>, key_id: web::Path<common_utils::id_type::ApiKeyId>,
json_payload: web::Json<api_types::UpdateApiKeyRequest>, json_payload: web::Json<api_types::UpdateApiKeyRequest>,
) -> impl Responder { ) -> impl Responder {
let flow = Flow::ApiKeyUpdate; let flow = Flow::ApiKeyUpdate;
@ -212,7 +218,10 @@ pub async fn api_key_update(
pub async fn api_key_revoke( pub async fn api_key_revoke(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
path: web::Path<(common_utils::id_type::MerchantId, String)>, path: web::Path<(
common_utils::id_type::MerchantId,
common_utils::id_type::ApiKeyId,
)>,
) -> impl Responder { ) -> impl Responder {
let flow = Flow::ApiKeyRevoke; let flow = Flow::ApiKeyRevoke;
let (merchant_id, key_id) = path.into_inner(); let (merchant_id, key_id) = path.into_inner();
@ -242,7 +251,10 @@ pub async fn api_key_revoke(
pub async fn api_key_revoke( pub async fn api_key_revoke(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
path: web::Path<(common_utils::id_type::MerchantId, String)>, path: web::Path<(
common_utils::id_type::MerchantId,
common_utils::id_type::ApiKeyId,
)>,
) -> impl Responder { ) -> impl Responder {
let flow = Flow::ApiKeyRevoke; let flow = Flow::ApiKeyRevoke;
let (merchant_id, key_id) = path.into_inner(); let (merchant_id, key_id) = path.into_inner();

View File

@ -89,7 +89,7 @@ pub struct AuthenticationDataWithUser {
pub enum AuthenticationType { pub enum AuthenticationType {
ApiKey { ApiKey {
merchant_id: id_type::MerchantId, merchant_id: id_type::MerchantId,
key_id: String, key_id: id_type::ApiKeyId,
}, },
AdminApiKey, AdminApiKey,
AdminApiAuthWithMerchantId { AdminApiAuthWithMerchantId {

View File

@ -67,7 +67,7 @@ pub enum Identifiers {
/// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`] /// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`]
ApiKey { ApiKey {
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: String, key_id: common_utils::id_type::ApiKeyId,
}, },
/// [`PublishableKey`] is an authentication method that uses a publishable key. This is used with [`PublishableKey`] /// [`PublishableKey`] is an authentication method that uses a publishable key. This is used with [`PublishableKey`]
PublishableKey { merchant_id: String }, PublishableKey { merchant_id: String },
@ -80,7 +80,7 @@ pub async fn add_api_key(
state: &SessionState, state: &SessionState,
api_key: Secret<String>, api_key: Secret<String>,
merchant_id: common_utils::id_type::MerchantId, merchant_id: common_utils::id_type::MerchantId,
key_id: String, key_id: common_utils::id_type::ApiKeyId,
expiry: Option<u64>, expiry: Option<u64>,
) -> CustomResult<(), ApiClientError> { ) -> CustomResult<(), ApiClientError> {
let decision_config = if let Some(config) = &state.conf.decision { let decision_config = if let Some(config) = &state.conf.decision {

View File

@ -1,7 +1,10 @@
use std::{borrow::Cow, string::ToString}; use std::{borrow::Cow, string::ToString};
use actix_web::http::header::HeaderMap; use actix_web::http::header::HeaderMap;
use common_utils::{crypto::VerifySignature, id_type::MerchantId}; use common_utils::{
crypto::VerifySignature,
id_type::{ApiKeyId, MerchantId},
};
use error_stack::ResultExt; use error_stack::ResultExt;
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;
@ -16,7 +19,7 @@ const HEADER_CHECKSUM: &str = "x-checksum";
pub struct ExtractedPayload { pub struct ExtractedPayload {
pub payload_type: PayloadType, pub payload_type: PayloadType,
pub merchant_id: Option<MerchantId>, pub merchant_id: Option<MerchantId>,
pub key_id: Option<String>, pub key_id: Option<ApiKeyId>,
} }
#[derive(strum::EnumString, strum::Display, PartialEq, Debug)] #[derive(strum::EnumString, strum::Display, PartialEq, Debug)]
@ -61,13 +64,19 @@ impl ExtractedPayload {
message: format!("`{}` header not present", HEADER_AUTH_TYPE), message: format!("`{}` header not present", HEADER_AUTH_TYPE),
})?; })?;
let key_id = headers
.get(HEADER_KEY_ID)
.and_then(|value| value.to_str().ok())
.map(|key_id| ApiKeyId::try_from(Cow::from(key_id.to_string())))
.transpose()
.change_context(ApiErrorResponse::InvalidRequestData {
message: format!("`{}` header is invalid or not present", HEADER_KEY_ID),
})?;
Ok(Self { Ok(Self {
payload_type: auth_type, payload_type: auth_type,
merchant_id: Some(merchant_id), merchant_id: Some(merchant_id),
key_id: headers key_id,
.get(HEADER_KEY_ID)
.and_then(|v| v.to_str().ok())
.map(|v| v.to_string()),
}) })
} }
@ -95,7 +104,7 @@ impl ExtractedPayload {
&self &self
.merchant_id .merchant_id
.as_ref() .as_ref()
.map(|inner| append_option(inner.get_string_repr(), &self.key_id)), .map(|inner| append_api_key(inner.get_string_repr(), &self.key_id)),
) )
} }
} }
@ -107,3 +116,11 @@ fn append_option(prefix: &str, data: &Option<String>) -> String {
None => prefix.to_string(), None => prefix.to_string(),
} }
} }
#[inline]
fn append_api_key(prefix: &str, data: &Option<ApiKeyId>) -> String {
match data {
Some(inner) => format!("{}:{}", prefix, inner.get_string_repr()),
None => prefix.to_string(),
}
}