import { CanActivate, ExecutionContext, Injectable, SetMetadata, createParamDecorator, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Role } from '@prisma/client'; import { BizException } from '@/common/exceptions/biz.exception'; import { JWTPayload, JwtService } from '@/libs/jwt/jwt.service'; import { ErrorCodeEnum } from 'shared/dist/error-code'; export { Role } from '@prisma/client'; export const ROLES_KEY = 'roles'; export const IS_PUBLIC_KEY = 'isPublic'; export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); export const Payload = createParamDecorator( (key: string, ctx: ExecutionContext) => { const payload = ctx.switchToHttp().getRequest().payload as JWTPayload; if (key) return payload?.[key]; return payload; }, ); @Injectable() export class AuthGuard implements CanActivate { constructor( private reflector: Reflector, private jwtService: JwtService, ) {} async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), ]); const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { if (isPublic) return true; throw new BizException(ErrorCodeEnum.AuthFail); } else { try { request['payload'] = await this.jwtService.verify(token); } catch { if (isPublic) return true; throw new BizException(ErrorCodeEnum.AuthFail); } } const roles = this.reflector.getAllAndOverride('roles', [ context.getHandler(), context.getClass(), ]); if (!roles) return true; return this.matchRoles(roles, request['payload']?.role); } private matchRoles(roles: Role[], payload: Role): boolean { return roles.includes(payload); } private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = new Headers(request.headers).get('authorization')?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } }