diff --git a/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.spec.ts b/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.spec.ts index a8df4f600..ae5ee3302 100644 --- a/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.spec.ts +++ b/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.spec.ts @@ -65,7 +65,7 @@ describe('AcceptSharedSubscriptionInvitation', () => { inviteeSubscription = { endsAt: 3, planName: SubscriptionName.PlusPlan } as jest.Mocked - inviterSubscription = { endsAt: 3, planName: SubscriptionName.PlusPlan } as jest.Mocked + inviterSubscription = { endsAt: 3, planName: SubscriptionName.PlusPlan, userUuid: '456' } as jest.Mocked userSubscriptionRepository = {} as jest.Mocked userSubscriptionRepository.findBySubscriptionIdAndType = jest.fn().mockReturnValue([inviterSubscription]) @@ -114,8 +114,8 @@ describe('AcceptSharedSubscriptionInvitation', () => { }) it('should create a shared subscription upon accepting the invitation if inviter has a second subscription', async () => { - const inviterSubscription1 = { endsAt: 1, planName: SubscriptionName.PlusPlan } as jest.Mocked - const inviterSubscription2 = { endsAt: 5, planName: SubscriptionName.PlusPlan } as jest.Mocked + const inviterSubscription1 = { endsAt: 1, planName: SubscriptionName.PlusPlan, userUuid: '456' } as jest.Mocked + const inviterSubscription2 = { endsAt: 5, planName: SubscriptionName.PlusPlan, userUuid: '456' } as jest.Mocked timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(3) @@ -208,6 +208,25 @@ describe('AcceptSharedSubscriptionInvitation', () => { expect(applyDefaultSubscriptionSettings.execute).not.toHaveBeenCalled() }) + it('should not create a shared subscription if invitee is the inviter', async () => { + inviterSubscription = { endsAt: 3, planName: SubscriptionName.PlusPlan, userUuid: '123' } as jest.Mocked + userSubscriptionRepository.findBySubscriptionIdAndType = jest.fn().mockReturnValue([inviterSubscription]) + + expect( + await createUseCase().execute({ + sharedSubscriptionInvitationUuid: '1-2-3', + }), + ).toEqual({ + success: false, + message: 'You cannot accept a subscription invitation sent to yourself.', + }) + + expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled() + expect(userSubscriptionRepository.save).not.toHaveBeenCalled() + expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled() + expect(applyDefaultSubscriptionSettings.execute).not.toHaveBeenCalled() + }) + it('should not create a shared subscription if inviter subscription is not found', async () => { userSubscriptionRepository.findBySubscriptionIdAndType = jest.fn().mockReturnValue([]) expect( @@ -226,8 +245,8 @@ describe('AcceptSharedSubscriptionInvitation', () => { }) it('should not create a shared subscription if inviter subscriptions are not active', async () => { - const inviterSubscription1 = { endsAt: 1, planName: SubscriptionName.PlusPlan } as jest.Mocked - const inviterSubscription2 = { endsAt: 2, planName: SubscriptionName.PlusPlan } as jest.Mocked + const inviterSubscription1 = { endsAt: 1, planName: SubscriptionName.PlusPlan, userUuid: '456' } as jest.Mocked + const inviterSubscription2 = { endsAt: 2, planName: SubscriptionName.PlusPlan, userUuid: '456' } as jest.Mocked timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(3) diff --git a/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.ts b/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.ts index 70af840e5..a4ac05a0d 100644 --- a/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.ts +++ b/packages/auth/src/Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation.ts @@ -74,6 +74,13 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface { } const inviterUserSubscription = activeUserSubscriptions[0] + if (invitee.uuid === inviterUserSubscription.userUuid) { + return { + success: false, + message: 'You cannot accept a subscription invitation sent to yourself.', + } + } + sharedSubscriptionInvitation.status = InvitationStatus.Accepted sharedSubscriptionInvitation.updatedAt = this.timer.getTimestampInMicroseconds() diff --git a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts index e97871cf5..4ba150f48 100644 --- a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts +++ b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.spec.ts @@ -58,6 +58,38 @@ describe('InviteToSharedSubscription', () => { domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked) }) + it('should not create an invitation if user invites themselves', async () => { + expect( + await createUseCase().execute({ + inviteeIdentifier: 'inviter@test.te', + inviterUuid: '1-2-3', + inviterEmail: 'inviter@test.te', + inviterRoles: [RoleName.NAMES.ProUser], + }), + ).toEqual({ + success: false, + }) + + expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled() + expect(domainEventFactory.createSharedSubscriptionInvitationCreatedEvent).not.toHaveBeenCalled() + expect(domainEventPublisher.publish).not.toHaveBeenCalled() + }) + + it('should not create an inivitation if user invites themselves with different email casing', async () => { + expect( + await createUseCase().execute({ + inviteeIdentifier: 'Inviter@Test.TE', + inviterUuid: '1-2-3', + inviterEmail: 'inviter@test.te', + inviterRoles: [RoleName.NAMES.ProUser], + }), + ).toEqual({ + success: false, + }) + + expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled() + }) + it('should not create an inivitation for sharing the subscription if inviter has no subscription', async () => { userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue(null) diff --git a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts index 77c8edbc6..2952e3632 100644 --- a/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts +++ b/packages/auth/src/Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription.ts @@ -45,6 +45,16 @@ export class InviteToSharedSubscription implements UseCaseInterface { } } + const inviteeIdentifierType = this.isInviteeIdentifierPotentiallyAPrivateUsernameAccount(dto.inviteeIdentifier) + ? InviteeIdentifierType.Hash + : InviteeIdentifierType.Email + + if (this.isSelfInvite(dto.inviteeIdentifier, dto.inviterEmail, inviteeIdentifierType)) { + return { + success: false, + } + } + const numberOfUsedInvites = await this.sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus( dto.inviterEmail, [InvitationStatus.Sent, InvitationStatus.Accepted], @@ -69,11 +79,7 @@ export class InviteToSharedSubscription implements UseCaseInterface { sharedSubscriptionInvition.inviterIdentifier = dto.inviterEmail sharedSubscriptionInvition.inviterIdentifierType = InviterIdentifierType.Email sharedSubscriptionInvition.inviteeIdentifier = dto.inviteeIdentifier - sharedSubscriptionInvition.inviteeIdentifierType = this.isInviteeIdentifierPotentiallyAPrivateUsernameAccount( - dto.inviteeIdentifier, - ) - ? InviteeIdentifierType.Hash - : InviteeIdentifierType.Email + sharedSubscriptionInvition.inviteeIdentifierType = inviteeIdentifierType sharedSubscriptionInvition.status = InvitationStatus.Sent sharedSubscriptionInvition.subscriptionId = inviterUserSubscription.subscriptionId as number sharedSubscriptionInvition.createdAt = this.timer.getTimestampInMicroseconds() @@ -110,4 +116,16 @@ export class InviteToSharedSubscription implements UseCaseInterface { private isInviteeIdentifierPotentiallyAPrivateUsernameAccount(identifier: string): boolean { return identifier.length === 64 && !identifier.includes('@') } + + private isSelfInvite( + inviteeIdentifier: string, + inviterEmail: string, + inviteeIdentifierType: InviteeIdentifierType, + ): boolean { + if (inviteeIdentifierType !== InviteeIdentifierType.Email) { + return false + } + + return inviteeIdentifier.trim().toLowerCase() === inviterEmail.trim().toLowerCase() + } }