90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
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),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|