From b3c846d637dd32a2d6d7044c118abbb2616642f0 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:47:25 +0530 Subject: [PATCH 1/3] feat(connector): [Multisafepay] add error handling (#2595) Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Co-authored-by: Arjun Karthik --- crates/router/src/connector/multisafepay.rs | 16 +- .../connector/multisafepay/transformers.rs | 189 +++++++++++------- .../QuickStart/Payments - Create/request.json | 4 + .../.event.meta.json | 3 + .../Payment Connector - Update/event.test.js | 39 ++++ .../Payment Connector - Update/request.json | 122 +++++++++++ .../Payment Connector - Update/response.json | 1 + .../Payments - Create/.event.meta.json | 3 + .../Payments - Create/event.test.js | 56 ++++++ .../Payments - Create/request.json | 90 +++++++++ .../Payments - Create/response.json | 1 + 11 files changed, 448 insertions(+), 76 deletions(-) create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index 1ea5029fd003..e09f242abc8a 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -200,10 +200,11 @@ impl ConnectorIntegration CustomResult { - let response: multisafepay::MultisafepayPaymentsResponse = res + let response: multisafepay::MultisafepayAuthResponse = res .response .parse_struct("multisafepay PaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), @@ -301,10 +302,11 @@ impl ConnectorIntegration CustomResult { - let response: multisafepay::MultisafepayPaymentsResponse = res + let response: multisafepay::MultisafepayAuthResponse = res .response .parse_struct("MultisafepayPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::ResponseRouterData { response, data: data.clone(), @@ -399,17 +401,17 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: multisafepay::RefundResponse = res + let response: multisafepay::MultisafepayRefundResponse = res .response .parse_struct("multisafepay RefundResponse") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, } .try_into() - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseDeserializationFailed) } fn get_error_response( @@ -472,7 +474,7 @@ impl ConnectorIntegration CustomResult { - let response: multisafepay::RefundResponse = res + let response: multisafepay::MultisafepayRefundResponse = res .response .parse_struct("multisafepay RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -482,7 +484,7 @@ impl ConnectorIntegration - TryFrom< - types::ResponseRouterData, - > for types::RouterData + TryFrom> + for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, - MultisafepayPaymentsResponse, + MultisafepayAuthResponse, T, types::PaymentsResponseData, >, ) -> Result { - let redirection_data = item - .response - .data - .payment_url - .clone() - .map(|url| services::RedirectForm::from((url, services::Method::Get))); - - let default_status = if item.response.success { - MultisafepayPaymentStatus::Initialized - } else { - MultisafepayPaymentStatus::Declined - }; - - let status = item.response.data.status.unwrap_or(default_status); - - Ok(Self { - status: enums::AttemptStatus::from(status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.data.order_id.clone(), - ), - redirection_data, - mandate_reference: item - .response + match item.response { + MultisafepayAuthResponse::PaymentResponse(payment_response) => { + let redirection_data = payment_response .data - .payment_details - .and_then(|payment_details| payment_details.recurring_id) - .map(|id| types::MandateReference { - connector_mandate_id: Some(id), - payment_method_id: None, + .payment_url + .clone() + .map(|url| services::RedirectForm::from((url, services::Method::Get))); + + let default_status = if payment_response.success { + MultisafepayPaymentStatus::Initialized + } else { + MultisafepayPaymentStatus::Declined + }; + + let status = payment_response.data.status.unwrap_or(default_status); + + Ok(Self { + status: enums::AttemptStatus::from(status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + payment_response.data.order_id.clone(), + ), + redirection_data, + mandate_reference: payment_response + .data + .payment_details + .and_then(|payment_details| payment_details.recurring_id) + .map(|id| types::MandateReference { + connector_mandate_id: Some(id), + payment_method_id: None, + }), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + payment_response.data.order_id.clone(), + ), }), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some(item.response.data.order_id.clone()), + ..item.data + }) + } + MultisafepayAuthResponse::ErrorResponse(error_response) => Ok(Self { + response: Err(types::ErrorResponse { + code: error_response.error_code.to_string(), + message: error_response.error_info.clone(), + reason: Some(error_response.error_info), + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } @@ -747,61 +766,93 @@ pub struct RefundData { pub error_code: Option, pub error_info: Option, } -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RefundResponse { pub success: bool, pub data: RefundData, } -impl TryFrom> +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MultisafepayRefundResponse { + ErrorResponse(MultisafepayErrorResponse), + RefundResponse(RefundResponse), +} + +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - let refund_stat = if item.response.success { - RefundStatus::Succeeded - } else { - RefundStatus::Failed - }; - - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.data.refund_id.to_string(), - refund_status: enums::RefundStatus::from(refund_stat), + match item.response { + MultisafepayRefundResponse::RefundResponse(refund_data) => { + let refund_status = if refund_data.success { + RefundStatus::Succeeded + } else { + RefundStatus::Failed + }; + + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: refund_data.data.refund_id.to_string(), + refund_status: enums::RefundStatus::from(refund_status), + }), + ..item.data + }) + } + MultisafepayRefundResponse::ErrorResponse(error_response) => Ok(Self { + response: Err(types::ErrorResponse { + code: error_response.error_code.to_string(), + message: error_response.error_info.clone(), + reason: Some(error_response.error_info), + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } -impl TryFrom> +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - let refund_status = if item.response.success { - RefundStatus::Succeeded - } else { - RefundStatus::Failed - }; - - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.data.refund_id.to_string(), - refund_status: enums::RefundStatus::from(refund_status), + match item.response { + MultisafepayRefundResponse::RefundResponse(refund_data) => { + let refund_status = if refund_data.success { + RefundStatus::Succeeded + } else { + RefundStatus::Failed + }; + + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: refund_data.data.refund_id.to_string(), + refund_status: enums::RefundStatus::from(refund_status), + }), + ..item.data + }) + } + MultisafepayRefundResponse::ErrorResponse(error_response) => Ok(Self { + response: Err(types::ErrorResponse { + code: error_response.error_code.to_string(), + message: error_response.error_info.clone(), + reason: Some(error_response.error_info), + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct MultisafepayErrorResponse { - pub success: bool, pub error_code: i32, pub error_info: String, } diff --git a/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json index 289e780a72cb..a4e816ad17d2 100644 --- a/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json +++ b/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json @@ -55,6 +55,10 @@ "last_name": "happy" } }, + "routing": { + "type": "single", + "data": "multisafepay" + }, "shipping": { "address": { "line1": "1467", diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js new file mode 100644 index 000000000000..d7259b6a840b --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js @@ -0,0 +1,39 @@ +// Validate status 2xx +pm.test( + "[POST]::/account/:account_id/connectors/:connector_id - Status code is 2xx", + function () { + pm.response.to.be.success; + }, +); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/account/:account_id/connectors/:connector_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 merchant_connector_id as variable for jsonData.merchant_connector_id +if (jsonData?.merchant_connector_id) { + pm.collectionVariables.set( + "merchant_connector_id", + jsonData.merchant_connector_id, + ); + console.log( + "- use {{merchant_connector_id}} as collection variable for value", + jsonData.merchant_connector_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.", + ); +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json new file mode 100644 index 000000000000..5328f86fbc92 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json @@ -0,0 +1,122 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "connector_type": "fiz_operations", + "connector_account_details": { + "auth_type": "HeaderKey", + "api_key": "wrongAPIKey" + }, + "test_mode": false, + "disabled": false, + "payment_methods_enabled": [ + { + "payment_method": "card", + "payment_method_types": [ + { + "payment_method_type": "credit", + "card_networks": ["Visa", "Mastercard"], + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "debit", + "card_networks": ["Visa", "Mastercard"], + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "pay_later", + "payment_method_types": [ + { + "payment_method_type": "klarna", + "payment_experience": "redirect_to_url", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "affirm", + "payment_experience": "redirect_to_url", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "afterpay_clearpay", + "payment_experience": "redirect_to_url", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + } + ], + "metadata": { + "city": "NY", + "unit": "245" + } + } + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors/:connector_id", + "host": ["{{baseUrl}}"], + "path": ["account", ":account_id", "connectors", ":connector_id"], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}" + }, + { + "key": "connector_id", + "value": "{{merchant_connector_id}}" + } + ] + }, + "description": "To update an existing Payment Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc" +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js new file mode 100644 index 000000000000..e75cb69a9249 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js @@ -0,0 +1,56 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + + + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// Validate error message in the JSON Body +pm.test("[POST]::/payments - Validate error message", function () { + pm.expect(jsonData.error_message).to.not.be.null +}); + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json new file mode 100644 index 000000000000..525eaa739e83 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json @@ -0,0 +1,90 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://duck.com", + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "routing": { + "type": "single", + "data": "multisafepay" + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "someone", + "last_name": "happy" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "someone", + "last_name": "happy" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json @@ -0,0 +1 @@ +[] From 6ab79e6fe8b8444d41d48e2c03bc180c72499247 Mon Sep 17 00:00:00 2001 From: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:19:08 +0530 Subject: [PATCH 2/3] CI: add suport for spell checking PR title (#2755) --- ...tional-commit-check.yml => pr-title-check.yml} | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) rename .github/workflows/{conventional-commit-check.yml => pr-title-check.yml} (90%) diff --git a/.github/workflows/conventional-commit-check.yml b/.github/workflows/pr-title-check.yml similarity index 90% rename from .github/workflows/conventional-commit-check.yml rename to .github/workflows/pr-title-check.yml index c02d8a3bfb49..8c15246f0e6c 100644 --- a/.github/workflows/conventional-commit-check.yml +++ b/.github/workflows/pr-title-check.yml @@ -1,4 +1,4 @@ -name: Conventional Commit Message Check +name: PR Title Checks on: # This is a dangerous event trigger as it causes the workflow to run in the @@ -35,6 +35,19 @@ env: CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse jobs: + typos: + name: Spell check PR title + runs-on: ubuntu-latest + steps: + - name: Store PR title in a file + shell: bash + run: echo '${{ github.event.pull_request.title }}' > pr_title.txt + + - name: Spell check + uses: crate-ci/typos@master + with: + files: ./pr_title.txt + pr_title_check: name: Verify PR title follows conventional commit standards runs-on: ubuntu-latest From 10944937a02502e0727f16368d8d055e575dd518 Mon Sep 17 00:00:00 2001 From: Sampras Lopes Date: Thu, 2 Nov 2023 17:53:35 +0530 Subject: [PATCH 3/3] feat(events): add api auth type details to events (#2760) --- crates/router/src/compatibility/wrap.rs | 1 - crates/router/src/events/api_logs.rs | 9 +- crates/router/src/services/api.rs | 9 +- crates/router/src/services/authentication.rs | 104 +++++++++++++++---- 4 files changed, 97 insertions(+), 26 deletions(-) diff --git a/crates/router/src/compatibility/wrap.rs b/crates/router/src/compatibility/wrap.rs index 8a1bf3f6aedb..75cb07de02ba 100644 --- a/crates/router/src/compatibility/wrap.rs +++ b/crates/router/src/compatibility/wrap.rs @@ -28,7 +28,6 @@ where Q: Serialize + std::fmt::Debug + 'a, S: TryFrom + Serialize, E: Serialize + error_stack::Context + actix_web::ResponseError + Clone, - U: auth::AuthInfo, error_stack::Report: services::EmbedError, errors::ApiErrorResponse: ErrorSwitch, T: std::fmt::Debug + Serialize, diff --git a/crates/router/src/events/api_logs.rs b/crates/router/src/events/api_logs.rs index 5e031fd945ff..35eaf1edae7f 100644 --- a/crates/router/src/events/api_logs.rs +++ b/crates/router/src/events/api_logs.rs @@ -1,16 +1,19 @@ use router_env::{tracing_actix_web::RequestId, types::FlowMetric}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use time::OffsetDateTime; use super::{EventType, RawEvent}; +use crate::services::authentication::AuthenticationType; -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct ApiEvent { api_flow: String, created_at_timestamp: i128, request_id: String, latency: u128, status_code: i64, + #[serde(flatten)] + auth_type: AuthenticationType, request: serde_json::Value, response: Option, } @@ -23,6 +26,7 @@ impl ApiEvent { status_code: i64, request: serde_json::Value, response: Option, + auth_type: AuthenticationType, ) -> Self { Self { api_flow: api_flow.to_string(), @@ -32,6 +36,7 @@ impl ApiEvent { status_code, request, response, + auth_type, } } } diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index cb94312b0e26..a1e657c7d92f 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -25,7 +25,7 @@ use serde_json::json; use tera::{Context, Tera}; use self::request::{HeaderExt, RequestBuilderExt}; -use super::authentication::{AuthInfo, AuthenticateAndFetch}; +use super::authentication::AuthenticateAndFetch; use crate::{ configs::settings::{Connectors, Settings}, consts, @@ -761,7 +761,6 @@ where Q: Serialize + Debug + 'a, T: Debug + Serialize, A: AppStateInfo + Clone, - U: AuthInfo, E: ErrorSwitch + error_stack::Context, OErr: ResponseError + error_stack::Context, errors::ApiErrorResponse: ErrorSwitch, @@ -782,12 +781,12 @@ where .change_context(errors::ApiErrorResponse::InternalServerError.switch())?; // Currently auth failures are not recorded as API events - let auth_out = api_auth + let (auth_out, auth_type) = api_auth .authenticate_and_fetch(request.headers(), &request_state) .await .switch()?; - let merchant_id = auth_out + let merchant_id = auth_type .get_merchant_id() .unwrap_or("MERCHANT_ID_NOT_FOUND") .to_string(); @@ -841,6 +840,7 @@ where status_code, serialized_request, serialized_response, + auth_type, ); match api_event.clone().try_into() { Ok(event) => { @@ -874,7 +874,6 @@ where Fut: Future, E>>, Q: Serialize + Debug + 'a, T: Debug + Serialize, - U: AuthInfo, A: AppStateInfo + Clone, ApplicationResponse: Debug, E: ErrorSwitch + error_stack::Context, diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index eec872a9f34f..faa7864aff5b 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -7,6 +7,7 @@ use error_stack::{report, IntoReport, ResultExt}; use external_services::kms::{self, decrypt::KmsDecrypt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use masking::{PeekInterface, StrongSecret}; +use serde::Serialize; use crate::{ configs::settings, @@ -21,11 +22,51 @@ use crate::{ utils::OptionExt, }; +#[derive(Clone, Debug)] pub struct AuthenticationData { pub merchant_account: domain::MerchantAccount, pub key_store: domain::MerchantKeyStore, } +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[serde(tag = "api_auth_type")] +pub enum AuthenticationType { + ApiKey { + merchant_id: String, + key_id: String, + }, + AdminApiKey, + MerchantJWT { + merchant_id: String, + user_id: Option, + }, + MerchantID { + merchant_id: String, + }, + PublishableKey { + merchant_id: String, + }, + NoAuth, +} + +impl AuthenticationType { + pub fn get_merchant_id(&self) -> Option<&str> { + match self { + Self::ApiKey { + merchant_id, + key_id: _, + } + | Self::MerchantID { merchant_id } + | Self::PublishableKey { merchant_id } + | Self::MerchantJWT { + merchant_id, + user_id: _, + } => Some(merchant_id.as_ref()), + Self::AdminApiKey | Self::NoAuth => None, + } + } +} + pub trait AuthInfo { fn get_merchant_id(&self) -> Option<&str>; } @@ -46,13 +87,12 @@ impl AuthInfo for AuthenticationData { pub trait AuthenticateAndFetch where A: AppStateInfo, - T: AuthInfo, { async fn authenticate_and_fetch( &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult; + ) -> RouterResult<(T, AuthenticationType)>; } #[derive(Debug)] @@ -69,8 +109,8 @@ where &self, _request_headers: &HeaderMap, _state: &A, - ) -> RouterResult<()> { - Ok(()) + ) -> RouterResult<((), AuthenticationType)> { + Ok(((), AuthenticationType::NoAuth)) } } @@ -83,7 +123,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let api_key = get_api_key(request_headers) .change_context(errors::ApiErrorResponse::Unauthorized)? .trim(); @@ -139,10 +179,17 @@ where .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; - Ok(AuthenticationData { + let auth = AuthenticationData { merchant_account: merchant, key_store, - }) + }; + Ok(( + auth.clone(), + AuthenticationType::ApiKey { + merchant_id: auth.merchant_account.merchant_id.clone(), + key_id: stored_api_key.key_id, + }, + )) } } @@ -183,7 +230,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<()> { + ) -> RouterResult<((), AuthenticationType)> { let request_admin_api_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; let conf = state.conf(); @@ -200,7 +247,7 @@ where .attach_printable("Admin Authentication Failure"))?; } - Ok(()) + Ok(((), AuthenticationType::AdminApiKey)) } } @@ -216,7 +263,7 @@ where &self, _request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let key_store = state .store() .get_merchant_key_store_by_merchant_id( @@ -245,10 +292,16 @@ where } })?; - Ok(AuthenticationData { + let auth = AuthenticationData { merchant_account: merchant, key_store, - }) + }; + Ok(( + auth.clone(), + AuthenticationType::MerchantID { + merchant_id: auth.merchant_account.merchant_id.clone(), + }, + )) } } @@ -264,7 +317,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let publishable_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; @@ -279,6 +332,14 @@ where e.change_context(errors::ApiErrorResponse::InternalServerError) } }) + .map(|auth| { + ( + auth.clone(), + AuthenticationType::PublishableKey { + merchant_id: auth.merchant_account.merchant_id.clone(), + }, + ) + }) } } @@ -300,12 +361,12 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<()> { + ) -> RouterResult<((), AuthenticationType)> { let mut token = get_jwt(request_headers)?; token = strip_jwt_token(token)?; decode_jwt::(token, state) .await - .map(|_| ()) + .map(|_| ((), AuthenticationType::NoAuth)) } } @@ -323,7 +384,7 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let mut token = get_jwt(request_headers)?; token = strip_jwt_token(token)?; let payload = decode_jwt::(token, state).await?; @@ -343,10 +404,17 @@ where .await .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; - Ok(AuthenticationData { + let auth = AuthenticationData { merchant_account: merchant, key_store, - }) + }; + Ok(( + auth.clone(), + AuthenticationType::MerchantJWT { + merchant_id: auth.merchant_account.merchant_id.clone(), + user_id: None, + }, + )) } }