import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import { DataSource } from "typeorm"; import { TokensService } from "./tokens.service"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; import { NotificationsService } from "../../notifications/providers/notifications.service"; import { RoleEnum } from "../../users/enums/role.enum"; import { UsersService } from "../../users/providers/users.service"; import { OTPService } from "../../utils/providers/otp.service"; import { PasswordService } from "../../utils/providers/password.service"; import { SmsService } from "../../utils/providers/sms.service"; import { CompleteRegistrationDto } from "../DTO/complete-register.dto"; import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto"; import { RequestOtpDto } from "../DTO/request-otp.dto"; import { UpdatePasswordDto } from "../DTO/update-password.dto"; import { VerifyOtpDto } from "../DTO/verify-otp.dto"; @Injectable() export class AuthService { private readonly logger = new Logger(AuthService.name); constructor( private readonly usersService: UsersService, private readonly passwordService: PasswordService, private readonly otpService: OTPService, private readonly tokensService: TokensService, private readonly dataSource: DataSource, private readonly notificationService: NotificationsService, private readonly smsService: SmsService, ) {} //****************** */ //****************** */ async initiateRegistration(requestOtpDto: RequestOtpDto) { const { phone } = requestOtpDto; const existUser = await this.usersService.findOneWithPhone(phone); if (existUser) throw new BadRequestException(AuthMessage.PHONE_EXISTS); const existCode = await this.otpService.checkExistOtp(phone, "REGISTER"); if (existCode) { return { message: AuthMessage.OTP_ALREADY_SENT, ttlSecond: existCode, }; } const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER"); // await this.smsService.sendSmsVerifyCode(phone, otpCode); this.logger.debug(`OTP sent to ${phone}: ${otpCode}`); return { message: AuthMessage.OTP_SENT, otpCode, }; } //****************** */ //****************** */ async completeRegistration(completeRegistrationDto: CompleteRegistrationDto) { const { phone, code } = completeRegistrationDto; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { const isValid = await this.otpService.verifyOtp(phone, code, "REGISTER"); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); await this.otpService.delOtpFormCache(phone, "REGISTER"); const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, queryRunner); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); await queryRunner.commitTransaction(); return { message: AuthMessage.USER_REGISTER_SUCCESS, ...tokens, }; } catch (error) { await queryRunner.rollbackTransaction(); throw error; } finally { await queryRunner.release(); } } //****************** */ //****************** */ async loginWithPassword(loginDto: LoginPasswordDTO) { const { email, password } = loginDto; const user = await this.checkUserLoginCredentialWithEmail(email, password); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); await this.notificationService.createLoginNotification(user.id); return { message: AuthMessage.PASSWORD_LOGIN_SUCCESS, ...tokens, }; } //****************** */ //****************** */ async adminLoginWithPassword(loginDto: LoginPasswordDTO) { const { email, password } = loginDto; const user = await this.checkUserLoginCredentialWithEmail(email, password); if (user.role.name !== RoleEnum.ADMIN) throw new BadRequestException(AuthMessage.NOT_ADMIN); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); return { message: AuthMessage.PASSWORD_LOGIN_SUCCESS, ...tokens, }; } //****************** */ //****************** */ async checkUserExist(checkUserExistDto: CheckUserExistDto) { const user = await this.usersService.findOneWithEmail(checkUserExistDto.email); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { message: UserMessage.USER_EXISTS }; } //****************** */ //****************** */ async requestLoginOtp(requestOtpDto: RequestOtpDto, isAdmin: boolean = false) { const { phone } = requestOtpDto; const user = await this.usersService.findOneWithPhone(phone); if (!user) throw new BadRequestException(AuthMessage.PHONE_NOT_FOUND); //check the if the method call is from admin or not if (isAdmin && user.role.name !== RoleEnum.ADMIN) throw new BadRequestException(AuthMessage.NOT_ADMIN); const existCode = await this.otpService.checkExistOtp(phone, "LOGIN"); if (existCode) { return { message: AuthMessage.OTP_ALREADY_SENT, ttlSecond: existCode, }; } const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN"); // await this.smsService.sendSmsVerifyCode(phone, otpCode); this.logger.debug(`OTP sent to ${phone}: ${otpCode}`); return { message: AuthMessage.OTP_SENT, otpCode, }; } //****************** */ //****************** */ async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) { const { code, phone } = verifyOtpDto; const user = await this.checkUserLoginCredentialWithPhone(phone, code); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); await this.notificationService.createLoginNotification(user.id); return { message: AuthMessage.LOGIN_SUCCESS, ...tokens, }; } //****************** */ //****************** */ async adminVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) { const { code, phone } = verifyOtpDto; const user = await this.checkUserLoginCredentialWithPhone(phone, code); if (user.role.name !== RoleEnum.ADMIN) throw new BadRequestException(AuthMessage.NOT_ADMIN); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); return { message: AuthMessage.LOGIN_SUCCESS, ...tokens, }; } //****************** */ async updatePassword(userId: string, updateDto: UpdatePasswordDto) { const { user } = await this.usersService.findOneById(userId); const passCompare = await this.passwordService.comparePassword(updateDto.password, user.password); if (!passCompare) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); if (updateDto.newPassword !== updateDto.repeatPassword) throw new BadRequestException(AuthMessage.INVALID_REAPET_PASSWORD); const hashedPassword = await this.passwordService.hashPassword(updateDto.newPassword); await this.usersService.updateUserPassword(user.id, hashedPassword); return { message: AuthMessage.PASSWORD_CHANGED_SUCCESSFULLY, }; } //****************** */ private async checkUserLoginCredentialWithEmail(email: string, password: string) { const user = await this.usersService.findOneWithEmail(email); if (!user) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); const passCompare = await this.passwordService.comparePassword(password, user.password); if (!passCompare) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); return user; } //****************** */ //****************** */ private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string) { const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN"); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); await this.otpService.delOtpFormCache(phone, "LOGIN"); const user = await this.usersService.findOneWithPhone(phone); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return user; } }