From bd4df4b06b967f0dc25bfffe78adf3f25c2cbed1 Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Wed, 30 Sep 2026 01:17:59 -0300 Subject: [PATCH] fix: Updates subscription invites after email change --- packages/auth/src/Bootstrap/Container.ts | 1 + ...bscriptionInvitationRepositoryInterface.ts | 2 ++ .../ChangeCredentials.spec.ts | 20 ++++++++++++++ .../ChangeCredentials/ChangeCredentials.ts | 11 ++++++++ ...MSharedSubscriptionInvitationRepository.ts | 26 +++++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/packages/auth/src/Bootstrap/Container.ts b/packages/auth/src/Bootstrap/Container.ts index a2176aaf1..4d6e1ac24 100644 --- a/packages/auth/src/Bootstrap/Container.ts +++ b/packages/auth/src/Bootstrap/Container.ts @@ -1299,6 +1299,7 @@ export class ContainerConfigLoader { container.get(TYPES.Auth_DomainEventFactory), container.get(TYPES.Auth_Timer), container.get(TYPES.Auth_DeleteOtherSessionsForUser), + container.get(TYPES.Auth_SharedSubscriptionInvitationRepository), container.get(TYPES.Auth_Logger), ), ) diff --git a/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts b/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts index 3d48e3a24..b6e4f8665 100644 --- a/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts +++ b/packages/auth/src/Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface.ts @@ -11,4 +11,6 @@ export interface SharedSubscriptionInvitationRepositoryInterface { inviterEmail: string, ): Promise countByInviterEmailAndStatus(inviterEmail: string, statuses: InvitationStatus[]): Promise + updateInviterIdentifierEmail(fromEmail: string, toEmail: string, updatedAt: number): Promise + updateInviteeIdentifierEmail(fromEmail: string, toEmail: string, updatedAt: number): Promise } diff --git a/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.spec.ts b/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.spec.ts index 9d64145b3..35319c9bf 100644 --- a/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.spec.ts +++ b/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.spec.ts @@ -16,6 +16,7 @@ import { DeleteOtherSessionsForUser } from '../DeleteOtherSessionsForUser' import { ApiVersion } from '../../Api/ApiVersion' import { Session } from '../../Session/Session' import { Logger } from 'winston' +import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface' describe('ChangeCredentials', () => { let userRepository: UserRepositoryInterface @@ -26,6 +27,7 @@ describe('ChangeCredentials', () => { let timer: TimerInterface let user: User let deleteOtherSessionsForUser: DeleteOtherSessionsForUser + let sharedSubscriptionInvitationRepository: SharedSubscriptionInvitationRepositoryInterface let logger: Logger const createUseCase = () => @@ -36,6 +38,7 @@ describe('ChangeCredentials', () => { domainEventFactory, timer, deleteOtherSessionsForUser, + sharedSubscriptionInvitationRepository, logger, ) @@ -68,9 +71,14 @@ describe('ChangeCredentials', () => { timer = {} as jest.Mocked timer.getUTCDate = jest.fn().mockReturnValue(new Date(1)) + timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(2) deleteOtherSessionsForUser = {} as jest.Mocked deleteOtherSessionsForUser.execute = jest.fn().mockReturnValue(Result.ok()) + + sharedSubscriptionInvitationRepository = {} as jest.Mocked + sharedSubscriptionInvitationRepository.updateInviterIdentifierEmail = jest.fn() + sharedSubscriptionInvitationRepository.updateInviteeIdentifierEmail = jest.fn() }) it('should change password', async () => { @@ -176,6 +184,16 @@ describe('ChangeCredentials', () => { updatedAt: new Date(1), }) expect(domainEventFactory.createUserEmailChangedEvent).toHaveBeenCalledWith('1-2-3', 'test@test.te', 'new@test.te') + expect(sharedSubscriptionInvitationRepository.updateInviterIdentifierEmail).toHaveBeenCalledWith( + 'test@test.te', + 'new@test.te', + 2, + ) + expect(sharedSubscriptionInvitationRepository.updateInviteeIdentifierEmail).toHaveBeenCalledWith( + 'test@test.te', + 'new@test.te', + 2, + ) expect(domainEventPublisher.publish).toHaveBeenCalled() expect(deleteOtherSessionsForUser.execute).toHaveBeenCalled() }) @@ -202,6 +220,8 @@ describe('ChangeCredentials', () => { expect(userRepository.save).not.toHaveBeenCalled() expect(domainEventFactory.createUserEmailChangedEvent).not.toHaveBeenCalled() + expect(sharedSubscriptionInvitationRepository.updateInviterIdentifierEmail).not.toHaveBeenCalled() + expect(sharedSubscriptionInvitationRepository.updateInviteeIdentifierEmail).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) diff --git a/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.ts b/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.ts index 3c22b2471..885f4c592 100644 --- a/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.ts +++ b/packages/auth/src/Domain/UseCase/ChangeCredentials/ChangeCredentials.ts @@ -14,6 +14,7 @@ import { getBody, getSubject } from '../../Email/UserEmailChanged' import { Logger } from 'winston' import { AuthResponseCreationResult } from '../../Auth/AuthResponseCreationResult' import { ApiVersion } from '../../Api/ApiVersion' +import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface' export class ChangeCredentials implements UseCaseInterface { constructor( @@ -23,6 +24,7 @@ export class ChangeCredentials implements UseCaseInterface { + const updatedAt = this.timer.getTimestampInMicroseconds() + + await this.sharedSubscriptionInvitationRepository.updateInviterIdentifierEmail(fromEmail, toEmail, updatedAt) + await this.sharedSubscriptionInvitationRepository.updateInviteeIdentifierEmail(fromEmail, toEmail, updatedAt) + } + private async deleteOtherSessionsForUserIfNeeded( userUuid: string, session: Session, diff --git a/packages/auth/src/Infra/TypeORM/TypeORMSharedSubscriptionInvitationRepository.ts b/packages/auth/src/Infra/TypeORM/TypeORMSharedSubscriptionInvitationRepository.ts index 47d1812a4..434a21918 100644 --- a/packages/auth/src/Infra/TypeORM/TypeORMSharedSubscriptionInvitationRepository.ts +++ b/packages/auth/src/Infra/TypeORM/TypeORMSharedSubscriptionInvitationRepository.ts @@ -3,6 +3,8 @@ import { Repository } from 'typeorm' import TYPES from '../../Bootstrap/Types' import { InvitationStatus } from '../../Domain/SharedSubscription/InvitationStatus' +import { InviteeIdentifierType } from '../../Domain/SharedSubscription/InviteeIdentifierType' +import { InviterIdentifierType } from '../../Domain/SharedSubscription/InviterIdentifierType' import { SharedSubscriptionInvitation } from '../../Domain/SharedSubscription/SharedSubscriptionInvitation' import { SharedSubscriptionInvitationRepositoryInterface } from '../../Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface' @@ -67,4 +69,28 @@ export class TypeORMSharedSubscriptionInvitationRepository implements SharedSubs }) .getOne() } + + async updateInviterIdentifierEmail(fromEmail: string, toEmail: string, updatedAt: number): Promise { + await this.ormRepository + .createQueryBuilder() + .update(SharedSubscriptionInvitation) + .set({ inviterIdentifier: toEmail, updatedAt }) + .where('inviter_identifier = :fromEmail AND inviter_identifier_type = :inviterType', { + fromEmail, + inviterType: InviterIdentifierType.Email, + }) + .execute() + } + + async updateInviteeIdentifierEmail(fromEmail: string, toEmail: string, updatedAt: number): Promise { + await this.ormRepository + .createQueryBuilder() + .update(SharedSubscriptionInvitation) + .set({ inviteeIdentifier: toEmail, updatedAt }) + .where('invitee_identifier = :fromEmail AND invitee_identifier_type = :inviteeType', { + fromEmail, + inviteeType: InviteeIdentifierType.Email, + }) + .execute() + } }