refactor: changed otp to use cache

This commit is contained in:
2026-07-29 15:46:25 +03:30
parent 2cfda9ca45
commit ce0b98783c
6 changed files with 209 additions and 64 deletions
+3 -2
View File
@@ -9,6 +9,7 @@ import { OtpRepository } from './repositories/otp.repository';
import { UsersModule } from 'src/modules/users/users.module';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './strategies/jwt.strategy';
import { OtpService } from './otp.service';
@Module({
imports: [
@@ -19,7 +20,7 @@ import { JwtStrategy } from './strategies/jwt.strategy';
UsersModule,
],
controllers: [AuthController],
providers: [AuthService, OtpRepository, JwtStrategy],
exports: [JwtModule, PassportModule]
providers: [AuthService, OtpService, OtpRepository, JwtStrategy],
exports: [JwtModule, PassportModule],
})
export class AuthModule {}
+4 -59
View File
@@ -12,6 +12,7 @@ import { UsersService } from 'src/modules/users/users.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import ms from 'ms';
import { OtpService } from './otp.service';
@Injectable()
export class AuthService {
@@ -19,13 +20,13 @@ export class AuthService {
@Inject(SMS_CONFIG) private smsConfig: ISmsConfigs,
private readonly smsService: SmsService,
private readonly configService: ConfigService,
private readonly OtpRepository: OtpRepository,
private readonly userService: UsersService,
private readonly jwtService: JwtService,
private readonly otpService: OtpService
) {}
async sendOTP(phone: string) {
const otp = this.generateOtp();
const otp = await this.otpService.generate(phone);
try {
await this.smsService.sendSms({
@@ -39,21 +40,6 @@ export class AuthService {
],
});
await this.OtpRepository.update(
{ phoneNumber: phone, status: OtpStatus.PENDING },
{ status: OtpStatus.EXPIRED },
);
await this.OtpRepository.save(
this.OtpRepository.create({
phoneNumber: phone,
code: otp,
expirationDate: new Date(Date.now() + 2 * 60 * 1000),
status: OtpStatus.PENDING,
numberOfTries: 0,
}),
);
return { otp };
} catch (error) {
if (this.configService.get('NODE_ENV') !== 'production') {
@@ -65,48 +51,7 @@ export class AuthService {
}
async verifyOTP(dto: VerifyOtpDto) {
const otp = await this.OtpRepository.findOne({
where: { phoneNumber: dto.phoneNumber, status: OtpStatus.PENDING },
order: {
createdAt: 'DESC',
},
});
if (!otp) {
throw new BadRequestException(OTPMessages.OTP_NOT_FOUND);
}
if (otp.expirationDate < new Date()) {
otp.status = OtpStatus.EXPIRED;
await this.OtpRepository.save(otp);
throw new BadRequestException(OTPMessages.OTP_EXPIRED);
}
if (otp.code !== dto.code) {
otp.numberOfTries++;
if (otp.numberOfTries >= 3) {
otp.status = OtpStatus.EXPIRED;
await this.OtpRepository.save(otp);
throw new BadRequestException(OTPMessages.OTP_TOO_MANY_TRIES);
}
await this.OtpRepository.save(otp);
throw new BadRequestException(OTPMessages.OTP_INVALID);
}
otp.status = OtpStatus.VERIFIED;
await this.OtpRepository.save(otp);
}
private generateOtp(): string {
return Math.floor(100000 + Math.random() * 900000).toString();
await this.otpService.verify(dto.phoneNumber, dto.code);
}
async checkLoginCredentials(credential: LoginCredentialDto): Promise<void> {
+74
View File
@@ -0,0 +1,74 @@
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { Cache } from 'cache-manager';
import { OTPMessages } from 'src/common/enums/messages.enum';
interface OtpCache {
code: string;
tries: number;
}
@Injectable()
export class OtpService {
private readonly TTL: number;
constructor(
@Inject(CACHE_MANAGER)
private readonly cache: Cache,
private readonly configService: ConfigService,
) {
this.TTL = this.configService.getOrThrow<number>('CACHE_TTL');
}
private getKey(phoneNumber: string) {
return `otp:${phoneNumber}`;
}
async generate(phoneNumber: string): Promise<string> {
const code = Math.floor(100000 + Math.random() * 900000).toString();
const value: OtpCache = {
code,
tries: 0,
};
await this.cache.set(this.getKey(phoneNumber), value, this.TTL);
return code;
}
async verify(phoneNumber: string, code: string): Promise<boolean> {
const otp = await this.cache.get<OtpCache>(this.getKey(phoneNumber));
if (!otp) {
throw new BadRequestException(OTPMessages.OTP_EXPIRED);
}
if (otp.tries >= 5) {
await this.cache.del(this.getKey(phoneNumber));
throw new BadRequestException(OTPMessages.OTP_TOO_MANY_TRIES);
}
if (otp.code !== code) {
otp.tries++;
await this.cache.set(this.getKey(phoneNumber), otp, this.TTL);
throw new BadRequestException(OTPMessages.OTP_INVALID);
}
await this.cache.del(this.getKey(phoneNumber));
return true;
}
async delete(phoneNumber: string): Promise<void> {
await this.cache.del(this.getKey(phoneNumber));
}
async exists(phoneNumber: string): Promise<boolean> {
return !!(await this.cache.get(this.getKey(phoneNumber)));
}
}