diff --git a/.changeset/in-store-token-global-customer.md b/.changeset/in-store-token-global-customer.md new file mode 100644 index 00000000..d3e3e82b --- /dev/null +++ b/.changeset/in-store-token-global-customer.md @@ -0,0 +1,5 @@ +--- +"@labdigital/commercetools-mock": patch +--- + +Issue an in-store password token to a global customer. A customer assigned to no store can sign in to any store again; a customer assigned only to other stores is still refused. diff --git a/src/oauth/server.test.ts b/src/oauth/server.test.ts index ce845b32..a2268d7a 100644 --- a/src/oauth/server.test.ts +++ b/src/oauth/server.test.ts @@ -222,5 +222,54 @@ describe("OAuth2Server", () => { token_type: "Bearer", }); }); + + const addCustomer = (projectKey: string, stores: string[]) => + storage.add(projectKey, "customer", { + ...getBaseResourceProperties(), + email: "j.doe@example.org", + password: hashPassword("password"), + addresses: [], + authenticationMode: "password", + isEmailVerified: true, + stores: stores.map((key) => ({ typeId: "store" as const, key })), + shippingAddressIds: [], + billingAddressIds: [], + customerGroupAssignments: [], + }); + + const requestToken = (projectKey: string, storeKey: string) => + app.inject({ + method: "POST", + url: `/${projectKey}/in-store/key=${storeKey}/customers/token?${new URLSearchParams( + { + grant_type: "password", + username: "j.doe@example.org", + password: "password", + scope: `${projectKey}:manage_my_profile`, + }, + )}`, + headers: { + Authorization: `Basic ${Buffer.from("validClientId:validClientSecret").toString("base64")}`, + }, + }); + + it("should return a token for a global customer", async () => { + addCustomer("test-project", []); + + const response = await requestToken("test-project", "test-store"); + + expect(response.statusCode).toBe(200); + }); + + it("should refuse a customer assigned to another store", async () => { + addCustomer("test-project", ["other-store"]); + + const response = await requestToken("test-project", "test-store"); + + expect(response.statusCode).toBe(400); + expect(response.json().errors[0].code).toBe( + "invalid_customer_account_credentials", + ); + }); }); }); diff --git a/src/oauth/server.ts b/src/oauth/server.ts index b7824daa..46e67c43 100644 --- a/src/oauth/server.ts +++ b/src/oauth/server.ts @@ -313,11 +313,13 @@ export class OAuth2Server { }, ); - // Emails are only unique per store, so make sure we only sign in the - // customer that is actually assigned to this store. - const customer = result.results.find((c) => - c.stores?.some((store) => store.key === storeKey), - ); + // Emails are only unique per store, so the customer assigned to this + // store wins. A global customer, assigned to no store, may sign in to + // any store; one assigned to other stores only may not. + const customer = + result.results.find((c) => + c.stores?.some((store) => store.key === storeKey), + ) ?? result.results.find((c) => !c.stores?.length); if (!customer) { throw new CommercetoolsError(