From 8a1089ce9d89ac4c61555b530afafca45c3239ad Mon Sep 17 00:00:00 2001 From: Igor Propisnov Date: Mon, 9 Sep 2024 09:18:15 +0200 Subject: [PATCH] delete tokens after 10 minutes --- .../src/cron/clear-expired-sesstions.cron.ts | 16 +++++++++- .../repositories/email-verify.repository.ts | 27 +++++++++++++++- .../services/email-verification.service.ts | 32 ++++++++++++++++--- backend/src/shared/exceptions/index.ts | 1 + .../exceptions/token-expired.exception.ts | 14 ++++++++ 5 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 backend/src/shared/exceptions/token-expired.exception.ts diff --git a/backend/src/cron/clear-expired-sesstions.cron.ts b/backend/src/cron/clear-expired-sesstions.cron.ts index 8d5a56e..6cc0e4a 100644 --- a/backend/src/cron/clear-expired-sesstions.cron.ts +++ b/backend/src/cron/clear-expired-sesstions.cron.ts @@ -1,12 +1,16 @@ import { Injectable, Logger } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { SessionService } from 'src/modules/session/services/session.service'; +import { EmailVerificationService } from 'src/modules/verify-module/services/email-verification.service'; @Injectable() export class ClearExpiredSessionsCron { private readonly logger: Logger = new Logger(ClearExpiredSessionsCron.name); - public constructor(private readonly sessionService: SessionService) {} + public constructor( + private readonly sessionService: SessionService, + private readonly emailVerificationService: EmailVerificationService + ) {} @Cron(CronExpression.EVERY_12_HOURS, { name: 'Clear-Expired-Sessions', @@ -17,4 +21,14 @@ export class ClearExpiredSessionsCron { this.sessionService.deleteAllExpiredSessions(); this.logger.log('-------------------------------------------'); } + + @Cron(CronExpression.EVERY_10_MINUTES, { + name: 'Clear-Expired-Tokens', + timeZone: 'Europe/Berlin', + }) + public handleClearExpiredTokens(): void { + this.logger.log('-Cronjob Executed: Delete-Expired-Tokens-'); + this.emailVerificationService.deleteAllExpiredTokens(); + this.logger.log('-------------------------------------------'); + } } diff --git a/backend/src/modules/verify-module/repositories/email-verify.repository.ts b/backend/src/modules/verify-module/repositories/email-verify.repository.ts index a87546a..f809161 100644 --- a/backend/src/modules/verify-module/repositories/email-verify.repository.ts +++ b/backend/src/modules/verify-module/repositories/email-verify.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { EmailVerification } from 'src/entities'; -import { Repository } from 'typeorm'; +import { LessThan, MoreThan, Repository } from 'typeorm'; @Injectable() export class EmailVerifyRepository { @@ -24,6 +24,23 @@ export class EmailVerifyRepository { }); } + public async findValidVerification( + token: string, + email: string + ): Promise { + const currentDate = new Date(); + const tenMinutesAgo = new Date(currentDate.getTime() - 10 * 60 * 1000); + + return await this.repository.findOne({ + where: { + token, + email, + createdAt: MoreThan(tenMinutesAgo), + expiresAt: MoreThan(currentDate), + }, + }); + } + public async findByTokenAndEmail( token: string, email: string @@ -49,6 +66,14 @@ export class EmailVerifyRepository { return this.repository.findOne({ where: { email } }); } + public async deleteAllExpiredTokens(): Promise { + const currentDate = new Date(); + + await this.repository.delete({ + expiresAt: LessThan(currentDate), + }); + } + public async deleteEmailVerificationByToken( tokenToDelete: string ): Promise { diff --git a/backend/src/modules/verify-module/services/email-verification.service.ts b/backend/src/modules/verify-module/services/email-verification.service.ts index dfbb8ac..1a7b603 100644 --- a/backend/src/modules/verify-module/services/email-verification.service.ts +++ b/backend/src/modules/verify-module/services/email-verification.service.ts @@ -4,7 +4,10 @@ import { Injectable } from '@nestjs/common'; import { EmailVerification } from 'src/entities'; import { SessionService } from 'src/modules/session/services/session.service'; import { SuccessDto, UriEncoderService } from 'src/shared'; -import { InternalServerErrorException } from 'src/shared/exceptions'; +import { + InternalServerErrorException, + TokenExpiredException, +} from 'src/shared/exceptions'; import { UserDataRepository } from '../../user-module/repositories/user-data.repository'; import { EmailVerifyRepository } from '../repositories'; @@ -59,11 +62,11 @@ export class EmailVerificationService { ): Promise { try { const verificationToken = await this.createVerificationToken(); - const expiration = new Date(Date.now() + 24 * 60 * 60 * 1000); + const expiresAt = new Date(Date.now() + 10 * 60 * 1000); await this.emailVerifyRepository.createEmailVerification( verificationToken, - expiration, + expiresAt, email, null ); @@ -85,13 +88,23 @@ export class EmailVerificationService { emailToVerify: string ): Promise { try { - const findTokenAndEmail: EmailVerification = - await this.emailVerifyRepository.findByTokenAndEmail( + const findTokenAndEmail: EmailVerification | null = + await this.emailVerifyRepository.findValidVerification( tokenToVerify, emailToVerify ); if (!findTokenAndEmail) { + const expiredToken = + await this.emailVerifyRepository.findByTokenAndEmail( + tokenToVerify, + emailToVerify + ); + + if (expiredToken) { + throw new TokenExpiredException(); + } + return { success: false }; } @@ -102,6 +115,9 @@ export class EmailVerificationService { return { success: true }; } catch (error) { + if (error instanceof TokenExpiredException) { + throw error; + } throw new InternalServerErrorException('EMAIL_VERIFICATION_ERROR', { message: 'An error occurred while verifying the email.', }); @@ -128,6 +144,12 @@ export class EmailVerificationService { } } + async deleteAllExpiredTokens(): Promise { + const currentDate = new Date(); + + await this.emailVerifyRepository.deleteAllExpiredTokens(); + } + private async createVerificationToken(): Promise { const verifyToken = randomBytes(32).toString('hex'); diff --git a/backend/src/shared/exceptions/index.ts b/backend/src/shared/exceptions/index.ts index c43a40a..f1bb424 100644 --- a/backend/src/shared/exceptions/index.ts +++ b/backend/src/shared/exceptions/index.ts @@ -2,3 +2,4 @@ export * from './conflict.exception'; export * from './forbidden.exception'; export * from './internal-server-error.exception'; export * from './not-found.exception'; +export * from './token-expired.exception'; diff --git a/backend/src/shared/exceptions/token-expired.exception.ts b/backend/src/shared/exceptions/token-expired.exception.ts new file mode 100644 index 0000000..82542eb --- /dev/null +++ b/backend/src/shared/exceptions/token-expired.exception.ts @@ -0,0 +1,14 @@ +import { HttpStatus } from '@nestjs/common'; + +import { BaseException } from './base.exception'; + +export class TokenExpiredException extends BaseException { + public constructor(details?: unknown) { + super( + 'The verification token has expired. Please request a new one.', + HttpStatus.BAD_REQUEST, + 'TOKEN_EXPIRED', + details + ); + } +}