feature/refactor-login #19
|
@ -1,12 +1,16 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||||
import { SessionService } from 'src/modules/session/services/session.service';
|
import { SessionService } from 'src/modules/session/services/session.service';
|
||||||
|
import { EmailVerificationService } from 'src/modules/verify-module/services/email-verification.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClearExpiredSessionsCron {
|
export class ClearExpiredSessionsCron {
|
||||||
private readonly logger: Logger = new Logger(ClearExpiredSessionsCron.name);
|
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, {
|
@Cron(CronExpression.EVERY_12_HOURS, {
|
||||||
name: 'Clear-Expired-Sessions',
|
name: 'Clear-Expired-Sessions',
|
||||||
|
@ -17,4 +21,14 @@ export class ClearExpiredSessionsCron {
|
||||||
this.sessionService.deleteAllExpiredSessions();
|
this.sessionService.deleteAllExpiredSessions();
|
||||||
this.logger.log('-------------------------------------------');
|
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('-------------------------------------------');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { EmailVerification } from 'src/entities';
|
import { EmailVerification } from 'src/entities';
|
||||||
import { Repository } from 'typeorm';
|
import { LessThan, MoreThan, Repository } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailVerifyRepository {
|
export class EmailVerifyRepository {
|
||||||
|
@ -24,6 +24,23 @@ export class EmailVerifyRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findValidVerification(
|
||||||
|
token: string,
|
||||||
|
email: string
|
||||||
|
): Promise<EmailVerification | undefined> {
|
||||||
|
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(
|
public async findByTokenAndEmail(
|
||||||
token: string,
|
token: string,
|
||||||
email: string
|
email: string
|
||||||
|
@ -49,6 +66,14 @@ export class EmailVerifyRepository {
|
||||||
return this.repository.findOne({ where: { email } });
|
return this.repository.findOne({ where: { email } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteAllExpiredTokens(): Promise<void> {
|
||||||
|
const currentDate = new Date();
|
||||||
|
|
||||||
|
await this.repository.delete({
|
||||||
|
expiresAt: LessThan(currentDate),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async deleteEmailVerificationByToken(
|
public async deleteEmailVerificationByToken(
|
||||||
tokenToDelete: string
|
tokenToDelete: string
|
||||||
): Promise<EmailVerification | null> {
|
): Promise<EmailVerification | null> {
|
||||||
|
|
|
@ -4,7 +4,10 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { EmailVerification } from 'src/entities';
|
import { EmailVerification } from 'src/entities';
|
||||||
import { SessionService } from 'src/modules/session/services/session.service';
|
import { SessionService } from 'src/modules/session/services/session.service';
|
||||||
import { SuccessDto, UriEncoderService } from 'src/shared';
|
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 { UserDataRepository } from '../../user-module/repositories/user-data.repository';
|
||||||
import { EmailVerifyRepository } from '../repositories';
|
import { EmailVerifyRepository } from '../repositories';
|
||||||
|
@ -59,11 +62,11 @@ export class EmailVerificationService {
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const verificationToken = await this.createVerificationToken();
|
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(
|
await this.emailVerifyRepository.createEmailVerification(
|
||||||
verificationToken,
|
verificationToken,
|
||||||
expiration,
|
expiresAt,
|
||||||
email,
|
email,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
@ -85,13 +88,23 @@ export class EmailVerificationService {
|
||||||
emailToVerify: string
|
emailToVerify: string
|
||||||
): Promise<SuccessDto> {
|
): Promise<SuccessDto> {
|
||||||
try {
|
try {
|
||||||
const findTokenAndEmail: EmailVerification =
|
const findTokenAndEmail: EmailVerification | null =
|
||||||
await this.emailVerifyRepository.findByTokenAndEmail(
|
await this.emailVerifyRepository.findValidVerification(
|
||||||
tokenToVerify,
|
tokenToVerify,
|
||||||
emailToVerify
|
emailToVerify
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!findTokenAndEmail) {
|
if (!findTokenAndEmail) {
|
||||||
|
const expiredToken =
|
||||||
|
await this.emailVerifyRepository.findByTokenAndEmail(
|
||||||
|
tokenToVerify,
|
||||||
|
emailToVerify
|
||||||
|
);
|
||||||
|
|
||||||
|
if (expiredToken) {
|
||||||
|
throw new TokenExpiredException();
|
||||||
|
}
|
||||||
|
|
||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +115,9 @@ export class EmailVerificationService {
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof TokenExpiredException) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new InternalServerErrorException('EMAIL_VERIFICATION_ERROR', {
|
throw new InternalServerErrorException('EMAIL_VERIFICATION_ERROR', {
|
||||||
message: 'An error occurred while verifying the email.',
|
message: 'An error occurred while verifying the email.',
|
||||||
});
|
});
|
||||||
|
@ -128,6 +144,12 @@ export class EmailVerificationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteAllExpiredTokens(): Promise<void> {
|
||||||
|
const currentDate = new Date();
|
||||||
|
|
||||||
|
await this.emailVerifyRepository.deleteAllExpiredTokens();
|
||||||
|
}
|
||||||
|
|
||||||
private async createVerificationToken(): Promise<string> {
|
private async createVerificationToken(): Promise<string> {
|
||||||
const verifyToken = randomBytes(32).toString('hex');
|
const verifyToken = randomBytes(32).toString('hex');
|
||||||
|
|
||||||
|
|
|
@ -2,3 +2,4 @@ export * from './conflict.exception';
|
||||||
export * from './forbidden.exception';
|
export * from './forbidden.exception';
|
||||||
export * from './internal-server-error.exception';
|
export * from './internal-server-error.exception';
|
||||||
export * from './not-found.exception';
|
export * from './not-found.exception';
|
||||||
|
export * from './token-expired.exception';
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue