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 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..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 @@ -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).then(user => 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 use the secret key linked user to set created_by", 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", () => { 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..902bd1233fb45 --- /dev/null +++ b/packages/core/framework/src/http/middlewares/set-secret-api-key-context.ts @@ -0,0 +1,59 @@ +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) { + return 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, + }, + } + ) + + 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, + } + 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/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, }, }) 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,