feat(users): Implemented cookie parsing for auth (#4298)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Rachit Naithani
2024-04-05 13:05:21 +05:30
committed by GitHub
parent 2fac436683
commit 2d394f98e9
5 changed files with 64 additions and 7 deletions

View File

@ -427,6 +427,7 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
| errors::ApiErrorResponse::InvalidJwtToken
| errors::ApiErrorResponse::GenericUnauthorized { .. }
| errors::ApiErrorResponse::AccessForbidden { .. }
| errors::ApiErrorResponse::InvalidCookie
| errors::ApiErrorResponse::InvalidEphemeralKey => Self::Unauthorized,
errors::ApiErrorResponse::InvalidRequestUrl
| errors::ApiErrorResponse::InvalidHttpMethod

View File

@ -260,6 +260,11 @@ pub enum ApiErrorResponse {
CurrencyConversionFailed,
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")]
PaymentMethodDeleteFailed,
#[error(
error_type = ErrorType::InvalidRequestError, code = "IR_26",
message = "Invalid Cookie"
)]
InvalidCookie,
}
impl PTError for ApiErrorResponse {

View File

@ -292,6 +292,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
Self::PaymentMethodDeleteFailed => {
AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None))
}
Self::InvalidCookie => {
AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None))
}
}
}
}

View File

@ -5,6 +5,7 @@ use common_utils::date_time;
use error_stack::{report, ResultExt};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use masking::PeekInterface;
use router_env::logger;
use serde::Serialize;
use self::blacklist::BlackList;
@ -33,7 +34,6 @@ use crate::{
utils::OptionExt,
};
pub mod blacklist;
#[cfg(feature = "olap")]
pub mod cookies;
#[derive(Clone, Debug)]
@ -598,6 +598,15 @@ where
A: AppStateInfo + Sync,
{
let token = get_jwt_from_authorization_header(headers)?;
if let Some(token_from_cookies) = get_cookie_from_header(headers)
.ok()
.and_then(|cookies| cookies::parse_cookie(cookies).ok())
{
logger::info!(
"Cookie header and authorization header JWT comparison result: {}",
token == token_from_cookies
);
}
let payload = decode_jwt(token, state).await?;
Ok(payload)
@ -959,6 +968,13 @@ pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&s
.ok_or(errors::ApiErrorResponse::InvalidJwtToken.into())
}
pub fn get_cookie_from_header(headers: &HeaderMap) -> RouterResult<&str> {
headers
.get(cookies::get_cookie_header())
.and_then(|header_value| header_value.to_str().ok())
.ok_or(errors::ApiErrorResponse::InvalidCookie.into())
}
pub fn strip_jwt_token(token: &str) -> RouterResult<&str> {
token
.strip_prefix("Bearer ")

View File

@ -1,15 +1,27 @@
use cookie::Cookie;
#[cfg(feature = "olap")]
use cookie::{
time::{Duration, OffsetDateTime},
Cookie, SameSite,
SameSite,
};
use masking::{ExposeInterface, Mask, Secret};
use error_stack::{report, ResultExt};
#[cfg(feature = "olap")]
use masking::Mask;
#[cfg(feature = "olap")]
use masking::{ExposeInterface, Secret};
use crate::{
consts::{JWT_TOKEN_COOKIE_NAME, JWT_TOKEN_TIME_IN_SECS},
consts::JWT_TOKEN_COOKIE_NAME,
core::errors::{ApiErrorResponse, RouterResult},
};
#[cfg(feature = "olap")]
use crate::{
consts::JWT_TOKEN_TIME_IN_SECS,
core::errors::{UserErrors, UserResponse},
services::ApplicationResponse,
};
#[cfg(feature = "olap")]
pub fn set_cookie_response<R>(response: R, token: Secret<String>) -> UserResponse<R> {
let jwt_expiry_in_seconds = JWT_TOKEN_TIME_IN_SECS
.try_into()
@ -19,16 +31,17 @@ pub fn set_cookie_response<R>(response: R, token: Secret<String>) -> UserRespons
let header_value = create_cookie(token, expiry, max_age)
.to_string()
.into_masked();
let header_key = get_cookie_header();
let header_key = get_set_cookie_header();
let header = vec![(header_key, header_value)];
Ok(ApplicationResponse::JsonWithHeaders((response, header)))
}
#[cfg(feature = "olap")]
pub fn remove_cookie_response() -> UserResponse<()> {
let (expiry, max_age) = get_expiry_and_max_age_from_seconds(0);
let header_key = get_cookie_header();
let header_key = get_set_cookie_header();
let header_value = create_cookie("".to_string().into(), expiry, max_age)
.to_string()
.into_masked();
@ -36,6 +49,19 @@ pub fn remove_cookie_response() -> UserResponse<()> {
Ok(ApplicationResponse::JsonWithHeaders(((), header)))
}
pub fn parse_cookie(cookies: &str) -> RouterResult<String> {
Cookie::split_parse(cookies)
.find_map(|cookie| {
cookie
.ok()
.filter(|parsed_cookie| parsed_cookie.name() == JWT_TOKEN_COOKIE_NAME)
.map(|parsed_cookie| parsed_cookie.value().to_owned())
})
.ok_or(report!(ApiErrorResponse::InvalidCookie))
.attach_printable("Cookie Parsing Failed")
}
#[cfg(feature = "olap")]
fn create_cookie<'c>(
token: Secret<String>,
expires: OffsetDateTime,
@ -51,12 +77,18 @@ fn create_cookie<'c>(
.build()
}
#[cfg(feature = "olap")]
fn get_expiry_and_max_age_from_seconds(seconds: i64) -> (OffsetDateTime, Duration) {
let max_age = Duration::seconds(seconds);
let expiry = OffsetDateTime::now_utc().saturating_add(max_age);
(expiry, max_age)
}
fn get_cookie_header() -> String {
#[cfg(feature = "olap")]
fn get_set_cookie_header() -> String {
actix_http::header::SET_COOKIE.to_string()
}
pub fn get_cookie_header() -> String {
actix_http::header::COOKIE.to_string()
}