hyperswitch icon indicating copy to clipboard operation
hyperswitch copied to clipboard

fix(authentication): add Organization context validation in `Merchant Create` and `Merchant List` APIs

Open tsdk02 opened this issue 7 months ago • 1 comments

Type of Change

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

Description

This PR introduces authentication validation checks in the following APIs to prevent unauthorized access across organizations:

List Merchant API

  • Problem: When using JWT-based auth, the API allowed any organization_id in the query params as long as the token was valid.
  • Fix: Added a validation to ensure that the organization_id provided in the query must match the one derived from the authentication context.
  • This prevents users from listing merchants in other organizations using a valid token.

Create Merchant API

  • Problem: Only the Admin API Key and configured fallback API Keys (set via env) can be used to create merchants.
    • If the Admin API Key fails, the fallback API Key is used as a backup.
    • When using this fallback API Key:
      • If organization_id is not provided, a new organization is created, potentially allowing unauthorized org creation.
      • If organization_id is manually provided, a user could create a merchant under any organization they should not have access to.
  • Fix:
    • When the fallback API Key is used, added a check to ensure that the merchant is created only within the organization associated with the authenticated merchant.
    • This prevents unauthorized creation of merchants in unintended organizations.

Changes to Auth Flow

We have updated the authentication mechanism to now return:

Option<AuthenticationDataWithOrg>
#[derive(Clone, Debug)]
pub struct AuthenticationDataWithOrg {
    pub organization_id: id_type::OrganizationId,
}

Some(AuthenticationDataWithOrg { organization_id }) is returned when:

  • A Configured Fallback API Key is used and the organization is resolved from the associated merchant.
  • A valid JWT token is used and the organization is derived from the merchant’s context.

None is returned when:

  • Admin API Key is used

Additional Changes

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

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_id passed in requests. This creates potential security risks where:

  • Users with valid JWTs can view merchants from other organizations.
  • API key users can create merchants under unrelated or unauthorized organizations.

This PR addresses those gaps by ensuring that all organization_id references 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:

  • Admin API Key (Primary)
  • Configured API Key (Fallback via env)

Request:

curl --location 'http://localhost:8080/accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data-raw '{
  "merchant_id": "merchant_1747853425",
  "locker_id": "m0010",
  "merchant_name": "M1 Account",
  "merchant_details": {
    "primary_contact_person": "John Test",
    "primary_email": "[email protected]",
    "primary_phone": "sunt laborum",
    "secondary_contact_person": "John Test2",
    "secondary_email": "[email protected]",
    "secondary_phone": "cillum do dolor id",
    "website": "https://www.example.com",
    "about_business": "Online Retail with a wide selection of organic products for North America",
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US",
      "first_name":"john",
      "last_name":"Doe"
    }
  },
  "organization_id": "org_AUw4KIR0COrD90wgqZLq", // (optional)
  "return_url": "https://google.com/success",
  "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
  },
  "sub_merchants_enabled": false,
  "parent_merchant_id":"merchant_123",
  "metadata": {
    "city": "NY",
    "unit": "245"
  },
  "primary_business_details": [
    {
      "country": "US",
      "business": "default"
    }
  ]
}'

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": "[email protected]",
        "secondary_contact_person": "John Test2",
        "secondary_phone": "cillum do dolor id",
        "secondary_email": "[email protected]",
        "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

Scenario organization_id in Request Expected Behavior
1.1 Present Use the provided organization_id to create the merchant.
1.2 Absent Create a new organization and associate the merchant with it.

The Admin API key is allowed to create new organizations if organization_id is missing.


2. Configured Fallback API Key (via env)

Scenario organization_id in Request Expected Behavior
2.1 Present & matches auth org Proceed with merchant creation.
2.2 Present but mismatched org Reject with InvalidRequestData.
2.3 Absent Automatically use the organization from the authentication context.

Fallback API Key must always operate within the authenticated org. Cross-org creation should be blocked.


List Merchant API:

We can test the behavior of the List Merchant API under three authentication modes:

  • Admin API Key
  • Configured Fallback API Key
  • JWT Token

Request:

curl --location 'http://localhost:8080/accounts/list?organization_id=org_AUw4KIR0COrD90wgqZLQ' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_UfHPiN8Ua6cSsV6QF0LZmH0U7JlPY1CMrjaKNWy2zyA6i5VR0NoKdW3lKqvb6vea' \
--data ''

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

Scenario organization_id in Query Expected Behavior
1.1 Present List merchants belonging to the specified organization.
1.2 Absent InvalidRequestData (organization_id is required).

Admin API key is allowed to list merchants from any organization explicitly passed via query.


2. Configured Fallback API Key (via env)

Scenario organization_id in Query Expected Behavior
2.1 Present & matches org from auth List merchants for that org.
2.2 Present but mismatched with auth org Reject with InvalidRequestData.
2.3 Absent organization_id is mandatory – reject the request.

Fallback API Key must always operate within its own org context.


3. JWT Authentication

Scenario X-Merchant-Id Header organization_id in Query Expected Behavior
3.1 Present & matches JWT Present & matches org auth context Return merchant list
3.2 Present & matches JWT Present but mismatches org auth context Error - InvalidRequestData
3.2 Present but mismatches JWT Present Error – mismatched merchant context
3.3 Present org_id not provided organization_id required
3.4 Header missing org_id matches JWT context merchant_id required via header

JWT-based requests must strictly match both merchant_id (via header) and organization_id with those in the token payload/auth context.


Checklist

  • [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

tsdk02 avatar May 21 '25 14:05 tsdk02