import { EntityManager } from "@mikro-orm/postgresql"; import { BadRequestException, Injectable } from "@nestjs/common"; import { TokensService } from "./tokens.service"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; import { Business } from "../../businesses/entities/business.entity"; import { NotificationQueue } from "../../notifications/queue/notification.queue"; import { Role } from "../../users/entities/role.entity"; import { RoleEnum } from "../../users/enums/role.enum"; import { UsersService } from "../../users/services/users.service"; import { OTPService } from "../../utils/providers/otp.service"; import { PasswordService } from "../../utils/providers/password.service"; import { SmsService } from "../../utils/providers/sms.service"; import { ChangePasswordDto } from "../DTO/change-password.dto"; import { CompleteRegistrationDto } from "../DTO/complete-register.dto"; import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto"; import { RequestOtpDto } from "../DTO/request-otp.dto"; import { VerifyOtpDto } from "../DTO/verify-otp.dto"; @Injectable() export class AuthService { constructor( private readonly usersService: UsersService, private readonly passwordService: PasswordService, private readonly otpService: OTPService, private readonly tokensService: TokensService, private readonly notificationQueue: NotificationQueue, private readonly smsService: SmsService, private readonly em: EntityManager, ) {} //****************** */ //****************** */ async initiateRegistration(requestOtpDto: RequestOtpDto, businessId: string) { const { phone } = requestOtpDto; const existUser = await this.usersService.findOneWithPhone(phone, businessId); 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.notificationQueue.addOtpNotification(phone, { userPhone: phone, otp: otpCode }); return { message: AuthMessage.OTP_SENT, }; } //****************** */ //****************** */ async completeRegistration(completeRegistrationDto: CompleteRegistrationDto, business: Business) { const { phone, code } = completeRegistrationDto; const entityManager = this.em.fork(); try { await entityManager.begin(); // const isValid = await this.otpService.verifyOtp(phone, code, "REGISTER"); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); const existUser = await this.usersService.findOneWithPhone(phone, business.id); if (existUser) throw new BadRequestException(AuthMessage.PHONE_EXISTS); const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, business, entityManager); const tokens = await this.tokensService.generateTokens(user, entityManager); await this.otpService.delOtpFormCache(phone, "REGISTER"); await entityManager.commit(); return { message: AuthMessage.USER_REGISTER_SUCCESS, ...tokens, }; } catch (error) { await entityManager.rollback(); throw error; } } //****************** */ //****************** */ async loginWithPassword(loginDto: LoginPasswordDTO, businessId: string) { const { email, password } = loginDto; const user = await this.checkUserLoginCredentialWithEmail(email, password, businessId); if (this.checkUserIsAdmin(user.role)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN); const tokens = await this.tokensService.generateTokens(user); await this.notificationQueue.addLoginNotification(user.id, { userPhone: user.phone }); return { message: AuthMessage.PASSWORD_LOGIN_SUCCESS, ...tokens, }; } //****************** */ //****************** */ async checkUserExist(checkUserExistDto: CheckUserExistDto, businessId: string) { const user = await this.usersService.findOneWithEmail(checkUserExistDto.email, businessId); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { message: UserMessage.USER_EXISTS }; } //****************** */ //****************** */ async requestLoginOtp(requestOtpDto: RequestOtpDto, businessId: string) { const { phone } = requestOtpDto; const user = await this.usersService.findOneWithPhone(phone, businessId); if (!user) throw new BadRequestException(AuthMessage.PHONE_NOT_FOUND); //check the if the method call is from admin or not const isUserAdmin = this.checkUserIsAdmin(user.role); if (isUserAdmin) 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); return { message: AuthMessage.OTP_SENT, }; } //****************** */ //****************** */ async verifyLoginOtp(verifyOtpDto: VerifyOtpDto, businessId: string) { const { code, phone } = verifyOtpDto; const user = await this.checkUserLoginCredentialWithPhone(phone, code, businessId); if (this.checkUserIsAdmin(user.role)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN); const tokens = await this.tokensService.generateTokens(user); await this.notificationQueue.addLoginNotification(user.id, { userPhone: user.phone, userEmail: user.email }); return { message: AuthMessage.LOGIN_SUCCESS, ...tokens, }; } //****************** */ async changePassword(userId: string, updateDto: ChangePasswordDto) { const { user } = await this.usersService.findOneById(userId); const passCompare = await this.passwordService.comparePassword(updateDto.password, user.password); if (!passCompare) throw new BadRequestException(AuthMessage.CURRENT_PASSWORD_INVALID); if (updateDto.newPassword !== updateDto.repeatPassword) throw new BadRequestException(AuthMessage.INVALID_REPEAT_PASSWORD); const hashedPassword = await this.passwordService.hashPassword(updateDto.newPassword); await this.usersService.updateUserPassword(user, hashedPassword); return { message: AuthMessage.PASSWORD_CHANGED_SUCCESSFULLY, }; } //****************** */ async refreshToken(oldRefreshToken: string) { return this.tokensService.refreshToken(oldRefreshToken); } //****************** */ async logout(userId: string) { await this.tokensService.invalidateRefreshToken(userId); return { message: AuthMessage.LOGOUT_SUCCESS, }; } //****************** */ private async checkUserLoginCredentialWithEmail(email: string, password: string, businessId: string) { const user = await this.usersService.findOneWithEmail(email, businessId); 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, businessId: 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, businessId); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return user; } // private checkUserPerm(user: User, perm: PermissionEnum) { // return user.roles.some((role) => role?.permissions?.some((p) => p.name === perm)); // } //****************** */ //****************** */ private checkUserIsAdmin(role: Role) { return role.name === RoleEnum.ADMIN; } }