Merge pull request #73 from SyncrowIOT/SP-340-implement-send-otp-endpoint

otp cooldown
This commit is contained in:
yousef-khriasat-uba
2024-08-11 14:23:08 +03:00
committed by GitHub
4 changed files with 79 additions and 4 deletions

View File

@ -0,0 +1,4 @@
export function differenceInSeconds(date1: Date, date2: Date): number {
const diffInMilliseconds = date1.getTime() - date2.getTime();
return Math.floor(diffInMilliseconds / 1000);
}

View File

@ -1,4 +1,11 @@
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import {
Column,
DeleteDateColumn,
Entity,
ManyToOne,
OneToMany,
Unique,
} from 'typeorm';
import { import {
UserDto, UserDto,
UserNotificationDto, UserNotificationDto,
@ -155,6 +162,9 @@ export class UserOtpEntity extends AbstractEntity<UserOtpDto> {
}) })
type: OtpType; type: OtpType;
@DeleteDateColumn({ nullable: true })
deletedAt?: Date;
constructor(partial: Partial<UserOtpEntity>) { constructor(partial: Partial<UserOtpEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -1,6 +1,12 @@
import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { OtpType } from '../../../libs/common/src/constants/otp-type.enum';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsEnum, IsNotEmpty, IsString } from 'class-validator'; import {
IsEmail,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
} from 'class-validator';
export class UserOtpDto { export class UserOtpDto {
@ApiProperty() @ApiProperty()
@ -12,6 +18,11 @@ export class UserOtpDto {
@IsEnum(OtpType) @IsEnum(OtpType)
@IsNotEmpty() @IsNotEmpty()
type: OtpType; type: OtpType;
@ApiProperty()
@IsOptional()
@IsString()
regionUuid?: string;
} }
export class VerifyOtpDto extends UserOtpDto { export class VerifyOtpDto extends UserOtpDto {

View File

@ -18,6 +18,8 @@ import { EmailService } from '../../../libs/common/src/util/email.service';
import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { OtpType } from '../../../libs/common/src/constants/otp-type.enum';
import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity'; import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import { differenceInSeconds } from '@app/common/helper/differenceInSeconds';
import { LessThan, MoreThan } from 'typeorm';
@Injectable() @Injectable()
export class UserAuthService { export class UserAuthService {
@ -114,7 +116,7 @@ export class UserAuthService {
async deleteUser(uuid: string) { async deleteUser(uuid: string) {
const user = await this.findOneById(uuid); const user = await this.findOneById(uuid);
if (!user) { if (!user) {
throw new BadRequestException('User does not found'); throw new BadRequestException('User not found');
} }
return await this.userRepository.update({ uuid }, { isActive: false }); return await this.userRepository.update({ uuid }, { isActive: false });
} }
@ -124,7 +126,55 @@ export class UserAuthService {
} }
async generateOTP(data: UserOtpDto): Promise<string> { async generateOTP(data: UserOtpDto): Promise<string> {
await this.otpRepository.delete({ email: data.email, type: data.type }); const threeDaysAgo = new Date();
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
const userExists = await this.userRepository.exists({
where: {
region: data.regionUuid
? {
uuid: data.regionUuid,
}
: undefined,
email: data.email,
isUserVerified: true,
},
});
if (!userExists) {
throw new BadRequestException('User not found');
}
await this.otpRepository.softDelete({ email: data.email, type: data.type });
await this.otpRepository.delete({
email: data.email,
type: data.type,
createdAt: LessThan(threeDaysAgo),
});
const countOfOtp = await this.otpRepository.count({
withDeleted: true,
where: {
email: data.email,
type: data.type,
createdAt: MoreThan(threeDaysAgo),
},
});
const lastOtp = await this.otpRepository.findOne({
where: { email: data.email, type: data.type },
order: { createdAt: 'DESC' },
withDeleted: true,
});
const cooldown = 30 * Math.pow(2, countOfOtp - 1);
if (lastOtp) {
const now = new Date();
const timeSinceLastOtp = differenceInSeconds(now, lastOtp.createdAt);
if (timeSinceLastOtp < cooldown) {
throw new BadRequestException({
message: `Please wait ${cooldown - timeSinceLastOtp} more seconds before requesting a new OTP.`,
data: {
cooldown: cooldown - timeSinceLastOtp,
},
});
}
}
const otpCode = Math.floor(100000 + Math.random() * 900000).toString(); const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
const expiryTime = new Date(); const expiryTime = new Date();
expiryTime.setMinutes(expiryTime.getMinutes() + 1); expiryTime.setMinutes(expiryTime.getMinutes() + 1);