Bug: [REFACTOR] API Key List and Update endpoints in Hyperswitch V2



Update API Key endpoint in V2, the URLs are registered in the `app.rs` but the necessary changes to it has not been made in `api_keys.rs`.

In addition to that, List API Key endpoint for V2 does not work as no such thing exist in `api_keys.rs`.

So at present, when that endpoint is hit, you'll receive a 404 as mentioned below:

```curl
curl --location --request PUT '{{baseUrl}}/v2/api_keys/{{api_key_id}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id: {{mechant_id}}' \
--header 'api-key: {{admin_api_key}}' \
--data '{
     "name": "My new Api Key"
}'
```
```json
wrong number of parameters: 1 expected 2
```

List end point:

```curl
curl --location '{{baseUrl}}/v2/api_keys/list' \
--header 'x-merchant-id: {{mechant_id}}' \
--header 'api-key: {{admin_api_key}}'
```
```json
wrong number of parameters: 0 expected 1
```

I'll be fixing this.
diff --git a/api-reference-v2/api-reference/api-key/api-key--list.mdx b/api-reference-v2/api-reference/api-key/api-key--list.mdx
new file mode 100644
index 00000000000..5975e9bd6ca
--- /dev/null
+++ b/api-reference-v2/api-reference/api-key/api-key--list.mdx
@@ -0,0 +1,3 @@
+---
+openapi: get /v2/api_keys/list
+---
diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json
index ae48e2b8e76..08bc5868af0 100644
--- a/api-reference-v2/mint.json
+++ b/api-reference-v2/mint.json
@@ -80,7 +80,8 @@
         "api-reference/api-key/api-key--create",
         "api-reference/api-key/api-key--retrieve",
         "api-reference/api-key/api-key--update",
-        "api-reference/api-key/api-key--revoke"
+        "api-reference/api-key/api-key--revoke",
+        "api-reference/api-key/api-key--list"
       ]
     },
     {
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index fb679970f3d..c60ea81c7b4 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -1433,6 +1433,60 @@
         ]
       }
     },
+    "/v2/api_keys/list": {
+      "get": {
+        "tags": [
+          "API Key"
+        ],
+        "summary": "API Key - List",
+        "description": "List all the API Keys associated to a merchant account.",
+        "operationId": "List all API Keys associated with a merchant account",
+        "parameters": [
+          {
+            "name": "limit",
+            "in": "query",
+            "description": "The maximum number of API Keys to include in the response",
+            "required": false,
+            "schema": {
+              "type": "integer",
+              "format": "int64",
+              "nullable": true
+            }
+          },
+          {
+            "name": "skip",
+            "in": "query",
+            "description": "The number of API Keys to skip when retrieving the list of API keys.",
+            "required": false,
+            "schema": {
+              "type": "integer",
+              "format": "int64",
+              "nullable": true
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "List of API Keys retrieved successfully",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "array",
+                  "items": {
+                    "$ref": "#/components/schemas/RetrieveApiKeyResponse"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "security": [
+          {
+            "admin_api_key": []
+          }
+        ]
+      }
+    },
     "/v2/customers": {
       "post": {
         "tags": [
diff --git a/crates/api_models/src/api_keys.rs b/crates/api_models/src/api_keys.rs
index 1fc7ce97be8..65cc6b9a25a 100644
--- a/crates/api_models/src/api_keys.rs
+++ b/crates/api_models/src/api_keys.rs
@@ -154,7 +154,7 @@ pub struct RevokeApiKeyResponse {
 }
 
 /// The constraints that are applicable when listing API Keys associated with a merchant account.
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Serialize)]
 #[serde(deny_unknown_fields)]
 pub struct ListApiKeyConstraints {
     /// The maximum number of API Keys to include in the response.
diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs
index b8a872ffd25..5816df518bd 100644
--- a/crates/api_models/src/events.rs
+++ b/crates/api_models/src/events.rs
@@ -90,6 +90,7 @@ impl_api_event_type!(
         CardInfoResponse,
         CreateApiKeyResponse,
         CreateApiKeyRequest,
+        ListApiKeyConstraints,
         MerchantConnectorDeleteResponse,
         MerchantConnectorUpdate,
         MerchantConnectorCreate,
diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs
index 85865584494..ae999bb0604 100644
--- a/crates/openapi/src/openapi.rs
+++ b/crates/openapi/src/openapi.rs
@@ -183,6 +183,7 @@ Never share your secret api keys. Keep them guarded and secure.
         routes::api_keys::api_key_retrieve,
         routes::api_keys::api_key_update,
         routes::api_keys::api_key_revoke,
+        routes::api_keys::api_key_list,
 
         // Routes for events
         routes::webhook_events::list_initial_webhook_delivery_attempts,
diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs
index 05fded520f0..ae9818ddee6 100644
--- a/crates/openapi/src/openapi_v2.rs
+++ b/crates/openapi/src/openapi_v2.rs
@@ -109,6 +109,7 @@ Never share your secret api keys. Keep them guarded and secure.
         routes::api_keys::api_key_retrieve,
         routes::api_keys::api_key_update,
         routes::api_keys::api_key_revoke,
+        routes::api_keys::api_key_list,
 
         //Routes for customers
         routes::customers::customers_create,
diff --git a/crates/openapi/src/routes/api_keys.rs b/crates/openapi/src/routes/api_keys.rs
index 7ed2afe91a1..7527c8a1095 100644
--- a/crates/openapi/src/routes/api_keys.rs
+++ b/crates/openapi/src/routes/api_keys.rs
@@ -163,3 +163,44 @@ pub async fn api_key_revoke() {}
     security(("admin_api_key" = []))
 )]
 pub async fn api_key_revoke() {}
+
+#[cfg(feature = "v1")]
+/// API Key - List
+///
+/// List all the API Keys associated to a merchant account.
+#[utoipa::path(
+    get,
+    path = "/api_keys/{merchant_id}/list",
+    params(
+        ("merchant_id" = String, Path, description = "The unique identifier for the merchant account"),
+        ("limit" = Option<i64>, Query, description = "The maximum number of API Keys to include in the response"),
+        ("skip" = Option<i64>, Query, description = "The number of API Keys to skip when retrieving the list of API keys."),
+    ),
+    responses(
+        (status = 200, description = "List of API Keys retrieved successfully", body = Vec<RetrieveApiKeyResponse>),
+    ),
+    tag = "API Key",
+    operation_id = "List all API Keys associated with a merchant account",
+    security(("admin_api_key" = []))
+)]
+pub async fn api_key_list() {}
+
+#[cfg(feature = "v2")]
+/// API Key - List
+///
+/// List all the API Keys associated to a merchant account.
+#[utoipa::path(
+    get,
+    path = "/v2/api_keys/list",
+    params(
+        ("limit" = Option<i64>, Query, description = "The maximum number of API Keys to include in the response"),
+        ("skip" = Option<i64>, Query, description = "The number of API Keys to skip when retrieving the list of API keys."),
+    ),
+    responses(
+        (status = 200, description = "List of API Keys retrieved successfully", body = Vec<RetrieveApiKeyResponse>),
+    ),
+    tag = "API Key",
+    operation_id = "List all API Keys associated with a merchant account",
+    security(("admin_api_key" = []))
+)]
+pub async fn api_key_list() {}
diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs
index a00e740b3e6..047e0d9b886 100644
--- a/crates/router/src/routes/api_keys.rs
+++ b/crates/router/src/routes/api_keys.rs
@@ -9,10 +9,6 @@ use crate::{
     types::api as api_types,
 };
 
-/// API Key - Create
-///
-/// Create a new API Key for accessing our APIs from your servers. The plaintext API Key will be
-/// displayed only once on creation, so ensure you store it securely.
 #[cfg(feature = "v1")]
 #[instrument(skip_all, fields(flow = ?Flow::ApiKeyCreate))]
 pub async fn api_key_create(
@@ -78,9 +74,6 @@ pub async fn api_key_create(
     .await
 }
 
-/// API Key - Retrieve
-///
-/// Retrieve information about the specified API Key.
 #[cfg(feature = "v2")]
 #[instrument(skip_all, fields(flow = ?Flow::ApiKeyRetrieve))]
 pub async fn api_key_retrieve(
@@ -117,9 +110,6 @@ pub async fn api_key_retrieve(
 }
 
 #[cfg(feature = "v1")]
-/// API Key - Retrieve
-///
-/// Retrieve information about the specified API Key.
 #[instrument(skip_all, fields(flow = ?Flow::ApiKeyRetrieve))]
 pub async fn api_key_retrieve(
     state: web::Data<AppState>,
@@ -150,9 +140,6 @@ pub async fn api_key_retrieve(
 }
 
 #[cfg(feature = "v1")]
-/// API Key - Update
-///
-/// Update information for the specified API Key.
 #[instrument(skip_all, fields(flow = ?Flow::ApiKeyUpdate))]
 pub async fn api_key_update(
     state: web::Data<AppState>,
@@ -190,26 +177,27 @@ pub async fn api_key_update(
 pub async fn api_key_update(
     state: web::Data<AppState>,
     req: HttpRequest,
-    path: web::Path<(common_utils::id_type::MerchantId, String)>,
+    key_id: web::Path<String>,
     json_payload: web::Json<api_types::UpdateApiKeyRequest>,
 ) -> impl Responder {
     let flow = Flow::ApiKeyUpdate;
-    let (merchant_id, key_id) = path.into_inner();
+    let api_key_id = key_id.into_inner();
     let mut payload = json_payload.into_inner();
-    payload.key_id = key_id;
-    payload.merchant_id.clone_from(&merchant_id);
+    payload.key_id = api_key_id;
 
     api::server_wrap(
         flow,
         state,
         &req,
         payload,
-        |state, _, payload, _| api_keys::update_api_key(state, payload),
+        |state, authentication_data, mut payload, _| {
+            payload.merchant_id = authentication_data.merchant_account.get_id().to_owned();
+            api_keys::update_api_key(state, payload)
+        },
         auth::auth_type(
-            &auth::AdminApiAuth,
-            &auth::JWTAuthMerchantFromRoute {
-                merchant_id,
-                required_permission: Permission::ApiKeyWrite,
+            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::JWTAuthMerchantFromHeader {
+                required_permission: Permission::ApiKeyRead,
                 minimum_entity_level: EntityType::Merchant,
             },
             req.headers(),
@@ -220,10 +208,6 @@ pub async fn api_key_update(
 }
 
 #[cfg(feature = "v1")]
-/// API Key - Revoke
-///
-/// Revoke the specified API Key. Once revoked, the API Key can no longer be used for
-/// authenticating with our APIs.
 #[instrument(skip_all, fields(flow = ?Flow::ApiKeyRevoke))]
 pub async fn api_key_revoke(
     state: web::Data<AppState>,
@@ -283,24 +267,7 @@ pub async fn api_key_revoke(
     .await
 }
 
-/// API Key - List
-///
-/// List all API Keys associated with your merchant account.
-#[utoipa::path(
-    get,
-    path = "/api_keys/{merchant_id}/list",
-    params(
-        ("merchant_id" = String, Path, description = "The unique identifier for the merchant account"),
-        ("limit" = Option<i64>, Query, description = "The maximum number of API Keys to include in the response"),
-        ("skip" = Option<i64>, Query, description = "The number of API Keys to skip when retrieving the list of API keys."),
-    ),
-    responses(
-        (status = 200, description = "List of API Keys retrieved successfully", body = Vec<RetrieveApiKeyResponse>),
-    ),
-    tag = "API Key",
-    operation_id = "List all API Keys associated with a merchant account",
-    security(("admin_api_key" = []))
-)]
+#[cfg(feature = "v1")]
 #[instrument(skip_all, fields(flow = ?Flow::ApiKeyList))]
 pub async fn api_key_list(
     state: web::Data<AppState>,
@@ -335,3 +302,34 @@ pub async fn api_key_list(
     )
     .await
 }
+#[cfg(feature = "v2")]
+#[instrument(skip_all, fields(flow = ?Flow::ApiKeyList))]
+pub async fn api_key_list(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query: web::Query<api_types::ListApiKeyConstraints>,
+) -> impl Responder {
+    let flow = Flow::ApiKeyList;
+    let payload = query.into_inner();
+
+    api::server_wrap(
+        flow,
+        state,
+        &req,
+        payload,
+        |state, authentication_data, payload, _| async move {
+            let merchant_id = authentication_data.merchant_account.get_id().to_owned();
+            api_keys::list_api_keys(state, merchant_id, payload.limit, payload.skip).await
+        },
+        auth::auth_type(
+            &auth::AdminApiAuthWithMerchantIdFromHeader,
+            &auth::JWTAuthMerchantFromHeader {
+                required_permission: Permission::ApiKeyRead,
+                minimum_entity_level: EntityType::Merchant,
+            },
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    )
+    .await
+}

## Type of Change
<!-- Put an `x` in the boxes that apply -->

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

## Description
<!-- Describe your changes in detail -->

This PR fixes `404` errors thrown by API Key List and Update endpoints.

Previously:

Update end point:

```curl
curl --location --request PUT '{{baseUrl}}/v2/api_keys/{{api_key_id}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id: {{mechant_id}}' \
--header 'api-key: {{admin_api_key}}' \
--data '{
     "name": "My new Api Key"
}'
```
```json
wrong number of parameters: 1 expected 2
```

List end point:

```curl
curl --location '{{baseUrl}}/v2/api_keys/list' \
--header 'x-merchant-id: {{mechant_id}}' \
--header 'api-key: {{admin_api_key}}'
```
```json
wrong number of parameters: 0 expected 1
```

### 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).
-->

endpoints under `v2` flags were not properly handled in `api_keys.rs` file and this PR fixes that.

closes #5979


## 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)?
-->

tested it by running locally.

Create Api Key endpoint:

```curl
curl --location 'http://Localhost:8080/v2/api_keys' \
--header 'x-merchant-id: cloth_seller_o6pEetqmlQjhp0kfR7ae' \
--header 'Content-Type: application/json' \
--header 'api-key: test_admin' \
--data '{
    "name": "My Api Key",
    "expiration": "never"
}'
```
```json
{
    "key_id": "dev_xhm09vaThZEPcxuZMJOG",
    "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
    "name": "My Api Key",
    "description": null,
    "api_key": "dev_N0jTf3lRHljowpMGs9IKTPhOYkZXY3Sca8kUpSR5DbtjxaBvfqMIqshU5hx81AJp",
    "created": "2024-09-20T13:03:00.859Z",
    "expiration": "never"
}
```

Update Api Key endpoint:

```curl
curl --location --request PUT 'http://Localhost:8080/v2/api_keys/dev_xhm09vaThZEPcxuZMJOG' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id: cloth_seller_o6pEetqmlQjhp0kfR7ae' \
--header 'api-key: test_admin' \
--data '{
     "name": "My new Api Key"
}'
```
```json
{
    "key_id": "dev_xhm09vaThZEPcxuZMJOG",
    "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
    "name": "My new Api Key",
    "description": null,
    "prefix": "dev_N0jTf3lR",
    "created": "2024-09-20T13:03:00.859Z",
    "expiration": "never"
}
```

List Api Keys endpoint:

```curl
curl --location 'http://Localhost:8080/v2/api_keys/list' \
--header 'x-merchant-id: cloth_seller_o6pEetqmlQjhp0kfR7ae' \
--header 'api-key: test_admin'
```
```json
[
    {
        "key_id": "dev_X8F7qFGnO9a1IGIrtuzb",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My Api Key",
        "description": null,
        "prefix": "dev_8jfmSMnn",
        "created": "2024-09-20T13:02:57.150Z",
        "expiration": "never"
    },
    {
        "key_id": "dev_F8TDC9bHerHzYdiql80m",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My Api Key",
        "description": null,
        "prefix": "dev_g8SO3blK",
        "created": "2024-09-20T13:02:57.815Z",
        "expiration": "never"
    },
    {
        "key_id": "dev_DQ3C3DdFczazUFNc6n0i",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My Api Key",
        "description": null,
        "prefix": "dev_GnLRtXQm",
        "created": "2024-09-20T13:02:58.361Z",
        "expiration": "never"
    },
    {
        "key_id": "dev_kdISpxDQBmMpJ8BQuGmH",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My Api Key",
        "description": null,
        "prefix": "dev_h34nUAh4",
        "created": "2024-09-20T13:02:58.796Z",
        "expiration": "never"
    },
    {
        "key_id": "dev_WBHT3v4SlWcU4R3R8Wt2",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My Api Key",
        "description": null,
        "prefix": "dev_v5jdBEFo",
        "created": "2024-09-20T13:02:59.806Z",
        "expiration": "never"
    },
    {
        "key_id": "dev_qiwCmtE8Lbj24uJYC1wQ",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My Api Key",
        "description": null,
        "prefix": "dev_WRaLVEGq",
        "created": "2024-09-20T13:03:00.335Z",
        "expiration": "never"
    },
    {
        "key_id": "dev_xhm09vaThZEPcxuZMJOG",
        "merchant_id": "cloth_seller_o6pEetqmlQjhp0kfR7ae",
        "name": "My new Api Key",
        "description": null,
        "prefix": "dev_N0jTf3lR",
        "created": "2024-09-20T13:03:00.859Z",
        "expiration": "never"
    }
]
```

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
