mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	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:
		| @ -427,6 +427,7 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode { | |||||||
|             | errors::ApiErrorResponse::InvalidJwtToken |             | errors::ApiErrorResponse::InvalidJwtToken | ||||||
|             | errors::ApiErrorResponse::GenericUnauthorized { .. } |             | errors::ApiErrorResponse::GenericUnauthorized { .. } | ||||||
|             | errors::ApiErrorResponse::AccessForbidden { .. } |             | errors::ApiErrorResponse::AccessForbidden { .. } | ||||||
|  |             | errors::ApiErrorResponse::InvalidCookie | ||||||
|             | errors::ApiErrorResponse::InvalidEphemeralKey => Self::Unauthorized, |             | errors::ApiErrorResponse::InvalidEphemeralKey => Self::Unauthorized, | ||||||
|             errors::ApiErrorResponse::InvalidRequestUrl |             errors::ApiErrorResponse::InvalidRequestUrl | ||||||
|             | errors::ApiErrorResponse::InvalidHttpMethod |             | errors::ApiErrorResponse::InvalidHttpMethod | ||||||
|  | |||||||
| @ -260,6 +260,11 @@ pub enum ApiErrorResponse { | |||||||
|     CurrencyConversionFailed, |     CurrencyConversionFailed, | ||||||
|     #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] |     #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] | ||||||
|     PaymentMethodDeleteFailed, |     PaymentMethodDeleteFailed, | ||||||
|  |     #[error( | ||||||
|  |         error_type = ErrorType::InvalidRequestError, code = "IR_26", | ||||||
|  |         message = "Invalid Cookie" | ||||||
|  |     )] | ||||||
|  |     InvalidCookie, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl PTError for ApiErrorResponse { | impl PTError for ApiErrorResponse { | ||||||
|  | |||||||
| @ -292,6 +292,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon | |||||||
|             Self::PaymentMethodDeleteFailed => { |             Self::PaymentMethodDeleteFailed => { | ||||||
|                 AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) |                 AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) | ||||||
|             } |             } | ||||||
|  |             Self::InvalidCookie => { | ||||||
|  |                 AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None)) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ use common_utils::date_time; | |||||||
| use error_stack::{report, ResultExt}; | use error_stack::{report, ResultExt}; | ||||||
| use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; | use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; | ||||||
| use masking::PeekInterface; | use masking::PeekInterface; | ||||||
|  | use router_env::logger; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  |  | ||||||
| use self::blacklist::BlackList; | use self::blacklist::BlackList; | ||||||
| @ -33,7 +34,6 @@ use crate::{ | |||||||
|     utils::OptionExt, |     utils::OptionExt, | ||||||
| }; | }; | ||||||
| pub mod blacklist; | pub mod blacklist; | ||||||
| #[cfg(feature = "olap")] |  | ||||||
| pub mod cookies; | pub mod cookies; | ||||||
|  |  | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| @ -598,6 +598,15 @@ where | |||||||
|     A: AppStateInfo + Sync, |     A: AppStateInfo + Sync, | ||||||
| { | { | ||||||
|     let token = get_jwt_from_authorization_header(headers)?; |     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?; |     let payload = decode_jwt(token, state).await?; | ||||||
|  |  | ||||||
|     Ok(payload) |     Ok(payload) | ||||||
| @ -959,6 +968,13 @@ pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&s | |||||||
|         .ok_or(errors::ApiErrorResponse::InvalidJwtToken.into()) |         .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> { | pub fn strip_jwt_token(token: &str) -> RouterResult<&str> { | ||||||
|     token |     token | ||||||
|         .strip_prefix("Bearer ") |         .strip_prefix("Bearer ") | ||||||
|  | |||||||
| @ -1,15 +1,27 @@ | |||||||
|  | use cookie::Cookie; | ||||||
|  | #[cfg(feature = "olap")] | ||||||
| use cookie::{ | use cookie::{ | ||||||
|     time::{Duration, OffsetDateTime}, |     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::{ | 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}, |     core::errors::{UserErrors, UserResponse}, | ||||||
|     services::ApplicationResponse, |     services::ApplicationResponse, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | #[cfg(feature = "olap")] | ||||||
| pub fn set_cookie_response<R>(response: R, token: Secret<String>) -> UserResponse<R> { | pub fn set_cookie_response<R>(response: R, token: Secret<String>) -> UserResponse<R> { | ||||||
|     let jwt_expiry_in_seconds = JWT_TOKEN_TIME_IN_SECS |     let jwt_expiry_in_seconds = JWT_TOKEN_TIME_IN_SECS | ||||||
|         .try_into() |         .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) |     let header_value = create_cookie(token, expiry, max_age) | ||||||
|         .to_string() |         .to_string() | ||||||
|         .into_masked(); |         .into_masked(); | ||||||
|     let header_key = get_cookie_header(); |     let header_key = get_set_cookie_header(); | ||||||
|     let header = vec![(header_key, header_value)]; |     let header = vec![(header_key, header_value)]; | ||||||
|  |  | ||||||
|     Ok(ApplicationResponse::JsonWithHeaders((response, header))) |     Ok(ApplicationResponse::JsonWithHeaders((response, header))) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "olap")] | ||||||
| pub fn remove_cookie_response() -> UserResponse<()> { | pub fn remove_cookie_response() -> UserResponse<()> { | ||||||
|     let (expiry, max_age) = get_expiry_and_max_age_from_seconds(0); |     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) |     let header_value = create_cookie("".to_string().into(), expiry, max_age) | ||||||
|         .to_string() |         .to_string() | ||||||
|         .into_masked(); |         .into_masked(); | ||||||
| @ -36,6 +49,19 @@ pub fn remove_cookie_response() -> UserResponse<()> { | |||||||
|     Ok(ApplicationResponse::JsonWithHeaders(((), header))) |     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>( | fn create_cookie<'c>( | ||||||
|     token: Secret<String>, |     token: Secret<String>, | ||||||
|     expires: OffsetDateTime, |     expires: OffsetDateTime, | ||||||
| @ -51,12 +77,18 @@ fn create_cookie<'c>( | |||||||
|         .build() |         .build() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "olap")] | ||||||
| fn get_expiry_and_max_age_from_seconds(seconds: i64) -> (OffsetDateTime, Duration) { | fn get_expiry_and_max_age_from_seconds(seconds: i64) -> (OffsetDateTime, Duration) { | ||||||
|     let max_age = Duration::seconds(seconds); |     let max_age = Duration::seconds(seconds); | ||||||
|     let expiry = OffsetDateTime::now_utc().saturating_add(max_age); |     let expiry = OffsetDateTime::now_utc().saturating_add(max_age); | ||||||
|     (expiry, 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() |     actix_http::header::SET_COOKIE.to_string() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn get_cookie_header() -> String { | ||||||
|  |     actix_http::header::COOKIE.to_string() | ||||||
|  | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Rachit Naithani
					Rachit Naithani