64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|||
|
|
import { PassportStrategy } from '@nestjs/passport';
|
||
|
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
||
|
|
import { expandWithParents } from './acl.constants';
|
||
|
|
|
||
|
|
export interface JwtPayload {
|
||
|
|
sub: string;
|
||
|
|
mobile: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The authenticated principal attached to `req.user`.
|
||
|
|
*
|
||
|
|
* `permissions` is already expanded with ancestors, so guards can test a rid with
|
||
|
|
* a plain `includes()` — the same shape the legacy frontend held in its Vuex store
|
||
|
|
* after checkParent().
|
||
|
|
*/
|
||
|
|
export interface AuthenticatedAdmin {
|
||
|
|
id: string;
|
||
|
|
fullName: string;
|
||
|
|
mobile: string;
|
||
|
|
role: string;
|
||
|
|
userTypeId: number | null;
|
||
|
|
branchId: string | null;
|
||
|
|
permissions: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||
|
|
constructor(private readonly prisma: PrismaService) {
|
||
|
|
super({
|
||
|
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||
|
|
ignoreExpiration: false,
|
||
|
|
secretOrKey: process.env.JWT_SECRET ?? 'dev-only-insecure-secret',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async validate(payload: JwtPayload): Promise<AuthenticatedAdmin> {
|
||
|
|
const user = await this.prisma.adminUser.findUnique({
|
||
|
|
where: { id: payload.sub },
|
||
|
|
include: { userType: { include: { permissions: true } } },
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!user || !user.isActive) {
|
||
|
|
throw new UnauthorizedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Permissions are re-read on every request rather than baked into the token,
|
||
|
|
// so a permission change takes effect immediately instead of at next login.
|
||
|
|
const rids = user.userType?.permissions.map((p) => p.rid) ?? [];
|
||
|
|
|
||
|
|
return {
|
||
|
|
id: user.id,
|
||
|
|
fullName: user.fullName,
|
||
|
|
mobile: user.mobile,
|
||
|
|
role: user.role,
|
||
|
|
userTypeId: user.userTypeId,
|
||
|
|
branchId: user.branchId,
|
||
|
|
permissions: expandWithParents(rids),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|