import { Injectable, BadRequestException } from '@nestjs/common'; import { RequestOtpDto } from '../dto/request-otp.dto'; import { SmsService } from '../../notification/services/sms.service'; import { UserService } from '../../user/providers/user.service'; import { randomInt } from 'crypto'; import { TokensService } from './tokens.service'; import { AuthMessage } from 'src/common/enums/message.enum'; import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; import { AdminLoginTransformer } from '../transformers/admin-login.transformer'; import { UserLoginTransformer } from '../transformers/user-login.transformer'; import { OtpService } from './otp.service'; import { normalizePhoneToLocal } from 'src/modules/util/phone.util'; import { CacheService } from 'src/modules/util/cache.service'; import { PermissionService } from 'src/modules/roles/providers/permissions.service'; @Injectable() export class AuthService { readonly ADMIN_PERMISSIONS_EXPIRATION_TIME = 1800; constructor( private readonly smsService: SmsService, private readonly userService: UserService, private readonly tokensService: TokensService, private readonly adminRepository: AdminRepository, private readonly otpService: OtpService, private readonly cacheService: CacheService, private readonly permissionService: PermissionService, ) { } async requestOtp(dto: RequestOtpDto, isAdmin: boolean) { const phone = normalizePhoneToLocal(dto.phone); if (isAdmin) { await this.valdateAdmin(phone) } const code = this.generateOtpCode(); await this.otpService.set(phone, code) // await this.smsService.sendotp(phone, code); return { code }; } async verifyOtp(phone: string, code: string) { phone = normalizePhoneToLocal(phone); const cachedCode = await this.otpService.get(phone); if (!cachedCode) throw new BadRequestException('OTP expired or not found'); if (cachedCode !== code) throw new BadRequestException('Invalid OTP'); await this.otpService.delete(phone) const user = await this.userService.getOrCreate({ phone: phone, gender: true, // maxCredit: 0 }); const tokens = await this.tokensService.generateAccessAndRefreshToken(user.id, false); const userResponse = UserLoginTransformer.transform(user); return { tokens, user: userResponse }; } async verifyOtpAdmin(phone: string, code: string) { phone = normalizePhoneToLocal(phone); const cachedCode = await this.otpService.get(phone); if (!cachedCode) throw new BadRequestException('OTP expired or not found'); if (cachedCode !== code) throw new BadRequestException('Invalid OTP'); await this.otpService.delete(phone); const admin = await this.adminRepository.findOne({ phone }, { populate: ['role', 'role.permissions'] }); if (!admin) { throw new BadRequestException(AuthMessage.ADMIN_NOT_FOUND); } const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, true); const permissionNames = admin.role.permissions.map(p => p.name); const adminResponse = await AdminLoginTransformer.transform(admin); return { tokens, admin: adminResponse }; } private generateOtpCode(): string { const code = randomInt(10000, 100000); return code.toString(); } refreshToken(oldRefreshToken: string) { return this.tokensService.refreshToken(oldRefreshToken); } private async valdateAdmin(phone: string) { const admin = await this.adminRepository.findOne({ phone }) if (!admin) { throw new BadRequestException('Admin Not Found') } } async setAdminPermissionsInCache(adminId: string, permissionNames: string[]) { await this.cacheService.set( this.getAdminPermissionsCacheKey(adminId), JSON.stringify(permissionNames), this.ADMIN_PERMISSIONS_EXPIRATION_TIME, ); return { success: true }; } async getAdminPermissionsFromCache(adminId: string): Promise { const cacheKey = this.getAdminPermissionsCacheKey(adminId); const cached = await this.cacheService.get(cacheKey); if (cached !== undefined) { return JSON.parse(cached) as string[]; } const permissions = await this.permissionService.getAdminPermissionsName(adminId); await this.setAdminPermissionsInCache(adminId, permissions); return permissions; } private getAdminPermissionsCacheKey(adminId: string): string { return `admin:${adminId}:permissions`; } }