310 lines
10 KiB
TypeScript
Executable File
310 lines
10 KiB
TypeScript
Executable File
import { randomBytes } from "node:crypto";
|
|
|
|
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 { NotificationQueue } from "../../notifications/queue/notification.queue";
|
|
import { ReferralsService } from "../../referrals/providers/referrals.service";
|
|
import { Role } from "../../users/entities/role.entity";
|
|
import { AdminsService } from "../../users/providers/admins.service";
|
|
import { UsersService } from "../../users/providers/users.service";
|
|
import { OTPService } from "../../utils/providers/otp.service";
|
|
import { PasswordService } from "../../utils/providers/password.service";
|
|
import { randomizeCase } from "../../utils/providers/string.utils";
|
|
import { ChangePasswordDto } from "../DTO/change-password.dto";
|
|
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
|
|
import { RequestOtpDto } from "../DTO/request-otp.dto";
|
|
import { VerifyOtpDto } from "../DTO/verify-otp.dto";
|
|
import { ForgetPasswordDto } from "../DTO/forget-password.dto";
|
|
import { VerifyForgotPasswordOtpDto } from "../DTO/verify-forgot-otp.dto";
|
|
import { EmailService } from "../../utils/providers/email.service";
|
|
|
|
@Injectable()
|
|
export class AuthService {
|
|
private readonly logger = new Logger(AuthService.name);
|
|
constructor(
|
|
private readonly usersService: UsersService,
|
|
private readonly adminsService: AdminsService,
|
|
private readonly passwordService: PasswordService,
|
|
private readonly otpService: OTPService,
|
|
private readonly tokensService: TokensService,
|
|
private readonly dataSource: DataSource,
|
|
private readonly notificationQueue: NotificationQueue,
|
|
private readonly referralsService: ReferralsService,
|
|
private readonly emailService: EmailService,
|
|
) { }
|
|
|
|
//****************** */
|
|
//****************** */
|
|
async loginWithPassword(loginDto: LoginPasswordDTO) {
|
|
const { email, password } = loginDto;
|
|
|
|
const user = await this.checkUserLoginCredentialWithEmail(email, password);
|
|
|
|
if (!user.roles.some((role) => !role.isAdmin)) 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 adminLoginWithPassword(loginDto: LoginPasswordDTO) {
|
|
const { email, password } = loginDto;
|
|
|
|
const user = await this.checkUserLoginCredentialWithEmail(email, password);
|
|
|
|
const isAdmin = this.checkUserIsAdmin(user.roles);
|
|
if (!isAdmin) throw new BadRequestException(AuthMessage.NOT_ADMIN);
|
|
|
|
if (user.deletedAt) throw new BadRequestException(AuthMessage.ACCESS_DENIED);
|
|
|
|
const tokens = await this.tokensService.generateTokens(user);
|
|
|
|
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) {
|
|
const { phone } = requestOtpDto;
|
|
|
|
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN/REGISTER");
|
|
if (existCode) {
|
|
return {
|
|
message: AuthMessage.OTP_ALREADY_SENT,
|
|
ttlSecond: existCode,
|
|
};
|
|
}
|
|
|
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN/REGISTER");
|
|
//
|
|
this.logger.log(`otp code: ${otpCode} for phone: ${phone}`);
|
|
await this.notificationQueue.addLoginOtpNotification({ phone, code: otpCode });
|
|
|
|
return {
|
|
message: AuthMessage.OTP_SENT + otpCode,
|
|
};
|
|
}
|
|
|
|
//****************** */
|
|
//****************** */
|
|
|
|
async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
|
const { code, phone, referralCode } = verifyOtpDto;
|
|
|
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
|
|
try {
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
|
|
const isValid = await this.otpService.verifyOtp(phone, code, "LOGIN/REGISTER");
|
|
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
|
|
|
let user = await this.usersService.findOneWithPhone(phone);
|
|
let isNewUser = false;
|
|
|
|
if (!user) {
|
|
const defaultUserPassword = randomizeCase(randomBytes(6).toString("hex"));
|
|
isNewUser = true;
|
|
|
|
const hashedPassword = await this.passwordService.hashPassword(defaultUserPassword);
|
|
user = await this.usersService.createUser(verifyOtpDto, hashedPassword, queryRunner);
|
|
|
|
if (referralCode) await this.referralsService.useReferralCode({ referralCode, userId: user.id }, queryRunner);
|
|
|
|
const superAdmins = await this.adminsService.getSuperAdmins(queryRunner);
|
|
|
|
for (const admin of superAdmins) {
|
|
await this.notificationQueue.addNewCustomerNotification(admin.id, {
|
|
userPhone: admin.phone,
|
|
userEmail: admin.email,
|
|
fullName: `${user.firstName} ${user.lastName}`,
|
|
mobile: user.phone,
|
|
});
|
|
}
|
|
|
|
await this.notificationQueue.addUserPasswordNotification(user.id, {
|
|
userPhone: user.phone,
|
|
userEmail: user.email,
|
|
password: defaultUserPassword,
|
|
});
|
|
}
|
|
|
|
if (!user.roles.some((role) => !role.isAdmin)) throw new BadRequestException(AuthMessage.ADMIN_CAN_NOT_LOGIN);
|
|
|
|
await this.notificationQueue.addLoginNotification(user.id, { userPhone: user.phone, userEmail: user.email });
|
|
|
|
const tokens = await this.tokensService.generateTokens(user, queryRunner);
|
|
|
|
await this.otpService.delOtpFormCache(phone, "LOGIN/REGISTER");
|
|
|
|
await queryRunner.commitTransaction();
|
|
|
|
return {
|
|
message: isNewUser ? AuthMessage.USER_REGISTER_SUCCESS : AuthMessage.LOGIN_SUCCESS,
|
|
...tokens,
|
|
};
|
|
} catch (error) {
|
|
await queryRunner.rollbackTransaction();
|
|
throw error;
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
|
|
//****************** */
|
|
|
|
async adminVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
|
const { code, phone } = verifyOtpDto;
|
|
|
|
const user = await this.checkUserAuthCredentialWithPhone(phone, code);
|
|
|
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
|
|
|
const isUserAdmin = this.checkUserIsAdmin(user.roles);
|
|
|
|
if (!isUserAdmin) throw new BadRequestException(AuthMessage.NOT_ADMIN);
|
|
if (user.deletedAt) throw new BadRequestException(AuthMessage.ACCESS_DENIED);
|
|
|
|
const tokens = await this.tokensService.generateTokens(user);
|
|
|
|
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_REAPET_PASSWORD);
|
|
|
|
const hashedPassword = await this.passwordService.hashPassword(updateDto.newPassword);
|
|
await this.usersService.updateUserPassword(user.id, 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) {
|
|
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 checkUserAuthCredentialWithPhone(phone: string, otpCode: string) {
|
|
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN/REGISTER");
|
|
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
|
|
|
await this.otpService.delOtpFormCache(phone, "LOGIN/REGISTER");
|
|
|
|
const user = await this.usersService.findOneWithPhone(phone);
|
|
|
|
return user;
|
|
}
|
|
//****************** */
|
|
//****************** */
|
|
async requestForgotPasswordOtp(dto: ForgetPasswordDto) {
|
|
const { email } = dto;
|
|
|
|
const user = await this.usersService.findOneWithEmail(email)
|
|
|
|
if (!user) {
|
|
throw new BadRequestException("کاربری با این ایمیل یافت نشد")
|
|
}
|
|
|
|
const existCode = await this.otpService.checkExistOtp(email, "FORGOT_PASSWORD");
|
|
|
|
if (existCode) {
|
|
return {
|
|
message: AuthMessage.OTP_ALREADY_SENT,
|
|
ttlSecond: existCode,
|
|
};
|
|
}
|
|
|
|
const otpCode = await this.otpService.generateAndSetInCache(email, "FORGOT_PASSWORD");
|
|
|
|
await this.emailService.sendOtpEmail({ userEmail: email, otp: otpCode });
|
|
|
|
this.logger.log(`otp code: ${otpCode} for email: ${email}`);
|
|
|
|
return { message: AuthMessage.OTP_SENTـTO_EMAIL }
|
|
}
|
|
|
|
async verifyForgotPasswordOtp(dto: VerifyForgotPasswordOtpDto) {
|
|
const { code, email, newPassword } = dto;
|
|
|
|
const isValid = await this.otpService.verifyOtp(email, code, "FORGOT_PASSWORD");
|
|
|
|
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
|
|
|
let user = await this.usersService.findOneWithEmail(email);
|
|
|
|
if (!user) {
|
|
throw new BadRequestException('کاربر پیدا نشد .')
|
|
}
|
|
|
|
const hashedPassword = await this.passwordService.hashPassword(newPassword);
|
|
|
|
await this.otpService.delOtpFormCache(email, "FORGOT_PASSWORD");
|
|
|
|
await this.usersService.updateUserPassword(user.id, hashedPassword);
|
|
|
|
return {
|
|
message: AuthMessage.PASSWORD_CHANGED_SUCCESSFULLY
|
|
};
|
|
}
|
|
//****************** */
|
|
//****************** */
|
|
private checkUserIsAdmin(roles: Role[]) {
|
|
return roles.some((role) => role.isAdmin);
|
|
}
|
|
}
|