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'; import { OtpService } from './otp.service'; @Injectable() export class AuthService { constructor( @Inject(SMS_CONFIG) private smsConfig: ISmsConfigs, private readonly smsService: SmsService, private readonly configService: ConfigService, private readonly userService: UsersService, private readonly jwtService: JwtService, private readonly otpService: OtpService ) {} async sendOTP(phone: string) { const otp = await this.otpService.generate(phone); try { await this.smsService.sendSms({ Mobile: phone, TemplateId: this.smsConfig.SMS_PATTERN_OTP, Parameters: [ { name: 'code', value: otp, }, ], }); return { otp }; } catch (error) { if (this.configService.get('NODE_ENV') !== 'production') { return { otp }; } throw error; } } async verifyOTP(dto: VerifyOtpDto) { await this.otpService.verify(dto.phoneNumber, dto.code); } async checkLoginCredentials(credential: LoginCredentialDto): Promise { 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('JWT_ACCESS_SECRET'), expiresIn: this.configService.getOrThrow( 'JWT_ACCESS_EXPIRES', ) as ms.StringValue, }); const refreshToken = await this.jwtService.signAsync(payload, { secret: this.configService.getOrThrow('JWT_REFRESH_SECRET'), expiresIn: this.configService.getOrThrow( '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, }; } }