Bug: refactor(users): Use domain email type in DB functions



Currently lowercase email check is there in `UserEmail` type. But DB is using `pii::Email` type, which might cause unexpected behaviour in some cases.

diff --git a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs
index b02da396075..850cc470316 100644
--- a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs
+++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs
@@ -274,6 +274,11 @@ pub enum ApiErrorResponse {
     LinkConfigurationError { message: String },
     #[error(error_type = ErrorType::InvalidRequestError, code = "IR_41", message = "Payout validation failed")]
     PayoutFailed { data: Option<serde_json::Value> },
+    #[error(
+        error_type = ErrorType::InvalidRequestError, code = "IR_42",
+        message = "Cookies are not found in the request"
+    )]
+    CookieNotFound,
 
     #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")]
     WebhookAuthenticationFailed,
@@ -627,6 +632,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
             Self::PayoutFailed { data } => {
                 AER::BadRequest(ApiError::new("IR", 41, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()})))
             },
+            Self::CookieNotFound => {
+                AER::Unauthorized(ApiError::new("IR", 42, "Cookies are not found in the request", None))
+            },
 
             Self::WebhookAuthenticationFailed => {
                 AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None))
diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs
index efe0ac157e0..6cf078b5f81 100644
--- a/crates/router/src/compatibility/stripe/errors.rs
+++ b/crates/router/src/compatibility/stripe/errors.rs
@@ -444,7 +444,8 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
             | errors::ApiErrorResponse::GenericUnauthorized { .. }
             | errors::ApiErrorResponse::AccessForbidden { .. }
             | errors::ApiErrorResponse::InvalidCookie
-            | errors::ApiErrorResponse::InvalidEphemeralKey => Self::Unauthorized,
+            | errors::ApiErrorResponse::InvalidEphemeralKey
+            | errors::ApiErrorResponse::CookieNotFound => Self::Unauthorized,
             errors::ApiErrorResponse::InvalidRequestUrl
             | errors::ApiErrorResponse::InvalidHttpMethod
             | errors::ApiErrorResponse::InvalidCardIin
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 2087d01dbb4..20fea3ac362 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -166,7 +166,7 @@ pub async fn signin_token_only_flow(
 ) -> UserResponse<user_api::TokenResponse> {
     let user_from_db: domain::UserFromStorage = state
         .global_store
-        .find_user_by_email(&request.email)
+        .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?)
         .await
         .to_not_found_response(UserErrors::InvalidCredentials)?
         .into();
@@ -191,7 +191,10 @@ pub async fn connect_account(
     request: user_api::ConnectAccountRequest,
     auth_id: Option<String>,
 ) -> UserResponse<user_api::ConnectAccountResponse> {
-    let find_user = state.global_store.find_user_by_email(&request.email).await;
+    let find_user = state
+        .global_store
+        .find_user_by_email(&domain::UserEmail::from_pii_email(request.email.clone())?)
+        .await;
 
     if let Ok(found_user) = find_user {
         let user_from_db: domain::UserFromStorage = found_user.into();
@@ -369,7 +372,7 @@ pub async fn forgot_password(
 
     let user_from_db = state
         .global_store
-        .find_user_by_email(&user_email.into_inner())
+        .find_user_by_email(&user_email)
         .await
         .map_err(|e| {
             if e.current_context().is_db_not_found() {
@@ -453,11 +456,7 @@ pub async fn reset_password_token_only_flow(
 
     let user_from_db: domain::UserFromStorage = state
         .global_store
-        .find_user_by_email(
-            &email_token
-                .get_email()
-                .change_context(UserErrors::InternalServerError)?,
-        )
+        .find_user_by_email(&email_token.get_email()?)
         .await
         .change_context(UserErrors::InternalServerError)?
         .into();
@@ -564,10 +563,7 @@ async fn handle_invitation(
     }
 
     let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
-    let invitee_user = state
-        .global_store
-        .find_user_by_email(&invitee_email.into_inner())
-        .await;
+    let invitee_user = state.global_store.find_user_by_email(&invitee_email).await;
 
     if let Ok(invitee_user) = invitee_user {
         handle_existing_user_invitation(
@@ -946,7 +942,7 @@ pub async fn resend_invite(
     let invitee_email = domain::UserEmail::from_pii_email(request.email)?;
     let user: domain::UserFromStorage = state
         .global_store
-        .find_user_by_email(&invitee_email.clone().into_inner())
+        .find_user_by_email(&invitee_email)
         .await
         .map_err(|e| {
             if e.current_context().is_db_not_found() {
@@ -1045,11 +1041,7 @@ pub async fn accept_invite_from_email_token_only_flow(
 
     let user_from_db: domain::UserFromStorage = state
         .global_store
-        .find_user_by_email(
-            &email_token
-                .get_email()
-                .change_context(UserErrors::InternalServerError)?,
-        )
+        .find_user_by_email(&email_token.get_email()?)
         .await
         .change_context(UserErrors::InternalServerError)?
         .into();
@@ -1493,11 +1485,7 @@ pub async fn verify_email_token_only_flow(
 
     let user_from_email = state
         .global_store
-        .find_user_by_email(
-            &email_token
-                .get_email()
-                .change_context(UserErrors::InternalServerError)?,
-        )
+        .find_user_by_email(&email_token.get_email()?)
         .await
         .change_context(UserErrors::InternalServerError)?;
 
@@ -1540,7 +1528,7 @@ pub async fn send_verification_mail(
     let user_email = domain::UserEmail::try_from(req.email)?;
     let user = state
         .global_store
-        .find_user_by_email(&user_email.into_inner())
+        .find_user_by_email(&user_email)
         .await
         .map_err(|e| {
             if e.current_context().is_db_not_found() {
@@ -1637,11 +1625,7 @@ pub async fn user_from_email(
 
     let user_from_db: domain::UserFromStorage = state
         .global_store
-        .find_user_by_email(
-            &email_token
-                .get_email()
-                .change_context(UserErrors::InternalServerError)?,
-        )
+        .find_user_by_email(&email_token.get_email()?)
         .await
         .change_context(UserErrors::InternalServerError)?
         .into();
@@ -2347,7 +2331,7 @@ pub async fn sso_sign(
     // TODO: Use config to handle not found error
     let user_from_db = state
         .global_store
-        .find_user_by_email(&email.into_inner())
+        .find_user_by_email(&email)
         .await
         .map(Into::into)
         .to_not_found_response(UserErrors::UserNotFound)?;
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index 6641e553fd8..df4771dbe70 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -440,7 +440,7 @@ pub async fn delete_user_role(
 ) -> UserResponse<()> {
     let user_from_db: domain::UserFromStorage = state
         .global_store
-        .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?.into_inner())
+        .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?)
         .await
         .map_err(|e| {
             if e.current_context().is_db_not_found() {
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index a9df1abbc08..5d6fac3b130 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -3,7 +3,7 @@ use std::sync::Arc;
 use common_enums::enums::MerchantStorageScheme;
 use common_utils::{
     errors::CustomResult,
-    id_type, pii,
+    id_type,
     types::{keymanager::KeyManagerState, theme::ThemeLineage},
 };
 use diesel_models::{
@@ -2983,7 +2983,7 @@ impl UserInterface for KafkaStore {
 
     async fn find_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
     ) -> CustomResult<storage::User, errors::StorageError> {
         self.diesel_store.find_user_by_email(user_email).await
     }
@@ -3007,7 +3007,7 @@ impl UserInterface for KafkaStore {
 
     async fn update_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
         user: storage::UserUpdate,
     ) -> CustomResult<storage::User, errors::StorageError> {
         self.diesel_store
diff --git a/crates/router/src/db/user.rs b/crates/router/src/db/user.rs
index 14bed15fa45..089c9fc6eb7 100644
--- a/crates/router/src/db/user.rs
+++ b/crates/router/src/db/user.rs
@@ -3,11 +3,10 @@ use error_stack::report;
 use masking::Secret;
 use router_env::{instrument, tracing};
 
-use super::MockDb;
+use super::{domain, MockDb};
 use crate::{
     connection,
     core::errors::{self, CustomResult},
-    pii,
     services::Store,
 };
 pub mod sample_data;
@@ -22,7 +21,7 @@ pub trait UserInterface {
 
     async fn find_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
     ) -> CustomResult<storage::User, errors::StorageError>;
 
     async fn find_user_by_id(
@@ -38,7 +37,7 @@ pub trait UserInterface {
 
     async fn update_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
         user: storage::UserUpdate,
     ) -> CustomResult<storage::User, errors::StorageError>;
 
@@ -70,10 +69,10 @@ impl UserInterface for Store {
     #[instrument(skip_all)]
     async fn find_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
     ) -> CustomResult<storage::User, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
-        storage::User::find_by_user_email(&conn, user_email)
+        storage::User::find_by_user_email(&conn, user_email.get_inner())
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))
     }
@@ -104,11 +103,11 @@ impl UserInterface for Store {
     #[instrument(skip_all)]
     async fn update_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
         user: storage::UserUpdate,
     ) -> CustomResult<storage::User, errors::StorageError> {
         let conn = connection::pg_connection_write(self).await?;
-        storage::User::update_by_user_email(&conn, user_email, user)
+        storage::User::update_by_user_email(&conn, user_email.get_inner(), user)
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))
     }
@@ -171,12 +170,12 @@ impl UserInterface for MockDb {
 
     async fn find_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
     ) -> CustomResult<storage::User, errors::StorageError> {
         let users = self.users.lock().await;
         users
             .iter()
-            .find(|user| user.email.eq(user_email))
+            .find(|user| user.email.eq(user_email.get_inner()))
             .cloned()
             .ok_or(
                 errors::StorageError::ValueNotFound(format!(
@@ -253,13 +252,13 @@ impl UserInterface for MockDb {
 
     async fn update_user_by_email(
         &self,
-        user_email: &pii::Email,
+        user_email: &domain::UserEmail,
         update_user: storage::UserUpdate,
     ) -> CustomResult<storage::User, errors::StorageError> {
         let mut users = self.users.lock().await;
         users
             .iter_mut()
-            .find(|user| user.email.eq(user_email))
+            .find(|user| user.email.eq(user_email.get_inner()))
             .map(|user| {
                 *user = match &update_user {
                     storage::UserUpdate::VerifyUser => storage::User {
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index c05e4514aaa..2d5f81e980c 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -2545,7 +2545,8 @@ where
     T: serde::de::DeserializeOwned,
     A: SessionStateInfo + Sync,
 {
-    let cookie_token_result = get_cookie_from_header(headers).and_then(cookies::parse_cookie);
+    let cookie_token_result =
+        get_cookie_from_header(headers).and_then(cookies::get_jwt_from_cookies);
     let auth_header_token_result = get_jwt_from_authorization_header(headers);
     let force_cookie = state.conf().user.force_cookies;
 
@@ -3112,7 +3113,7 @@ pub fn is_ephemeral_auth<A: SessionStateInfo + Sync + Send>(
 pub fn is_jwt_auth(headers: &HeaderMap) -> bool {
     headers.get(headers::AUTHORIZATION).is_some()
         || get_cookie_from_header(headers)
-            .and_then(cookies::parse_cookie)
+            .and_then(cookies::get_jwt_from_cookies)
             .is_ok()
 }
 
@@ -3174,10 +3175,13 @@ pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&s
 }
 
 pub fn get_cookie_from_header(headers: &HeaderMap) -> RouterResult<&str> {
-    headers
+    let cookie = headers
         .get(cookies::get_cookie_header())
-        .and_then(|header_value| header_value.to_str().ok())
-        .ok_or(errors::ApiErrorResponse::InvalidCookie.into())
+        .ok_or(report!(errors::ApiErrorResponse::CookieNotFound))?;
+
+    cookie
+        .to_str()
+        .change_context(errors::ApiErrorResponse::InvalidCookie)
 }
 
 pub fn strip_jwt_token(token: &str) -> RouterResult<&str> {
diff --git a/crates/router/src/services/authentication/cookies.rs b/crates/router/src/services/authentication/cookies.rs
index 96518840800..4a297c0f0eb 100644
--- a/crates/router/src/services/authentication/cookies.rs
+++ b/crates/router/src/services/authentication/cookies.rs
@@ -49,7 +49,7 @@ pub fn remove_cookie_response() -> UserResponse<()> {
     Ok(ApplicationResponse::JsonWithHeaders(((), header)))
 }
 
-pub fn parse_cookie(cookies: &str) -> RouterResult<String> {
+pub fn get_jwt_from_cookies(cookies: &str) -> RouterResult<String> {
     Cookie::split_parse(cookies)
         .find_map(|cookie| {
             cookie
@@ -57,8 +57,8 @@ pub fn parse_cookie(cookies: &str) -> RouterResult<String> {
                 .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")
+        .ok_or(report!(ApiErrorResponse::InvalidJwtToken))
+        .attach_printable("Unable to find JWT token in cookies")
 }
 
 #[cfg(feature = "olap")]
diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs
index 87ff9f6abd5..8f48ef068ce 100644
--- a/crates/router/src/services/authorization.rs
+++ b/crates/router/src/services/authorization.rs
@@ -40,10 +40,12 @@ where
         i64::try_from(token.exp).change_context(ApiErrorResponse::InternalServerError)?;
     let cache_ttl = token_expiry - common_utils::date_time::now_unix_timestamp();
 
-    set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl)
-        .await
-        .map_err(|e| logger::error!("Failed to set role info in cache {e:?}"))
-        .ok();
+    if cache_ttl > 0 {
+        set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl)
+            .await
+            .map_err(|e| logger::error!("Failed to set role info in cache {e:?}"))
+            .ok();
+    }
     Ok(role_info)
 }
 
diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs
index d092afdc5de..8c966d79d80 100644
--- a/crates/router/src/services/email/types.rs
+++ b/crates/router/src/services/email/types.rs
@@ -1,9 +1,6 @@
 use api_models::user::dashboard_metadata::ProdIntent;
 use common_enums::EntityType;
-use common_utils::{
-    errors::{self, CustomResult},
-    pii,
-};
+use common_utils::{errors::CustomResult, pii};
 use error_stack::ResultExt;
 use external_services::email::{EmailContents, EmailData, EmailError};
 use masking::{ExposeInterface, Secret};
@@ -183,7 +180,7 @@ impl EmailToken {
         entity: Option<Entity>,
         flow: domain::Origin,
         settings: &configs::Settings,
-    ) -> CustomResult<String, UserErrors> {
+    ) -> UserResult<String> {
         let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS);
         let exp = jwt::generate_exp(expiration_duration)?.as_secs();
         let token_payload = Self {
@@ -195,8 +192,10 @@ impl EmailToken {
         jwt::generate_jwt(&token_payload, settings).await
     }
 
-    pub fn get_email(&self) -> CustomResult<pii::Email, errors::ParsingError> {
+    pub fn get_email(&self) -> UserResult<domain::UserEmail> {
         pii::Email::try_from(self.email.clone())
+            .change_context(UserErrors::InternalServerError)
+            .and_then(domain::UserEmail::from_pii_email)
     }
 
     pub fn get_entity(&self) -> Option<&Entity> {
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index 6d0d2a4ea07..21212bf6b62 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -131,6 +131,10 @@ impl UserEmail {
         self.0
     }
 
+    pub fn get_inner(&self) -> &pii::Email {
+        &self.0
+    }
+
     pub fn get_secret(self) -> Secret<String, pii::EmailStrategy> {
         (*self.0).clone()
     }
@@ -617,7 +621,7 @@ impl NewUser {
     pub async fn check_if_already_exists_in_db(&self, state: SessionState) -> UserResult<()> {
         if state
             .global_store
-            .find_user_by_email(&self.get_email().into_inner())
+            .find_user_by_email(&self.get_email())
             .await
             .is_ok()
         {
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index f115a16c062..443db741ae3 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -124,7 +124,7 @@ pub async fn get_user_from_db_by_email(
 ) -> CustomResult<UserFromStorage, StorageError> {
     state
         .global_store
-        .find_user_by_email(&email.into_inner())
+        .find_user_by_email(&email)
         .await
         .map(UserFromStorage::from)
 }

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [x] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This PR does 3 things.
1. Throw 401 if cookies are not found in request or if JWT is not found in cookies.
2. Cache role info only if the ttl is greater than or equal to 0.
3. Use `UserEmail` type in DB functions.

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
4. `crates/router/src/configs`
5. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Closes #6696.
Closes #6697.
Closes #6698.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
todo!()

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
