From 339d873c20f5d3616cd42f66bc48092fa5eb0a10 Mon Sep 17 00:00:00 2001 From: Nicolas Gorga Date: Thu, 13 Nov 2025 11:12:40 -0300 Subject: [PATCH 1/8] Resolve user_id field when actor type is api-key to the user linked to the key --- .../src/api/admin/draft-orders/[id]/route.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/medusa/src/api/admin/draft-orders/[id]/route.ts b/packages/medusa/src/api/admin/draft-orders/[id]/route.ts index 23945f47f1199..f348c45ed720b 100644 --- a/packages/medusa/src/api/admin/draft-orders/[id]/route.ts +++ b/packages/medusa/src/api/admin/draft-orders/[id]/route.ts @@ -7,6 +7,7 @@ import { AuthenticatedMedusaRequest, MedusaRequest, MedusaResponse, + AuthType } from "@medusajs/framework/http" import { HttpTypes } from "@medusajs/framework/types" import { ContainerRegistrationKeys } from "@medusajs/framework/utils" @@ -39,10 +40,23 @@ export const POST = async ( ) => { const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) + let userId = req.auth_context.actor_id + const authType = req.auth_context.actor_type as AuthType + + const shouldResolveUser = authType === 'api-key' + if (shouldResolveUser) { + const {data: [apiKey]} = await query.graph({ + entity: 'api_key', + fields: ['created_by'], + filters: { id: userId }, + }) + userId = apiKey.created_by + } + await updateDraftOrderWorkflow(req.scope).run({ input: { ...req.validatedBody, - user_id: req.auth_context.actor_id, + user_id: userId, id: req.params.id, }, }) From faeaf8e8ea46230e47ae9441a1b4f64574309856 Mon Sep 17 00:00:00 2001 From: Nicolas Gorga Date: Thu, 13 Nov 2025 11:42:50 -0300 Subject: [PATCH 2/8] Add tests --- .../draft-order/admin/draft-order.spec.ts | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts index 178b0fed00fd7..d8702a6e72816 100644 --- a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts +++ b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts @@ -1,6 +1,6 @@ import { medusaIntegrationTestRunner } from "@medusajs/test-utils" import { HttpTypes } from "@medusajs/types" -import { ModuleRegistrationName, ProductStatus } from "@medusajs/utils" +import { ApiKeyType, ModuleRegistrationName, ProductStatus } from "@medusajs/utils" import { adminHeaders, createAdminUser, } from "../../../../helpers/create-admin-user" import { setupTaxStructure } from "../../../../modules/__tests__/fixtures" @@ -14,12 +14,14 @@ medusaIntegrationTestRunner({ let testDraftOrder: HttpTypes.AdminDraftOrder let shippingOption: HttpTypes.AdminShippingOption let shippingOptionHeavy: HttpTypes.AdminShippingOption + let apiKey: HttpTypes.AdminApiKeyResponse['api_key'] + let userId: string beforeEach(async () => { const container = getContainer() await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX)) - await createAdminUser(dbConnection, adminHeaders, container) + userId = (await createAdminUser(dbConnection, adminHeaders, container)).user.id region = ( await api.post( @@ -217,6 +219,36 @@ medusaIntegrationTestRunner({ expect(response.status).toBe(200) expect(response.data.draft_order.email).toBe("test_new@test.com") }) + + it("should set created_by to id of user linked to secret key", async () => { + apiKey = (await api.post('/admin/api-keys', { + title: 'secret-key', + type: ApiKeyType.SECRET + }, adminHeaders)).data.api_key + + const draftOrderResponse = await api.post( + `/admin/draft-orders/${testDraftOrder.id}`, + { + email: "test_new@test.com", + }, + { + auth: { + username: apiKey.token, + }, + } + ) + + expect(draftOrderResponse.status).toBe(200) + expect(draftOrderResponse.data.draft_order.email).toBe("test_new@test.com") + + const orderChange = (await api.get(`/admin/orders/${testDraftOrder.id}/changes`, adminHeaders)).data.order_changes[0] + + expect(orderChange).toEqual( + expect.objectContaining({ + created_by: userId + }) + ) + }) }) describe("DELETE /draft-orders/:id", () => { From a410cf3e27d00e271d0db40461b71bd5eb1f8c3e Mon Sep 17 00:00:00 2001 From: Nicolas Gorga Date: Thu, 13 Nov 2025 11:44:08 -0300 Subject: [PATCH 3/8] Add changeset --- .changeset/hungry-oranges-find.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-oranges-find.md diff --git a/.changeset/hungry-oranges-find.md b/.changeset/hungry-oranges-find.md new file mode 100644 index 0000000000000..e107027d543ef --- /dev/null +++ b/.changeset/hungry-oranges-find.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): resolve user_id from user linked to secret key on draft order edit with api-key auth From 2098d19bf5b57aac5488a2a0a69a5e65c43ef610 Mon Sep 17 00:00:00 2001 From: Nicolas Gorga Date: Tue, 18 Nov 2025 11:59:10 +0100 Subject: [PATCH 4/8] Add middleware to set secret api key context --- .../framework/src/http/middlewares/index.ts | 1 + .../middlewares/set-secret-api-key-context.ts | 52 +++++++++++++++++++ packages/core/framework/src/http/types.ts | 5 ++ packages/medusa/src/api/middlewares.ts | 7 +++ 4 files changed, 65 insertions(+) create mode 100644 packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts diff --git a/packages/core/framework/src/http/middlewares/index.ts b/packages/core/framework/src/http/middlewares/index.ts index 2c3e6aaf61669..63e08af2a838f 100644 --- a/packages/core/framework/src/http/middlewares/index.ts +++ b/packages/core/framework/src/http/middlewares/index.ts @@ -5,3 +5,4 @@ export * from "./apply-default-filters" export * from "./apply-params-as-filters" export * from "./clear-filters-by-key" export * from "./set-context" +export * from "./set-secret-api-key-context" diff --git a/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts new file mode 100644 index 0000000000000..81339a87deacd --- /dev/null +++ b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts @@ -0,0 +1,52 @@ +import { + ContainerRegistrationKeys, + MedusaError, + MedusaErrorTypes, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaNextFunction, + MedusaResponse, +} from "../types" + +export async function setSecretApiKeyContext( + req: AuthenticatedMedusaRequest, + _: MedusaResponse, + next: MedusaNextFunction +) { + if (!req.auth_context) { + throw new MedusaError( + MedusaErrorTypes.INVALID_DATA, + "No `auth_context` found in request, make sure to apply this middleware after the `authenticate` middleware." + ) + } + + const shouldSkip = req.auth_context.actor_type !== "api-key" + if (shouldSkip) { + next() + } + + const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) + const { + data: [apiKey], + } = await query.graph( + { + entity: "api_key", + fields: ["created_by"], + filters: { + id: req.auth_context.actor_id, + }, + withDeleted: true, + }, + { + cache: { + enable: true, + }, + } + ) + + req.secret_key_context = { + created_by: apiKey.created_by, + } + next() +} diff --git a/packages/core/framework/src/http/types.ts b/packages/core/framework/src/http/types.ts index 6a0412f46967e..ffedde5b1899d 100644 --- a/packages/core/framework/src/http/types.ts +++ b/packages/core/framework/src/http/types.ts @@ -197,12 +197,17 @@ export interface PublishableKeyContext { sales_channel_ids: string[] } +export interface SecretKeyContext { + created_by: string +} + export interface AuthenticatedMedusaRequest< Body = unknown, QueryFields = Record > extends MedusaRequest { auth_context: AuthContext publishable_key_context?: PublishableKeyContext + secret_key_context?: SecretKeyContext } export interface MedusaStoreRequest< diff --git a/packages/medusa/src/api/middlewares.ts b/packages/medusa/src/api/middlewares.ts index ec8f03f310324..64f557505144d 100644 --- a/packages/medusa/src/api/middlewares.ts +++ b/packages/medusa/src/api/middlewares.ts @@ -65,9 +65,16 @@ import { storeReturnReasonRoutesMiddlewares } from "./store/return-reasons/middl import { storeShippingOptionRoutesMiddlewares } from "./store/shipping-options/middlewares" import { adminShippingOptionTypeRoutesMiddlewares } from "./admin/shipping-option-types/middlewares" import { adminIndexRoutesMiddlewares } from "./admin/index/middlewares" +import { setSecretApiKeyContext } from "@medusajs/framework" export default defineMiddlewares([ ...storeRoutesMiddlewares, + { + matcher: "/admin*", + middlewares: [ + setSecretApiKeyContext, + ] + }, ...adminCustomerGroupRoutesMiddlewares, ...adminCustomerRoutesMiddlewares, ...adminPromotionRoutesMiddlewares, From bf8ee12607b0c5f9cd4ced063b4ab2e4bc793a81 Mon Sep 17 00:00:00 2001 From: Nicolas Gorga <62995075+NicolasGorga@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:00:56 +0100 Subject: [PATCH 5/8] Update integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts Co-authored-by: Adrien de Peretti --- .../http/__tests__/draft-order/admin/draft-order.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts index d8702a6e72816..907f0fe2d02fe 100644 --- a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts +++ b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts @@ -21,7 +21,7 @@ medusaIntegrationTestRunner({ const container = getContainer() await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX)) - userId = (await createAdminUser(dbConnection, adminHeaders, container)).user.id + userId = await createAdminUser(dbConnection, adminHeaders, container).then(user => user.id) region = ( await api.post( From 31562b8fc4c156e6492294e03665bc47788ba4d3 Mon Sep 17 00:00:00 2001 From: Nicolas Gorga <62995075+NicolasGorga@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:01:07 +0100 Subject: [PATCH 6/8] Update integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts Co-authored-by: Adrien de Peretti --- .../http/__tests__/draft-order/admin/draft-order.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts index 907f0fe2d02fe..f931967be295d 100644 --- a/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts +++ b/integration-tests/http/__tests__/draft-order/admin/draft-order.spec.ts @@ -220,7 +220,7 @@ medusaIntegrationTestRunner({ expect(response.data.draft_order.email).toBe("test_new@test.com") }) - it("should set created_by to id of user linked to secret key", async () => { + it("should use the secret key linked user to set created_by", async () => { apiKey = (await api.post('/admin/api-keys', { title: 'secret-key', type: ApiKeyType.SECRET From 1ffdffce96fb52bcac435996a9cd350e50f7f7f8 Mon Sep 17 00:00:00 2001 From: Nicolas Gorga Date: Sun, 23 Nov 2025 14:58:52 -0300 Subject: [PATCH 7/8] Add missing return --- .../src/http/middlewares/set-secret-api-key-context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts index 81339a87deacd..1e1d24f9103b3 100644 --- a/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts +++ b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts @@ -23,7 +23,7 @@ export async function setSecretApiKeyContext( const shouldSkip = req.auth_context.actor_type !== "api-key" if (shouldSkip) { - next() + return next() } const query = req.scope.resolve(ContainerRegistrationKeys.QUERY) From 6a6b0b84897e2aeaad1f7cfb18df230d8414783f Mon Sep 17 00:00:00 2001 From: Nicolas Gorga Date: Tue, 25 Nov 2025 07:48:18 -0300 Subject: [PATCH 8/8] Add not found check --- .../src/http/middlewares/set-secret-api-key-context.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts index 1e1d24f9103b3..902bd1233fb45 100644 --- a/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts +++ b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts @@ -45,6 +45,13 @@ export async function setSecretApiKeyContext( } ) + if (!apiKey) { + throw new MedusaError( + MedusaErrorTypes.NOT_FOUND, + `API key with id ${req.auth_context.actor_id} not found` + ) + } + req.secret_key_context = { created_by: apiKey.created_by, }