From b60ced02ffba21624a9491a63fcde1c04cfa0b06 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:53:10 +0530 Subject: [PATCH] feat: use admin_api_key auth along with merchant_id for connector list, retrieve and update apis (#5613) --- crates/router/src/routes/admin.rs | 6 +- crates/router/src/services/authentication.rs | 88 +++++++++++- cypress-tests/cypress/support/commands.js | 12 +- .../stripe/PaymentConnectors/.meta.json | 1 - .../API Key - Create/.event.meta.json | 3 - .../API Key - Create/event.test.js | 46 ------ .../API Key - Create/request.json | 47 ------- .../API Key - Create/response.json | 1 - .../List Connectors by MID/request.json | 6 +- .../Payment Connector - Retrieve/request.json | 6 +- .../Payment Connector - Update/request.json | 6 +- .../Payment Connector - Update/request.json | 6 +- .../stripe.postman_collection.json | 133 +++--------------- 13 files changed, 135 insertions(+), 226 deletions(-) delete mode 100644 postman/collection-dir/stripe/PaymentConnectors/API Key - Create/.event.meta.json delete mode 100644 postman/collection-dir/stripe/PaymentConnectors/API Key - Create/event.test.js delete mode 100644 postman/collection-dir/stripe/PaymentConnectors/API Key - Create/request.json delete mode 100644 postman/collection-dir/stripe/PaymentConnectors/API Key - Create/response.json diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index 353be92163..46b679f050 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -434,7 +434,7 @@ pub async fn connector_retrieve( ) }, auth::auth_type( - &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::AdminApiAuthWithMerchantId::default(), &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantConnectorAccountRead, @@ -533,7 +533,7 @@ pub async fn payment_connector_list( merchant_id.to_owned(), |state, _auth, merchant_id, _| list_payment_connectors(state, merchant_id, None), auth::auth_type( - &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::AdminApiAuthWithMerchantId::default(), &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantConnectorAccountRead, @@ -593,7 +593,7 @@ pub async fn connector_update( ) }, auth::auth_type( - &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::AdminApiAuthWithMerchantId::default(), &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::MerchantConnectorAccountWrite, diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 30857f797f..4ac2c02c94 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -37,6 +37,7 @@ use crate::{ api_keys, errors::{self, utils::StorageErrorExt, RouterResult}, }, + headers, routes::app::SessionStateInfo, services::api, types::domain, @@ -76,6 +77,9 @@ pub enum AuthenticationType { key_id: String, }, AdminApiKey, + AdminApiAuthWithMerchantId { + merchant_id: id_type::MerchantId, + }, MerchantJwt { merchant_id: id_type::MerchantId, user_id: Option, @@ -122,6 +126,7 @@ impl AuthenticationType { merchant_id, key_id: _, } + | Self::AdminApiAuthWithMerchantId { merchant_id } | Self::MerchantId { merchant_id } | Self::PublishableKey { merchant_id } | Self::MerchantJwt { @@ -655,7 +660,7 @@ where } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct AdminApiAuth; #[async_trait] @@ -683,6 +688,81 @@ where } } +#[derive(Debug, Default)] +pub struct AdminApiAuthWithMerchantId(AdminApiAuth); + +#[async_trait] +impl AuthenticateAndFetch for AdminApiAuthWithMerchantId +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + self.0 + .authenticate_and_fetch(request_headers, state) + .await?; + let merchant_id = + get_header_value_by_key(headers::X_MERCHANT_ID.to_string(), request_headers)? + .get_required_value(headers::X_MERCHANT_ID) + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is missing", headers::X_MERCHANT_ID), + }) + .and_then(|merchant_id_str| { + id_type::MerchantId::try_from(std::borrow::Cow::from( + merchant_id_str.to_string(), + )) + .change_context( + errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid", headers::X_MERCHANT_ID), + }, + ) + })?; + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(errors::ApiErrorResponse::Unauthorized) + } else { + e.change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch merchant key store for the merchant id") + } + })?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(errors::ApiErrorResponse::Unauthorized) + } else { + e.change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch merchant account for the merchant id") + } + })?; + + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile_id: None, + }; + Ok(( + auth, + AuthenticationType::AdminApiAuthWithMerchantId { merchant_id }, + )) + } +} + #[derive(Debug)] pub struct EphemeralKeyAuth; @@ -1425,7 +1505,7 @@ pub fn is_ephemeral_auth( } pub fn is_jwt_auth(headers: &HeaderMap) -> bool { - headers.get(crate::headers::AUTHORIZATION).is_some() + headers.get(headers::AUTHORIZATION).is_some() || get_cookie_from_header(headers) .and_then(cookies::parse_cookie) .is_ok() @@ -1465,8 +1545,8 @@ pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&str> { headers - .get(crate::headers::AUTHORIZATION) - .get_required_value(crate::headers::AUTHORIZATION)? + .get(headers::AUTHORIZATION) + .get_required_value(headers::AUTHORIZATION)? .to_str() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert JWT token to string")? diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index bc23934efc..3affe123db 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -484,7 +484,8 @@ Cypress.Commands.add("connectorRetrieveCall", (globalState) => { headers: { Accept: "application/json", "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), + "api-key": globalState.get("adminApiKey"), + "x-merchant-id": merchant_id, }, failOnStatusCode: false, }).then((response) => { @@ -530,7 +531,8 @@ Cypress.Commands.add( headers: { Accept: "application/json", "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), + "api-key": globalState.get("adminApiKey"), + "x-merchant-id": merchant_id, }, body: updateConnectorBody, failOnStatusCode: false, @@ -554,7 +556,8 @@ Cypress.Commands.add("connectorListByMid", (globalState) => { url: `${globalState.get("baseUrl")}/account/${merchant_id}/connectors`, headers: { "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), + "api-key": globalState.get("adminApiKey"), + "X-Merchant-Id": merchant_id, }, failOnStatusCode: false, }).then((response) => { @@ -2062,7 +2065,8 @@ Cypress.Commands.add("ListMCAbyMID", (globalState) => { url: `${globalState.get("baseUrl")}/account/${merchantId}/connectors`, headers: { "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), + "api-key": globalState.get("adminApiKey"), + "X-Merchant-Id": merchantId, }, failOnStatusCode: false, }).then((response) => { diff --git a/postman/collection-dir/stripe/PaymentConnectors/.meta.json b/postman/collection-dir/stripe/PaymentConnectors/.meta.json index 198acc6d53..3f8bc360dd 100644 --- a/postman/collection-dir/stripe/PaymentConnectors/.meta.json +++ b/postman/collection-dir/stripe/PaymentConnectors/.meta.json @@ -1,6 +1,5 @@ { "childrenOrder": [ - "API Key - Create", "Payment Connector - Create", "Payment Connector - Retrieve", "Payment Connector - Update", diff --git a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/.event.meta.json b/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/.event.meta.json deleted file mode 100644 index 0731450e6b..0000000000 --- a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/event.test.js b/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/event.test.js deleted file mode 100644 index 4e27c5a502..0000000000 --- a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/event.test.js +++ /dev/null @@ -1,46 +0,0 @@ -// Validate status 2xx -pm.test("[POST]::/api_keys/:merchant_id - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test( - "[POST]::/api_keys/:merchant_id - Content-Type is application/json", - function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); - }, -); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set api_key_id as variable for jsonData.key_id -if (jsonData?.key_id) { - pm.collectionVariables.set("api_key_id", jsonData.key_id); - console.log( - "- use {{api_key_id}} as collection variable for value", - jsonData.key_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{api_key_id}}, as jsonData.key_id is undefined.", - ); -} - -// pm.collectionVariables - Set api_key as variable for jsonData.api_key -if (jsonData?.api_key) { - pm.collectionVariables.set("api_key", jsonData.api_key); - console.log( - "- use {{api_key}} as collection variable for value", - jsonData.api_key, - ); -} else { - console.log( - "INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.", - ); -} diff --git a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/request.json b/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/request.json deleted file mode 100644 index 6ceefe5d24..0000000000 --- a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/request.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{admin_api_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - } - ] - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw_json_formatted": { - "name": "API Key 1", - "description": null, - "expiration": "2069-09-23T01:02:03.000Z" - } - }, - "url": { - "raw": "{{baseUrl}}/api_keys/:merchant_id", - "host": ["{{baseUrl}}"], - "path": ["api_keys", ":merchant_id"], - "variable": [ - { - "key": "merchant_id", - "value": "{{merchant_id}}" - } - ] - } -} diff --git a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/response.json b/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/response.json deleted file mode 100644 index fe51488c70..0000000000 --- a/postman/collection-dir/stripe/PaymentConnectors/API Key - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/stripe/PaymentConnectors/List Connectors by MID/request.json b/postman/collection-dir/stripe/PaymentConnectors/List Connectors by MID/request.json index f731ff3a42..e3ed8ab292 100644 --- a/postman/collection-dir/stripe/PaymentConnectors/List Connectors by MID/request.json +++ b/postman/collection-dir/stripe/PaymentConnectors/List Connectors by MID/request.json @@ -4,7 +4,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -19,6 +19,10 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "url": { diff --git a/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Retrieve/request.json b/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Retrieve/request.json index 05f84b5771..97afd9f26e 100644 --- a/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Retrieve/request.json +++ b/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Retrieve/request.json @@ -4,7 +4,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -24,6 +24,10 @@ { "key": "Accept", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "url": { diff --git a/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Update/request.json b/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Update/request.json index bcc133ce43..f963110335 100644 --- a/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Update/request.json +++ b/postman/collection-dir/stripe/PaymentConnectors/Payment Connector - Update/request.json @@ -4,7 +4,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -28,6 +28,10 @@ { "key": "Accept", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "body": { diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json index 8b46ad180f..84c7a2fa48 100644 --- a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json @@ -4,7 +4,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -28,6 +28,10 @@ { "key": "Accept", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "body": { diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index c3e3c6f158..329b444a41 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -1348,115 +1348,6 @@ } ] }, - { - "name": "API Key - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/api_keys/:merchant_id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/api_keys/:merchant_id - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set api_key_id as variable for jsonData.key_id", - "if (jsonData?.key_id) {", - " pm.collectionVariables.set(\"api_key_id\", jsonData.key_id);", - " console.log(", - " \"- use {{api_key_id}} as collection variable for value\",", - " jsonData.key_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{api_key_id}}, as jsonData.key_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set api_key as variable for jsonData.api_key", - "if (jsonData?.api_key) {", - " pm.collectionVariables.set(\"api_key\", jsonData.api_key);", - " console.log(", - " \"- use {{api_key}} as collection variable for value\",", - " jsonData.api_key,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.\",", - " );", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{admin_api_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - } - ] - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\"name\":\"API Key 1\",\"description\":null,\"expiration\":\"2069-09-23T01:02:03.000Z\"}" - }, - "url": { - "raw": "{{baseUrl}}/api_keys/:merchant_id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "api_keys", - ":merchant_id" - ], - "variable": [ - { - "key": "merchant_id", - "value": "{{merchant_id}}" - } - ] - } - }, - "response": [] - }, { "name": "Payment Connector - Create", "event": [ @@ -1637,7 +1528,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -1657,6 +1548,10 @@ { "key": "Accept", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "url": { @@ -1752,7 +1647,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -1776,6 +1671,10 @@ { "key": "Accept", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "body": { @@ -1849,7 +1748,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -1864,6 +1763,10 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "url": { @@ -2684,7 +2587,7 @@ "apikey": [ { "key": "value", - "value": "{{api_key}}", + "value": "{{admin_api_key}}", "type": "string" }, { @@ -2708,6 +2611,10 @@ { "key": "Accept", "value": "application/json" + }, + { + "key": "x-merchant-id", + "value": "{{merchant_id}}" } ], "body": {