Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | 13x 13x 13x 13x 13x 13x 13x 14x 14x 14x 14x 3x 3x 3x 3x 1x 1x 2x 2x 2x 2x 1x 1x 1x | import { Injectable, CanActivate, ExecutionContext, Logger, Optional } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { WsException } from '@nestjs/websockets';
import { PrismaService } from '@app/modules/prisma/prisma.service';
import { Algorithm } from 'jsonwebtoken';
const IS_TEST = process.env.NODE_ENV === 'test' || process.env.TEST_MODE === '1';
@Injectable()
export class WsJwtGuard implements CanActivate {
private readonly logger = new Logger(WsJwtGuard.name);
constructor(
private jwtService: JwtService,
private config: ConfigService,
@Optional() private prisma?: PrismaService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
const client = context.switchToWs().getClient();
const token =
client.handshake.auth.token ||
client.handshake.headers.authorization?.replace('Bearer ', '');
if (!token) {
this.logger.warn('WsJwtGuard: No token provided in WebSocket connection');
throw new WsException('Unauthorized: No token provided');
}
// In test mode, use simplified verification
if (IS_TEST || !this.prisma) {
const payload = await this.jwtService.verifyAsync(token);
client.data.user = {
id: payload.sub,
sub: payload.sub,
email: payload.email,
role: payload.role || 'USER',
};
return true;
}
// Production mode: Verify with fixed public key
const publicKey = this.config.get<string>('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n');
Iif (!publicKey) {
this.logger.error('WsJwtGuard: JWT_PUBLIC_KEY not configured');
throw new WsException('Unauthorized: Configuration error');
}
// Verify token with public key and enforce issuer/audience
const algorithm = (this.config.get<string>('JWT_ALGORITHM') || 'RS256') as Algorithm;
const issuer = this.config.get<string>('JWT_ISSUER');
const audience = this.config.get<string>('JWT_AUDIENCE');
const payload = await this.jwtService.verifyAsync(token, {
publicKey,
algorithms: [algorithm],
issuer,
audience,
});
// Validate user exists and token version matches
const user = await this.prisma.client.user.findUnique({
where: { id: payload.sub },
select: {
id: true,
email: true,
role: true,
tokenVersion: true,
}
});
Iif (!user || user.tokenVersion !== payload.tokenVersion) {
this.logger.warn(`WsJwtGuard: Invalid user or token version mismatch for user ${payload.sub}`);
throw new WsException('Unauthorized: Invalid or expired token');
}
// Attach complete user context to socket
client.data.user = {
id: user.id,
sub: user.id,
email: user.email,
role: user.role,
};
return true;
} catch (error) {
const msg = (error as any)?.message ?? String(error);
this.logger.error(`WsJwtGuard: Token verification failed: ${msg}`);
throw new WsException('Unauthorized: Invalid token');
}
}
}
|