diff --git a/src/email-verification/email-verification-sending-limit.service.ts b/src/email-verification/email-verification-sending-limit.service.ts index 95582f0..5b9f21a 100644 --- a/src/email-verification/email-verification-sending-limit.service.ts +++ b/src/email-verification/email-verification-sending-limit.service.ts @@ -37,7 +37,10 @@ export class EmailVerificationSendingLimitService { private async getSendingAttemptsCount(senderIp: string, email: string): Promise { const timeThreshold = new Date(Date.now() - this.lifetimeMilliseconds) const sendingAttemptsCount = await this.sendingAttemptRepository.count({ - where: {senderIp, createdAt: MoreThan(timeThreshold)}, + where: [ + {senderIp, createdAt: MoreThan(timeThreshold)}, + {email, createdAt: MoreThan(timeThreshold)}, + ], take: this.maxSendingAttempts, cache: false, }) diff --git a/src/interceptors/timeout.interceptor.spec.ts b/src/interceptors/timeout.interceptor.spec.ts deleted file mode 100644 index 711c039..0000000 --- a/src/interceptors/timeout.interceptor.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import {Test, TestingModule} from '@nestjs/testing' -import {TimeoutInterceptor} from './timeout.interceptor' -import {RequestTimeoutException} from '@nestjs/common' -import {Observable, of, throwError, TimeoutError} from 'rxjs' -import {ServerConfigService} from '../config/server/server.config.service' -import {ConfigModule} from '@nestjs/config' -import {configModuleOptions} from '../config/config-module.options' - -describe('TimeoutInterceptor', () => { - let interceptor: TimeoutInterceptor - let module: TestingModule - - beforeEach(async () => { - module = await Test.createTestingModule({ - imports: [ConfigModule.forRoot(configModuleOptions)], - providers: [TimeoutInterceptor, ServerConfigService], - }).compile() - - interceptor = module.get(TimeoutInterceptor) - }) - - it('should handle the request without timing out', done => { - const mockCallHandler = {handle: () => of('Response')} - - interceptor.intercept({} as any, mockCallHandler).subscribe(result => { - expect(result).toEqual('Response') - done() - }) - }) - - it('should handle the request with a timeout error', done => { - const mockCallHandler = {handle: () => throwError(new TimeoutError())} - - interceptor.intercept({} as any, mockCallHandler as any).subscribe({ - error: err => { - expect(err).toBeInstanceOf(RequestTimeoutException) - done() - }, - }) - }) - - it('should handle other errors without timing out', done => { - const error = new Error('Some other error') - const mockCallHandler = {handle: () => throwError(error)} - - interceptor.intercept({} as any, mockCallHandler as any).subscribe({ - error: err => { - expect(err).toBe(error) - done() - }, - }) - }) - - it('should handle a timeout in the controller', done => { - const serverConfigService = module.get(ServerConfigService) - jest.spyOn(serverConfigService, 'requestTimeoutMs', 'get').mockReturnValue(1) - const mockCallHandler = { - handle: () => { - return new Observable(observer => { - setTimeout(() => { - observer.error(new Error('Timeout occurred')) - }, serverConfigService.requestTimeoutMs + 1) - }) - }, - } - - interceptor.intercept({} as any, mockCallHandler).subscribe({ - error: err => { - expect(err).toBeInstanceOf(RequestTimeoutException) - done() - }, - }) - }) -}) diff --git a/test/email-verification/gql-send-verification-code-to-email.e2e-spec.ts b/test/email-verification/gql-send-verification-code-to-email.e2e-spec.ts index 854a5c0..0a14da4 100644 --- a/test/email-verification/gql-send-verification-code-to-email.e2e-spec.ts +++ b/test/email-verification/gql-send-verification-code-to-email.e2e-spec.ts @@ -130,7 +130,7 @@ describe('sendVerificationCodeToEmail', () => { }) }) - it('should throw an error if sending attempts limit exceeded', async () => { + it('should throw an error if sending attempts limit exceeded with same ip', async () => { const emailVerificationCodeSendingAttemptRepository = app.get( getRepositoryToken(EmailVerificationCodeSendingAttempt), ) @@ -175,6 +175,96 @@ describe('sendVerificationCodeToEmail', () => { expect(emailVerificationCodeSendingAttempts).toEqual([attempt1, attempt2, attempt3]) }) + it('should throw an error if sending attempts limit exceeded with same email', async () => { + const email = 'test@test.com' + + const emailVerificationCodeSendingAttemptRepository = app.get( + getRepositoryToken(EmailVerificationCodeSendingAttempt), + ) + const attempt1 = await emailVerificationCodeSendingAttemptRepository.save({ + email, + senderIp: '::ffff:127.0.0.2', + }) + const attempt2 = await emailVerificationCodeSendingAttemptRepository.save({ + email, + senderIp: '::ffff:127.0.0.3', + }) + const attempt3 = await emailVerificationCodeSendingAttemptRepository.save({ + email, + senderIp: '::ffff:127.0.0.4', + }) + + const result = await gqlService.sendRequest({ + queryType: 'mutation', + query: { + operation: 'sendVerificationCodeToEmail', + variables: {email: {type: 'String!', value: email}}, + }, + }) + + expect(result.body).toEqual({ + errors: [ + { + message: + 'You have exceeded the limit of email verification requests for the last 10 minutes.', + locations: [{line: 2, column: 7}], + path: ['sendVerificationCodeToEmail'], + code: 'FORBIDDEN', + }, + ], + data: null, + }) + + const emailVerificationCodeSendingAttempts = + await emailVerificationCodeSendingAttemptRepository.find({order: {createdAt: 'ASC'}}) + expect(emailVerificationCodeSendingAttempts).toHaveLength(3) + expect(emailVerificationCodeSendingAttempts).toEqual([attempt1, attempt2, attempt3]) + }) + + it('should throw an error if sending attempts limit exceeded with same email and ip', async () => { + const emailVerificationCodeSendingAttemptRepository = app.get( + getRepositoryToken(EmailVerificationCodeSendingAttempt), + ) + const attempt1 = await emailVerificationCodeSendingAttemptRepository.save({ + email: 'test@test.com', + senderIp: '::ffff:127.0.0.1', + }) + const attempt2 = await emailVerificationCodeSendingAttemptRepository.save({ + email: 'test@test.com', + senderIp: '::ffff:127.0.0.2', + }) + const attempt3 = await emailVerificationCodeSendingAttemptRepository.save({ + email: 'test2@test2.com', + senderIp: '::ffff:127.0.0.1', + }) + + const result = await gqlService.sendRequest({ + queryType: 'mutation', + query: { + operation: 'sendVerificationCodeToEmail', + variables: {email: {type: 'String!', value: 'test@test.com'}}, + }, + }) + + expect(result.body).toEqual({ + errors: [ + { + message: + 'You have exceeded the limit of email verification requests for the last 10 minutes.', + locations: [{line: 2, column: 7}], + path: ['sendVerificationCodeToEmail'], + code: 'FORBIDDEN', + }, + ], + data: null, + }) + + const emailVerificationCodeSendingAttempts = + await emailVerificationCodeSendingAttemptRepository.find({order: {createdAt: 'ASC'}}) + expect(emailVerificationCodeSendingAttempts).toHaveLength(3) + expect(emailVerificationCodeSendingAttempts).toEqual([attempt1, attempt2, attempt3]) + }) + it('should throw InternalServerError', async () => { const emailVerificationSendingLimitService = app.get(EmailVerificationSendingLimitService) jest diff --git a/test/email-verification/gql-verify-email-code.e2e-spec.ts b/test/email-verification/gql-verify-email-code.e2e-spec.ts index 1320a57..16231bf 100644 --- a/test/email-verification/gql-verify-email-code.e2e-spec.ts +++ b/test/email-verification/gql-verify-email-code.e2e-spec.ts @@ -890,11 +890,6 @@ describe('VerifyEmailCode', () => { ) jest .spyOn(inputLimitService, 'enforceEmailVerificationInputLimit') - .mockImplementation(async () => {}) - - const emailVerificationService = app.get(EmailVerificationService) - jest - .spyOn(emailVerificationService, 'verifyEmail') .mockImplementation(() => new Promise(resolve => setTimeout(resolve, 1))) const result = await gqlService.sendRequest({ @@ -1018,6 +1013,7 @@ describe('VerifyEmailCode', () => { app.get(getRepositoryToken(EmailVerificationCodeInputAttempt)) const emailVerificationCodeInputAttempts = await emailVerificationCodeInputAttemptRepository.find() + expect(emailVerificationCodeInputAttempts.length).toEqual(1) expect(emailVerificationCodeInputAttempts).toEqual([ { id: expect.any(String),