fix: Updates subscription invites after email change

This commit is contained in:
Antonella Sgarlatta
2026-10-05 14:33:34 -03:00
committed by Antonella Sgarlatta
parent 639509362a
commit bd4df4b06b
5 changed files with 60 additions and 0 deletions
+1
View File
@@ -1299,6 +1299,7 @@ export class ContainerConfigLoader {
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<TimerInterface>(TYPES.Auth_Timer),
container.get<DeleteOtherSessionsForUser>(TYPES.Auth_DeleteOtherSessionsForUser),
container.get<SharedSubscriptionInvitationRepositoryInterface>(TYPES.Auth_SharedSubscriptionInvitationRepository),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
@@ -11,4 +11,6 @@ export interface SharedSubscriptionInvitationRepositoryInterface {
inviterEmail: string,
): Promise<SharedSubscriptionInvitation | null>
countByInviterEmailAndStatus(inviterEmail: string, statuses: InvitationStatus[]): Promise<number>
updateInviterIdentifierEmail(fromEmail: string, toEmail: string, updatedAt: number): Promise<void>
updateInviteeIdentifierEmail(fromEmail: string, toEmail: string, updatedAt: number): Promise<void>
}
@@ -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<TimerInterface>
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(2)
deleteOtherSessionsForUser = {} as jest.Mocked<DeleteOtherSessionsForUser>
deleteOtherSessionsForUser.execute = jest.fn().mockReturnValue(Result.ok())
sharedSubscriptionInvitationRepository = {} as jest.Mocked<SharedSubscriptionInvitationRepositoryInterface>
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', '[email protected]', '[email protected]')
expect(sharedSubscriptionInvitationRepository.updateInviterIdentifierEmail).toHaveBeenCalledWith(
'[email protected]',
'[email protected]',
2,
)
expect(sharedSubscriptionInvitationRepository.updateInviteeIdentifierEmail).toHaveBeenCalledWith(
'[email protected]',
'[email protected]',
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()
})
@@ -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<AuthResponseCreationResult> {
constructor(
@@ -23,6 +24,7 @@ export class ChangeCredentials implements UseCaseInterface<AuthResponseCreationR
private domainEventFactory: DomainEventFactoryInterface,
private timer: TimerInterface,
private deleteOtherSessionsForUserUseCase: DeleteOtherSessionsForUser,
private sharedSubscriptionInvitationRepository: SharedSubscriptionInvitationRepositoryInterface,
private logger: Logger,
) {}
@@ -82,6 +84,8 @@ export class ChangeCredentials implements UseCaseInterface<AuthResponseCreationR
const updatedUser = await this.userRepository.save(user)
if (userEmailChangedEvent !== undefined) {
await this.syncSharedSubscriptionInvitationsAfterEmailChange(existingEmailAddress, updatedUser.email)
await this.domainEventPublisher.publish(userEmailChangedEvent)
await this.sendEmailChangedNotification(existingEmailAddress, updatedUser.email)
@@ -106,6 +110,13 @@ export class ChangeCredentials implements UseCaseInterface<AuthResponseCreationR
return Result.ok(authResponse)
}
private async syncSharedSubscriptionInvitationsAfterEmailChange(fromEmail: string, toEmail: string): Promise<void> {
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,
@@ -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<void> {
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<void> {
await this.ormRepository
.createQueryBuilder()
.update(SharedSubscriptionInvitation)
.set({ inviteeIdentifier: toEmail, updatedAt })
.where('invitee_identifier = :fromEmail AND invitee_identifier_type = :inviteeType', {
fromEmail,
inviteeType: InviteeIdentifierType.Email,
})
.execute()
}
}