refactor: changed otp to use cache
This commit is contained in:
+16
-3
@@ -10,21 +10,34 @@ import { OrganModule } from './modules/organ/organ.module';
|
||||
import { ComplaintModule } from './modules/complaint/complaint.module';
|
||||
import { AttachmentModule } from './modules/attachment/attachment.module';
|
||||
import { ReplyModule } from './modules/reply/reply.module';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import KeyvRedis from '@keyv/redis';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
validationSchema
|
||||
validationSchema,
|
||||
}),
|
||||
CacheModule.registerAsync({
|
||||
isGlobal: true,
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
stores: [new KeyvRedis(config.getOrThrow('REDIS_URL'))],
|
||||
}),
|
||||
}),
|
||||
TypeOrmModule.forRootAsync(databaseConfig()),
|
||||
HttpModule.register({global: true, timeout: 10000, headers: {"Content-Type": "application/json"}}),
|
||||
HttpModule.register({
|
||||
global: true,
|
||||
timeout: 10000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}),
|
||||
UsersModule,
|
||||
AuthModule,
|
||||
OrganModule,
|
||||
ComplaintModule,
|
||||
AttachmentModule,
|
||||
ReplyModule
|
||||
ReplyModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user