refactor(authentication): authenticate merchant by API keys from API keys table (#712)

This commit is contained in:
Sanchith Hegde
2023-03-08 14:34:22 +05:30
committed by GitHub
parent 1a27facaa7
commit afd08d42c7
8 changed files with 129 additions and 47 deletions

View File

@ -1,10 +1,8 @@
use common_utils::{date_time, errors::CustomResult, fp_utils};
use common_utils::date_time;
use error_stack::{report, IntoReport, ResultExt};
use masking::{PeekInterface, Secret, StrongSecret};
use masking::{PeekInterface, StrongSecret};
use router_env::{instrument, tracing};
#[cfg(feature = "kms")]
use crate::services::kms;
use crate::{
configs::settings,
consts,
@ -14,6 +12,8 @@ use crate::{
types::{api, storage, transformers::ForeignInto},
utils,
};
#[cfg(feature = "kms")]
use crate::{routes::metrics, services::kms};
pub static HASH_KEY: tokio::sync::OnceCell<StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> =
tokio::sync::OnceCell::const_new();
@ -28,6 +28,10 @@ pub async fn get_hash_key(
api_key_config.kms_encrypted_hash_key.clone(),
)
.await
.map_err(|error| {
metrics::AWS_KMS_FAILURES.add(&metrics::CONTEXT, 1, &[]);
error
})
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to KMS decrypt API key hashing key")?;
@ -49,11 +53,13 @@ pub async fn get_hash_key(
// Defining new types `PlaintextApiKey` and `HashedApiKey` in the hopes of reducing the possibility
// of plaintext API key being stored in the data store.
pub struct PlaintextApiKey(Secret<String>);
pub struct PlaintextApiKey(StrongSecret<String>);
#[derive(Debug, PartialEq, Eq)]
pub struct HashedApiKey(String);
impl PlaintextApiKey {
pub const HASH_KEY_LEN: usize = 32;
const HASH_KEY_LEN: usize = 32;
const PREFIX_LEN: usize = 12;
@ -107,22 +113,6 @@ impl PlaintextApiKey {
.to_string(),
)
}
pub fn verify_hash(
&self,
key: &[u8; Self::HASH_KEY_LEN],
stored_api_key: &HashedApiKey,
) -> CustomResult<(), errors::ApiKeyError> {
// Converting both hashes to `blake3::Hash` since it provides constant-time equality checks
let provided_api_key_hash = blake3::keyed_hash(key, self.0.peek().as_bytes());
let stored_api_key_hash = blake3::Hash::from_hex(&stored_api_key.0)
.into_report()
.change_context(errors::ApiKeyError::FailedToReadHashFromHex)?;
fp_utils::when(provided_api_key_hash != stored_api_key_hash, || {
Err(errors::ApiKeyError::HashVerificationFailed).into_report()
})
}
}
#[instrument(skip_all)]
@ -165,7 +155,7 @@ pub async fn retrieve_api_key(
key_id: &str,
) -> RouterResponse<api::RetrieveApiKeyResponse> {
let api_key = store
.find_api_key_optional(key_id)
.find_api_key_by_key_id_optional(key_id)
.await
.change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
.attach_printable("Failed to retrieve new API key")?
@ -224,12 +214,30 @@ pub async fn list_api_keys(
Ok(ApplicationResponse::Json(api_keys))
}
impl From<&str> for PlaintextApiKey {
fn from(s: &str) -> Self {
Self(s.to_owned().into())
}
}
impl From<String> for PlaintextApiKey {
fn from(s: String) -> Self {
Self(s.into())
}
}
impl From<HashedApiKey> for storage::HashedApiKey {
fn from(hashed_api_key: HashedApiKey) -> Self {
hashed_api_key.0.into()
}
}
impl From<storage::HashedApiKey> for HashedApiKey {
fn from(hashed_api_key: storage::HashedApiKey) -> Self {
Self(hashed_api_key.into_inner())
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used)]
@ -251,8 +259,7 @@ mod tests {
hashed_api_key.0.as_bytes()
);
plaintext_api_key
.verify_hash(hash_key.peek(), &hashed_api_key)
.unwrap();
let new_hashed_api_key = plaintext_api_key.keyed_hash(hash_key.peek());
assert_eq!(hashed_api_key, new_hashed_api_key)
}
}