import { Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcryptjs'; import { PrismaService } from '../../prisma/prisma.service'; import type { LoginDto } from './dto/login.dto'; @Injectable() export class AuthService { constructor( private readonly prisma: PrismaService, private readonly jwt: JwtService, ) {} /** * Port of legacy AdminController::login (henza_server). * * Legacy issued a random 60-char string as `api_token` and persisted it on the * user row; this issues a signed JWT instead. The response keeps the legacy * shape the admin panel's auth store reads: * - `api_token` -> stored in localStorage under token/branch_token * - `roles` -> [{ rid }], mapped to rids by the frontend then expanded * through checkParent() * * Deliberate deviation from legacy: a bad phone and a bad password both return * 401 here. Legacy returned 404 (firstOrFail) for an unknown phone and 422 for * a wrong password, which let anyone enumerate valid admin phone numbers. */ async login(dto: LoginDto) { const user = await this.prisma.adminUser.findUnique({ where: { mobile: dto.phone }, include: { userType: { include: { permissions: true } } }, }); if (!user || !user.isActive) { throw new UnauthorizedException('نام کاربری یا رمز عبور اشتباه است'); } const passwordOk = await bcrypt.compare(dto.password, user.passwordHash); if (!passwordOk) { throw new UnauthorizedException('نام کاربری یا رمز عبور اشتباه است'); } const apiToken = await this.jwt.signAsync({ sub: user.id, mobile: user.mobile }); return { id: user.id, name: user.fullName, fullName: user.fullName, mobile: user.mobile, role: user.role, ut: user.userTypeId, branch_id: user.branchId, api_token: apiToken, roles: (user.userType?.permissions ?? []).map((p) => ({ rid: p.rid })), }; } /** * Port of legacy UserController::showData, which the router guard calls on every * cold load to refresh the permission list: * * $user['permission'] = Permission::where('ut',$user->ut)->select('rid')->get()->pluck('rid'); * * Returns raw (unexpanded) rids — the frontend runs checkParent() on them, as in * legacy. The guard's own checks use the pre-expanded list from JwtStrategy. */ async showData(adminId: string) { const user = await this.prisma.adminUser.findUnique({ where: { id: adminId }, include: { userType: { include: { permissions: true } } }, }); if (!user) { throw new UnauthorizedException(); } return { id: user.id, name: user.fullName, fullName: user.fullName, mobile: user.mobile, role: user.role, ut: user.userTypeId, branch_id: user.branchId, user_type: user.userType ? { id: user.userType.id, title: user.userType.title } : null, permission: (user.userType?.permissions ?? []).map((p) => p.rid), }; } }