Sfoglia il codice sorgente

fix(core): Update login credentials when changing customer email address

Fixes #1071
Michael Bromley 4 anni fa
parent
commit
1ebc872aaa

+ 11 - 0
packages/core/e2e/customer.e2e-spec.ts

@@ -32,6 +32,7 @@ import {
     GetCustomerList,
     GetCustomerOrders,
     GetCustomerWithUser,
+    Me,
     UpdateAddress,
     UpdateCustomer,
     UpdateCustomerNote,
@@ -45,6 +46,7 @@ import {
     GET_CUSTOMER,
     GET_CUSTOMER_HISTORY,
     GET_CUSTOMER_LIST,
+    ME,
     UPDATE_ADDRESS,
     UPDATE_CUSTOMER,
     UPDATE_CUSTOMER_NOTE,
@@ -64,6 +66,7 @@ let sendEmailFn: jest.Mock;
 })
 class TestEmailPlugin implements OnModuleInit {
     constructor(private eventBus: EventBus) {}
+
     onModuleInit() {
         this.eventBus.ofType(AccountRegistrationEvent).subscribe(event => {
             sendEmailFn(event);
@@ -483,6 +486,14 @@ describe('Customer resolver', () => {
 
             expect(updateCustomer.emailAddress).toBe('unique-email@test.com');
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/1071
+        it('updates the associated User email address', async () => {
+            await shopClient.asUserWithCredentials('unique-email@test.com', 'test');
+            const { me } = await shopClient.query<Me.Query>(ME);
+
+            expect(me?.identifier).toBe('unique-email@test.com');
+        });
     });
 
     describe('deletion', () => {

+ 19 - 3
packages/core/src/service/services/customer.service.ts

@@ -246,6 +246,10 @@ export class CustomerService {
         const hasEmailAddress = (i: any): i is UpdateCustomerInput & { emailAddress: string } =>
             Object.hasOwnProperty.call(i, 'emailAddress');
 
+        const customer = await this.connection.getEntityOrThrow(ctx, Customer, input.id, {
+            channelId: ctx.channelId,
+        });
+
         if (hasEmailAddress(input)) {
             const existingCustomerInChannel = await this.connection
                 .getRepository(ctx, Customer)
@@ -260,11 +264,23 @@ export class CustomerService {
             if (existingCustomerInChannel) {
                 return new EmailAddressConflictAdminError();
             }
+
+            if (customer.user) {
+                const existingUserWithEmailAddress = await this.userService.getUserByEmailAddress(
+                    ctx,
+                    input.emailAddress,
+                );
+
+                if (
+                    existingUserWithEmailAddress &&
+                    !idsAreEqual(customer.user.id, existingUserWithEmailAddress.id)
+                ) {
+                    return new EmailAddressConflictAdminError();
+                }
+                await this.userService.changeNativeIdentifier(ctx, customer.user.id, input.emailAddress);
+            }
         }
 
-        const customer = await this.connection.getEntityOrThrow(ctx, Customer, input.id, {
-            channelId: ctx.channelId,
-        });
         const updatedCustomer = patchEntity(customer, input);
         await this.connection.getRepository(ctx, Customer).save(updatedCustomer, { reload: false });
         await this.customFieldRelationService.updateRelations(ctx, Customer, input, updatedCustomer);

+ 28 - 2
packages/core/src/service/services/user.service.ts

@@ -80,7 +80,8 @@ export class UserService {
         }
         const authenticationMethod = new NativeAuthenticationMethod();
         if (this.configService.authOptions.requireVerification) {
-            authenticationMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
+            authenticationMethod.verificationToken =
+                this.verificationTokenGenerator.generateVerificationToken();
             user.verified = false;
         } else {
             user.verified = true;
@@ -169,7 +170,8 @@ export class UserService {
             return;
         }
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
-        nativeAuthMethod.passwordResetToken = await this.verificationTokenGenerator.generateVerificationToken();
+        nativeAuthMethod.passwordResetToken =
+            await this.verificationTokenGenerator.generateVerificationToken();
         await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
         return user;
     }
@@ -199,6 +201,30 @@ export class UserService {
         }
     }
 
+    /**
+     * Changes the User identifier without an email verification step, so this should be only used when
+     * an Administrator is setting a new email address.
+     */
+    async changeNativeIdentifier(ctx: RequestContext, userId: ID, newIdentifier: string) {
+        const user = await this.getUserById(ctx, userId);
+        if (!user) {
+            return;
+        }
+        const nativeAuthMethod = user.getNativeAuthenticationMethod();
+        user.identifier = newIdentifier;
+        nativeAuthMethod.identifier = newIdentifier;
+        nativeAuthMethod.identifierChangeToken = null;
+        nativeAuthMethod.pendingIdentifier = null;
+        await this.connection
+            .getRepository(ctx, NativeAuthenticationMethod)
+            .save(nativeAuthMethod, { reload: false });
+        await this.connection.getRepository(ctx, User).save(user, { reload: false });
+    }
+
+    /**
+     * Changes the User identifier as part of the storefront flow used by Customers to set a
+     * new email address.
+     */
     async changeIdentifierByToken(
         ctx: RequestContext,
         token: string,