rwadurian/backend/services/mpc-service/src/shared/guards/jwt-auth.guard.ts

98 lines
2.6 KiB
TypeScript

/**
* JWT Auth Guard
*
* Protects routes by verifying JWT tokens.
*/
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
export interface JwtPayload {
sub: string; // User ID or service ID
type: 'access' | 'refresh' | 'service';
partyId?: string; // Party ID for MPC operations
iat?: number;
exp?: number;
}
export interface CurrentUserData {
userId: string;
partyId?: string;
tokenType: string;
}
@Injectable()
export class JwtAuthGuard implements CanActivate {
private readonly logger = new Logger(JwtAuthGuard.name);
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// Check if endpoint is public
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('缺少认证令牌');
}
try {
const payload = await this.jwtService.verifyAsync<JwtPayload>(token, {
secret: this.configService.get<string>('JWT_SECRET'),
});
// Validate token type
if (payload.type !== 'access' && payload.type !== 'service') {
throw new UnauthorizedException('无效的令牌类型');
}
// Inject user data into request
request.user = {
userId: payload.sub,
partyId: payload.partyId,
tokenType: payload.type,
} as CurrentUserData;
return true;
} catch (error) {
if (error instanceof UnauthorizedException) {
throw error;
}
this.logger.warn(`Token verification failed: ${error.message}`);
throw new UnauthorizedException('令牌无效或已过期');
}
}
private extractTokenFromHeader(request: any): string | undefined {
const authHeader = request.headers.authorization;
if (!authHeader) {
return undefined;
}
const [type, token] = authHeader.split(' ');
return type === 'Bearer' ? token : undefined;
}
}