import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { JwtService } from '@nestjs/jwt'; import { IS_PUBLIC_KEY } from './decorators.js'; import { AuthService } from './auth.service.js'; export interface AuthUser { userId: string; tenantCode: string; username: string; } @Injectable() export class JwtAuthGuard implements CanActivate { constructor( private readonly jwt: JwtService, private readonly reflector: Reflector, private readonly auth: AuthService, ) {} async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), ]); if (isPublic) return true; const request = context.switchToHttp().getRequest(); const url: string = request.originalUrl ?? request.url ?? ''; if (url.startsWith('/docs')) return true; const token = this.extractToken(request); if (!token) throw new UnauthorizedException('未登录'); try { const payload = await this.jwt.verifyAsync<{ sub: string; tenantCode: string; username: string; ver?: number; sid?: string }>(token); const active = await this.auth.checkUserActive(payload.tenantCode, payload.sub, payload.ver ?? 0); if (!active) throw new UnauthorizedException('账号已停用或登录已失效'); if (payload.sid && (await this.auth.isSessionRevoked(payload.sid))) { throw new UnauthorizedException('会话已失效,请重新登录'); } request.user = { userId: payload.sub, tenantCode: payload.tenantCode, username: payload.username } satisfies AuthUser; return true; } catch (e) { if (e instanceof UnauthorizedException) throw e; throw new UnauthorizedException('token 无效或已过期'); } } private extractToken(request: { headers?: Record }): string | undefined { const auth = request.headers?.authorization; if (!auth) return undefined; const [type, token] = auth.split(' '); return type === 'Bearer' && token ? token : undefined; } }