fix(authentication): add Organization context validation in Merchant Create and Merchant List APIs#8103
Conversation
…th Organization context validation
Changed Files
|
Merchant Create and Merchant List APIs with Organization context validationMerchant Create and Merchant List APIs
| state: &SessionState, | ||
| key_store: domain::MerchantKeyStore, | ||
| identifier: &id_type::MerchantId, | ||
| _org_data: Option<authentication::AuthenticationDataWithOrg>, |
There was a problem hiding this comment.
why are we not using in v2? is it not required?
There was a problem hiding this comment.
The trait MerchantAccountCreateBridge is common to both v1 and v2, and we would not receive org_id from authentication in v2. The request body - MerchantAccountCreate will mandatorily have the organization_id, which can be used to retrieve the organization in the v2 implementation.
| &req, | ||
| new_request_payload_with_org_id, | ||
| |state, _, req, _| create_merchant_account(state, req), | ||
| |state, _, req, _| create_merchant_account(state, req, None), |
There was a problem hiding this comment.
For v2 create_merchant, V2AdminApiAuth is the only authentication allowed, so we would not receive any org_id from auth, hence passing None.
| 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)?; |
There was a problem hiding this comment.
This is also be unexpected in a way: if we are able to find API key in our database, but unable to find the merchant account associated with the API key, then our database is possibly in an inconsistent state.
Not entirely sure if we should return 401 or another status code here, let's at least add a log line maybe.
There was a problem hiding this comment.
This is how it is being handled in all types of API Key authentications currently. We are throwing 401 when the API Key is found, but merchant account is not found.
Should this be changed?
There was a problem hiding this comment.
I can take this up in a separate PR, for handling this in all such instances.
…ordea-sepa * 'main' of github.com:juspay/hyperswitch: (30 commits) chore(version): 2025.05.30.0 chore(ci): update postman ci credentials (#8172) chore(docs): remove old add_connector.md file (#8143) refactor: Payment Attempt as mandatory field in PaymentStatusData (#8126) fix(payment_link): sanitize embedded payment link data (#7736) chore(version): 2025.05.29.0 feat(analytics): Add ckh columns for 3ds intelligence analytics (#8136) refactor(debit_routing): Handle missing merchant_business_country by defaulting to US (#8141) chore(version): 2025.05.28.0 refactor(success_based): add support for exploration (#8158) feat(dynamic_routing): add get api for dynamic routing volume split (#8114) fix: incorrect payout_method_id in payouts table (#8107) feat(router): Enable client_secret auth for payments_get_intent [v2] (#8119) chore: address Rust 1.87.0 clippy lints (#8130) feat: list for dynamic routing (#8111) ci(cypress): fix mandates unsupported connectors (#8086) feat(connector): [Barclaycard] Implement Cards - Non 3DS flow (#8068) fix(authentication): add Organization context validation in `Merchant Create` and `Merchant List` APIs (#8103) feat(connector): [Worldpayxml] add card payment (#8076) feat(connector): Stripe revolut pay wallet integration (#8066) ...
Type of Change
Description
This PR introduces authentication validation checks in the following APIs to prevent unauthorized access across organizations:
List Merchant API
organization_idin the query params as long as the token was valid.organization_idprovided in the query must match the one derived from the authentication context.Create Merchant API
Only the Admin API Key and configured fallback API Keys (set via env) can be used to create merchants.
organization_idis not provided, a new organization is created, potentially allowing unauthorized org creation.organization_idis manually provided, a user could create a merchant under any organization they should not have access to.Changes to Auth Flow
We have updated the authentication mechanism to now return:
Some(AuthenticationDataWithOrg { organization_id })is returned when:Noneis returned when:Additional Changes
Motivation and Context
The current implementations of the List and Create Merchant APIs do not enforce strict validation between the authenticated user's organization context and the
organization_idpassed in requests. This creates potential security risks where:This PR addresses those gaps by ensuring that all
organization_idreferences are explicitly validated against the authenticated context.How did you test it?
Create Merchant API:
We can test the behavior of the Create Merchant API under two authentication modes:
Request:
Expected Response:
{ "merchant_id": "merchant_1747853253", "merchant_name": "M1 Account", "return_url": "https://google.com/success", "enable_payment_response_hash": true, "payment_response_hash_key": "zXBHjOf9FmDuG4hi8tX409O6yVeY9H4Ldt5ofqvxFvv6RROH3Edj3p6WDU94maPG", "redirect_to_merchant_with_http_post": false, "merchant_details": { "primary_contact_person": "John Test", "primary_phone": "sunt laborum", "primary_email": "JohnTest@test.com", "secondary_contact_person": "John Test2", "secondary_phone": "cillum do dolor id", "secondary_email": "JohnTest2@test.com", "website": "https://www.example.com", "about_business": "Online Retail with a wide selection of organic products for North America", "address": { "city": "San Fransico", "country": "US", "line1": "1467", "line2": "Harrison Street", "line3": "Harrison Street", "zip": "94122", "state": "California", "first_name": "john", "last_name": "Doe" } }, "webhook_details": { "webhook_version": "1.0.1", "webhook_username": "ekart_retail", "webhook_password": "password_ekart@123", "webhook_url": "https://webhook.site", "payment_created_enabled": true, "payment_succeeded_enabled": true, "payment_failed_enabled": true }, "payout_routing_algorithm": null, "sub_merchants_enabled": false, "parent_merchant_id": null, "publishable_key": "pk_dev_14a42e013f8042888e6aef2b00fb6d35", "metadata": { "city": "NY", "unit": "245", "compatible_connector": null }, "locker_id": "m0010", "primary_business_details": [ { "country": "US", "business": "default" } ], "frm_routing_algorithm": null, "organization_id": "org_AUw4KIR0COrD90wgqZLq", "is_recon_enabled": false, "default_profile": "pro_tIoLTR9Zz3QO66EtmhE4", "recon_status": "not_requested", "pm_collect_link_config": null, "product_type": "orchestration" }1. Admin API Key
organization_idto create the merchant.2. Configured Fallback API Key (via env)
InvalidRequestData.List Merchant API:
We can test the behavior of the List Merchant API under three authentication modes:
Request:
Expected Response:
[ { "merchant_id": "merchant_1747665050", "merchant_name": "Hyperswitch", "return_url": null, "enable_payment_response_hash": true, "payment_response_hash_key": "rki2yoKsrd7xZWc4EiGsHWO1W0XaGKNwmfyHJ1E4IeuEJWKlzsGavQDBd89e6PxL", "redirect_to_merchant_with_http_post": false, "merchant_details": null, "webhook_details": null, "payout_routing_algorithm": null, "sub_merchants_enabled": false, "parent_merchant_id": null, "publishable_key": "pk_dev_dfb4faba44374de8851bc7f36b1a25b9", "metadata": null, "locker_id": null, "primary_business_details": [], "frm_routing_algorithm": null, "organization_id": "org_AUw4KIR0COrD90wgqZLQ", "is_recon_enabled": false, "default_profile": "pro_poTKIbFuUTQlH4Lukesv", "recon_status": "not_requested", "pm_collect_link_config": null, "product_type": "orchestration" }, { "merchant_id": "merchant_1747665195", "merchant_name": "Standard", "return_url": null, "enable_payment_response_hash": true, "payment_response_hash_key": "XuyZ2ltZeYtTvVSaYjgQ4U5TL8JLTKcoIkzab89PkfUAWueP3OeYbNmypErVefyh", "redirect_to_merchant_with_http_post": false, "merchant_details": null, "webhook_details": null, "payout_routing_algorithm": null, "sub_merchants_enabled": false, "parent_merchant_id": null, "publishable_key": "pk_dev_44ab7414c3634bde825492fa49b9a49a", "metadata": null, "locker_id": null, "primary_business_details": [], "frm_routing_algorithm": null, "organization_id": "org_AUw4KIR0COrD90wgqZLQ", "is_recon_enabled": false, "default_profile": "pro_QxoXgT37uHxKdVZZxpQS", "recon_status": "not_requested", "pm_collect_link_config": null, "product_type": "orchestration" } ]1. Admin API Key
2. Configured Fallback API Key (via env)
InvalidRequestData.3. JWT Authentication
Checklist
cargo +nightly fmt --allcargo clippy