@@ -3,6 +3,7 @@ import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||
@@ -16,27 +17,37 @@ import * as jwt from 'jsonwebtoken';
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
private jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
||||
private readonly logger = new Logger('JwtAuthGuard');
|
||||
|
||||
constructor() {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request & { identity?: IdentityData }>();
|
||||
|
||||
const authHeader = request.headers.authorization;
|
||||
let token: string | undefined;
|
||||
|
||||
if (!authHeader) {
|
||||
throw new UnauthorizedException('Authorization header is missing.');
|
||||
const authHeader = request.headers.authorization;
|
||||
if (authHeader) {
|
||||
const [type, t] = authHeader.split(' ');
|
||||
if (type === 'Bearer' && t) token = t;
|
||||
}
|
||||
|
||||
const [type, token] = authHeader.split(' ');
|
||||
// fallback to cookie if no Authorization header
|
||||
if (!token) {
|
||||
token = (request as any).cookies?.raylab_jwt;
|
||||
this.logger.debug(`No Authorization header. Trying cookie. cookiePresent=${!!(request as any).cookies} tokenFromCookie=${!!token}`);
|
||||
} else {
|
||||
this.logger.debug('Authorization header found. Using Bearer token.');
|
||||
}
|
||||
|
||||
if (type !== 'Bearer' || !token) {
|
||||
throw new UnauthorizedException('Invalid authorization header.');
|
||||
if (!token) {
|
||||
this.logger.debug('No token found in Authorization header or cookie.');
|
||||
throw new UnauthorizedException('Authorization token is missing.');
|
||||
}
|
||||
|
||||
const jwksUri = process.env.AUTHENTIK_JWKS_URI;
|
||||
|
||||
// First try verifying with external JWKS (Authentik)
|
||||
// First try verifying with external JWKS (Authentik)
|
||||
if (jwksUri) {
|
||||
try {
|
||||
if (!this.jwks) this.jwks = createRemoteJWKSet(new URL(jwksUri));
|
||||
@@ -54,15 +65,18 @@ export class JwtAuthGuard implements CanActivate {
|
||||
);
|
||||
|
||||
request.identity = identity;
|
||||
this.logger.debug(`Verified token using external JWKS. sub=${payload.sub}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.logger.debug(`External JWKS verification failed: ${(err as Error).message}`);
|
||||
// ignore and try internal verification
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: verify with internal symmetric secret
|
||||
const secret = process.env.RAYLAB_JWT_SECRET;
|
||||
const secret = process.env.RAYLAB_JWT_SECRET;
|
||||
if (!secret) {
|
||||
this.logger.error('RAYLAB_JWT_SECRET is not configured.');
|
||||
throw new UnauthorizedException('Invalid or expired token.');
|
||||
}
|
||||
|
||||
@@ -77,8 +91,10 @@ export class JwtAuthGuard implements CanActivate {
|
||||
);
|
||||
|
||||
request.identity = identity;
|
||||
this.logger.debug(`Verified token using internal secret. sub=${payload.sub}`);
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
this.logger.debug(`Internal token verification failed: ${(err as Error).message}`);
|
||||
throw new UnauthorizedException('Invalid or expired token.');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user