Issue:
Bug: Modify profile and merchant_connector_account routes to use `ApiKeyAuthWithMerchantIdFromRoute` auth Hotfix



Description:
Currently, the following routes use AdminApiAuthWithMerchantIdFromRoute for authentication:

Profile Create, Retrieve, Update
Merchant Connector Account Create, List, Retrieve, Update
This has to be updated to a more appropriate auth layer: ApiKeyAuthWithMerchantIdFromRoute, which validates that the merchant_id in the API key matches the one in the route.

Pull request description:
## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Hotfix PR
Original PR: [https://github.com/juspay/hyperswitch/pull/7656](https://github.com/juspay/hyperswitch/pull/7656)

### 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`
2. `crates/router/src/configs`
3. `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).
-->


## 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)?
-->


## 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


Testing:

Patch:
diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs
index 3cd959838a5..1c494542228 100644
--- a/crates/router/src/routes/admin.rs
+++ b/crates/router/src/routes/admin.rs
@@ -499,7 +499,7 @@ pub async fn connector_create(
             )
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id: merchant_id.clone(),
                 required_permission: Permission::ProfileConnectorWrite,
@@ -598,7 +598,7 @@ pub async fn connector_retrieve(
             )
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 // This should ideally be ProfileConnectorRead, but since this API responds with
@@ -716,7 +716,7 @@ pub async fn connector_list(
         merchant_id.to_owned(),
         |state, _auth, merchant_id, _| list_payment_connectors(state, merchant_id, None),
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 required_permission: Permission::MerchantConnectorRead,
@@ -769,7 +769,7 @@ pub async fn connector_list_profile(
             )
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 required_permission: Permission::ProfileConnectorRead,
@@ -830,7 +830,7 @@ pub async fn connector_update(
             )
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id: merchant_id.clone(),
                 required_permission: Permission::ProfileConnectorWrite,
diff --git a/crates/router/src/routes/profiles.rs b/crates/router/src/routes/profiles.rs
index 1b98160546d..cd91275df71 100644
--- a/crates/router/src/routes/profiles.rs
+++ b/crates/router/src/routes/profiles.rs
@@ -29,7 +29,7 @@ pub async fn profile_create(
             create_profile(state, req, auth_data.merchant_account, auth_data.key_store)
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 required_permission: permissions::Permission::MerchantAccountWrite,
@@ -95,7 +95,7 @@ pub async fn profile_retrieve(
         profile_id,
         |state, auth_data, profile_id, _| retrieve_profile(state, profile_id, auth_data.key_store),
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id: merchant_id.clone(),
                 required_permission: permissions::Permission::ProfileAccountRead,
@@ -158,7 +158,7 @@ pub async fn profile_update(
         json_payload.into_inner(),
         |state, auth_data, req, _| update_profile(state, &profile_id, auth_data.key_store, req),
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantAndProfileFromRoute {
                 merchant_id: merchant_id.clone(),
                 profile_id: profile_id.clone(),
@@ -243,7 +243,7 @@ pub async fn profiles_list(
         merchant_id.clone(),
         |state, _auth, merchant_id, _| list_profile(state, merchant_id, None),
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 required_permission: permissions::Permission::MerchantAccountRead,
@@ -309,7 +309,7 @@ pub async fn profiles_list_at_profile_level(
             )
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 required_permission: permissions::Permission::ProfileAccountRead,
@@ -402,7 +402,7 @@ pub async fn payment_connector_list_profile(
             )
         },
         auth::auth_type(
-            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
             &auth::JWTAuthMerchantFromRoute {
                 merchant_id,
                 required_permission: permissions::Permission::ProfileConnectorRead,
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index 4eb91756661..7e3d6802b88 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -633,6 +633,137 @@ where
     }
 }
 
+#[derive(Debug)]
+pub struct ApiKeyAuthWithMerchantIdFromRoute(pub id_type::MerchantId);
+
+#[cfg(feature = "partial-auth")]
+impl GetAuthType for ApiKeyAuthWithMerchantIdFromRoute {
+    fn get_auth_type(&self) -> detached::PayloadType {
+        detached::PayloadType::ApiKey
+    }
+}
+
+#[cfg(feature = "v1")]
+#[async_trait]
+impl<A> AuthenticateAndFetch<AuthenticationData, A> for ApiKeyAuthWithMerchantIdFromRoute
+where
+    A: SessionStateInfo + Sync,
+{
+    async fn authenticate_and_fetch(
+        &self,
+        request_headers: &HeaderMap,
+        state: &A,
+    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
+        let merchant_id_from_route = self.0.clone();
+        let api_key = get_api_key(request_headers)
+            .change_context(errors::ApiErrorResponse::Unauthorized)?
+            .trim();
+        if api_key.is_empty() {
+            return Err(errors::ApiErrorResponse::Unauthorized)
+                .attach_printable("API key is empty");
+        }
+
+        let api_key = api_keys::PlaintextApiKey::from(api_key);
+        let hash_key = {
+            let config = state.conf();
+            config.api_keys.get_inner().get_hash_key()?
+        };
+        let hashed_api_key = api_key.keyed_hash(hash_key.peek());
+
+        let stored_api_key = state
+            .store()
+            .find_api_key_by_hash_optional(hashed_api_key.into())
+            .await
+            .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
+            .attach_printable("Failed to retrieve API key")?
+            .ok_or(report!(errors::ApiErrorResponse::Unauthorized)) // If retrieve returned `None`
+            .attach_printable("Merchant not authenticated")?;
+
+        if stored_api_key
+            .expires_at
+            .map(|expires_at| expires_at < date_time::now())
+            .unwrap_or(false)
+        {
+            return Err(report!(errors::ApiErrorResponse::Unauthorized))
+                .attach_printable("API key has expired");
+        }
+
+        let key_manager_state = &(&state.session_state()).into();
+
+        let key_store = state
+            .store()
+            .get_merchant_key_store_by_merchant_id(
+                key_manager_state,
+                &stored_api_key.merchant_id,
+                &state.store().get_master_key().to_vec().into(),
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::Unauthorized)
+            .attach_printable("Failed to fetch merchant key store for the merchant id")?;
+
+        let profile_id =
+            get_header_value_by_key(headers::X_PROFILE_ID.to_string(), request_headers)?
+                .map(id_type::ProfileId::from_str)
+                .transpose()
+                .change_context(errors::ValidationError::IncorrectValueProvided {
+                    field_name: "X-Profile-Id",
+                })
+                .change_context(errors::ApiErrorResponse::Unauthorized)?;
+
+        if merchant_id_from_route != stored_api_key.merchant_id {
+            return Err(report!(errors::ApiErrorResponse::Unauthorized)).attach_printable(
+                "Merchant ID from route and Merchant ID from api-key in header do not match",
+            );
+        }
+
+        let merchant = state
+            .store()
+            .find_merchant_account_by_merchant_id(
+                key_manager_state,
+                &stored_api_key.merchant_id,
+                &key_store,
+            )
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
+
+        // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account
+        let (merchant, platform_merchant_account) = if state.conf().platform.enabled {
+            get_platform_merchant_account(state, request_headers, merchant).await?
+        } else {
+            (merchant, None)
+        };
+
+        let key_store = if platform_merchant_account.is_some() {
+            state
+                .store()
+                .get_merchant_key_store_by_merchant_id(
+                    key_manager_state,
+                    merchant.get_id(),
+                    &state.store().get_master_key().to_vec().into(),
+                )
+                .await
+                .change_context(errors::ApiErrorResponse::Unauthorized)
+                .attach_printable("Failed to fetch merchant key store for the merchant id")?
+        } else {
+            key_store
+        };
+
+        let auth = AuthenticationData {
+            merchant_account: merchant,
+            platform_merchant_account,
+            key_store,
+            profile_id,
+        };
+        Ok((
+            auth.clone(),
+            AuthenticationType::ApiKey {
+                merchant_id: auth.merchant_account.get_id().clone(),
+                key_id: stored_api_key.key_id,
+            },
+        ))
+    }
+}
+
 #[cfg(not(feature = "partial-auth"))]
 #[async_trait]
 impl<A, I> AuthenticateAndFetch<AuthenticationData, A> for HeaderAuth<I>
diff --git a/cypress-tests-v2/cypress/support/commands.js b/cypress-tests-v2/cypress/support/commands.js
index b6955c542a7..33f8907ce3f 100644
--- a/cypress-tests-v2/cypress/support/commands.js
+++ b/cypress-tests-v2/cypress/support/commands.js
@@ -310,7 +310,7 @@ Cypress.Commands.add(
   "businessProfileCreateCall",
   (businessProfileCreateBody, globalState) => {
     // Define the necessary variables and constants
-    const api_key = globalState.get("adminApiKey");
+    const api_key = globalState.get("apiKey");
     const base_url = globalState.get("baseUrl");
     const merchant_id = globalState.get("merchantId");
     const url = `${base_url}/v2/profiles`;
@@ -353,7 +353,7 @@ Cypress.Commands.add(
 );
 Cypress.Commands.add("businessProfileRetrieveCall", (globalState) => {
   // Define the necessary variables and constants
-  const api_key = globalState.get("adminApiKey");
+  const api_key = globalState.get("apiKey");
   const base_url = globalState.get("baseUrl");
   const merchant_id = globalState.get("merchantId");
   const profile_id = globalState.get("profileId");
@@ -395,7 +395,7 @@ Cypress.Commands.add(
   "businessProfileUpdateCall",
   (businessProfileUpdateBody, globalState) => {
     // Define the necessary variables and constants
-    const api_key = globalState.get("adminApiKey");
+    const api_key = globalState.get("apiKey");
     const base_url = globalState.get("baseUrl");
     const merchant_id = globalState.get("merchantId");
     const profile_id = globalState.get("profileId");