refactor: changed the project structure
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import type { ISmsConfigs } from 'src/config/sms.config';
|
||||
import { SMS_CONFIG } from '../utils/constants';
|
||||
import { SmsService } from '../utils/providers/sms.service';
|
||||
import { OtpRepository } from './repositories/otp.repository';
|
||||
import { OtpStatus } from './enums/otp-status.enum';
|
||||
import { VerifyOtpDto } from './DTO/verify-otp.dto';
|
||||
import { OTPMessages, UserMessages } from 'src/common/enums/messages.enum';
|
||||
import { LoginCredentialDto } from './DTO/login-credential.dto';
|
||||
import { UsersService } from 'src/modules/users/users.service';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import ms from 'ms';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
@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,
|
||||
) {}
|
||||
|
||||
async sendOTP(phone: string) {
|
||||
const otp = this.generateOtp();
|
||||
|
||||
try {
|
||||
await this.smsService.sendSms({
|
||||
Mobile: phone,
|
||||
TemplateId: this.smsConfig.SMS_PATTERN_OTP,
|
||||
Parameters: [
|
||||
{
|
||||
name: 'code',
|
||||
value: otp,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
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') {
|
||||
return { otp };
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
async checkLoginCredentials(credential: LoginCredentialDto): Promise<void> {
|
||||
const { phoneNumber, password } = credential;
|
||||
|
||||
const user = await this.userService.findOneByPhoneOrFail(phoneNumber);
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw new BadRequestException(UserMessages.USER_PASSWORD_INVALID);
|
||||
}
|
||||
|
||||
await this.sendOTP(phoneNumber);
|
||||
}
|
||||
|
||||
async verifyOtpAndLogin(dto: VerifyOtpDto) {
|
||||
await this.verifyOTP(dto);
|
||||
|
||||
const user = await this.userService.findUserWithRolesByPhoneOrFail(
|
||||
dto.phoneNumber,
|
||||
);
|
||||
|
||||
const roles = user.roles.map((r) => r.role);
|
||||
|
||||
const payload = {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
roles,
|
||||
};
|
||||
|
||||
const accessToken = await this.jwtService.signAsync(payload, {
|
||||
secret: this.configService.getOrThrow<string>('JWT_ACCESS_SECRET'),
|
||||
expiresIn: this.configService.getOrThrow<string>(
|
||||
'JWT_ACCESS_EXPIRES',
|
||||
) as ms.StringValue,
|
||||
});
|
||||
|
||||
const refreshToken = await this.jwtService.signAsync(payload, {
|
||||
secret: this.configService.getOrThrow<string>('JWT_REFRESH_SECRET'),
|
||||
expiresIn: this.configService.getOrThrow<string>(
|
||||
'JWT_REFRESH_EXPIRES',
|
||||
) as ms.StringValue,
|
||||
});
|
||||
|
||||
await this.userService.updateRefreshToken(user.id, refreshToken);
|
||||
|
||||
return {
|
||||
user: {
|
||||
userId: user.id,
|
||||
username: user.firstName + ' ' + user.lastName,
|
||||
userPhone: user.phone,
|
||||
userEmail: user.email,
|
||||
},
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user