@@ -7,21 +7,30 @@ import {
77 SetMetadata ,
88} from '@nestjs/common' ;
99import { Reflector } from '@nestjs/core' ;
10+ import { Request } from 'express' ;
1011import { JwtService } from '../modules/global/jwt.service' ;
1112import { SCOPES_KEY } from '../decorators/scopes.decorator' ;
13+ import { LoggerService } from '../modules/global/logger.service' ;
1214import { UserRole } from '../enums/userRole.enum' ;
1315
1416export const ROLES_KEY = 'roles' ;
1517export const Roles = ( ...roles : UserRole [ ] ) => SetMetadata ( ROLES_KEY , roles ) ;
1618
1719@Injectable ( )
1820export class TokenRolesGuard implements CanActivate {
21+ private readonly logger = LoggerService . forRoot ( TokenRolesGuard . name ) ;
22+
1923 constructor (
2024 private reflector : Reflector ,
2125 private jwtService : JwtService ,
2226 ) { }
2327
2428 canActivate ( context : ExecutionContext ) : boolean {
29+ const handler = context . getHandler ?.( ) ;
30+ const controllerClass = context . getClass ?.( ) ;
31+ const controllerName = controllerClass ?. name || 'UnknownController' ;
32+ const handlerName = handler ?. name || 'UnknownHandler' ;
33+
2534 // Get required roles and scopes from decorators
2635 const requiredRoles =
2736 this . reflector . get < UserRole [ ] > ( ROLES_KEY , context . getHandler ( ) ) || [ ] ;
@@ -31,18 +40,49 @@ export class TokenRolesGuard implements CanActivate {
3140
3241 // If no roles or scopes are required, allow access
3342 if ( requiredRoles . length === 0 && requiredScopes . length === 0 ) {
43+ this . logger . log ( {
44+ message : 'No roles or scopes required, allowing request' ,
45+ controllerName,
46+ handlerName,
47+ } ) ;
3448 return true ;
3549 }
3650
3751 const request = context . switchToHttp ( ) . getRequest < Request > ( ) ;
52+ const requestMeta = this . buildRequestLogMeta (
53+ request ,
54+ controllerName ,
55+ handlerName ,
56+ ) ;
57+
58+ this . logger . log ( {
59+ message : 'Evaluating token roles guard' ,
60+ ...requestMeta ,
61+ requiredRoles,
62+ requiredScopes,
63+ } ) ;
3864
3965 try {
4066 const user = request [ 'user' ] ;
4167
4268 if ( ! user && ( requiredRoles . length || requiredScopes . length ) ) {
69+ this . logger . warn ( {
70+ message : 'Rejecting request due to missing user payload' ,
71+ ...requestMeta ,
72+ hasUser : false ,
73+ } ) ;
4374 throw new UnauthorizedException ( 'Missing or invalid token!' ) ;
4475 }
4576
77+ this . logger . log ( {
78+ message : 'User payload extracted from request' ,
79+ ...requestMeta ,
80+ userId : user ?. userId ,
81+ isMachine : Boolean ( user ?. isMachine ) ,
82+ roles : user ?. roles ,
83+ scopes : user ?. scopes ,
84+ } ) ;
85+
4686 const normalizedRequiredRoles = requiredRoles . map ( ( role ) =>
4787 String ( role ) . trim ( ) . toLowerCase ( ) ,
4888 ) ;
@@ -51,10 +91,22 @@ export class TokenRolesGuard implements CanActivate {
5191 if ( normalizedRequiredRoles . length > 0 ) {
5292 const normalizedUserRoles = this . normalizeUserRoles ( user . roles ) ;
5393
94+ this . logger . log ( {
95+ message : 'Checking role-based permissions' ,
96+ ...requestMeta ,
97+ normalizedRequiredRoles,
98+ normalizedUserRoles,
99+ } ) ;
100+
54101 const hasRole = normalizedRequiredRoles . some ( ( role ) =>
55102 normalizedUserRoles . includes ( role ) ,
56103 ) ;
57104 if ( hasRole ) {
105+ this . logger . log ( {
106+ message : 'Access granted via role-based authorization' ,
107+ ...requestMeta ,
108+ normalizedRequiredRoles,
109+ } ) ;
58110 return true ;
59111 }
60112
@@ -66,6 +118,13 @@ export class TokenRolesGuard implements CanActivate {
66118 user ,
67119 )
68120 ) {
121+ this . logger . log ( {
122+ message :
123+ 'Access granted via submission list challenge fallback rule' ,
124+ ...requestMeta ,
125+ challengeId : request ?. query ?. challengeId ,
126+ userId : user ?. userId ,
127+ } ) ;
69128 return true ;
70129 }
71130 }
@@ -75,7 +134,20 @@ export class TokenRolesGuard implements CanActivate {
75134 const hasScope = requiredScopes . some ( ( scope ) =>
76135 user . scopes ? user . scopes . includes ( scope ) : false ,
77136 ) ;
137+
138+ this . logger . log ( {
139+ message : 'Checking scope-based permissions' ,
140+ ...requestMeta ,
141+ requiredScopes,
142+ tokenScopes : user . scopes ,
143+ hasScope,
144+ } ) ;
145+
78146 if ( hasScope ) {
147+ this . logger . log ( {
148+ message : 'Access granted via scope-based authorization' ,
149+ ...requestMeta ,
150+ } ) ;
79151 return true ;
80152 }
81153 }
@@ -89,10 +161,22 @@ export class TokenRolesGuard implements CanActivate {
89161 requiredRoles . length > 0 &&
90162 requiredScopes . length === 0
91163 ) {
164+ this . logger . warn ( {
165+ message :
166+ 'M2M token detected without role permissions for role-only endpoint' ,
167+ ...requestMeta ,
168+ tokenScopes : user . scopes ,
169+ } ) ;
92170 throw new ForbiddenException ( 'M2M token not allowed for this endpoint' ) ;
93171 }
94172
95173 // Access denied - neither roles nor scopes match
174+ this . logger . warn ( {
175+ message : 'Access denied due to insufficient permissions' ,
176+ ...requestMeta ,
177+ requiredRoles,
178+ requiredScopes,
179+ } ) ;
96180 throw new ForbiddenException ( 'Insufficient permissions' ) ;
97181 } catch ( error ) {
98182 if (
@@ -101,6 +185,15 @@ export class TokenRolesGuard implements CanActivate {
101185 ) {
102186 throw error ;
103187 }
188+ this . logger . error (
189+ {
190+ message : 'Unexpected error validating token roles' ,
191+ ...requestMeta ,
192+ error :
193+ error instanceof Error ? error . message : String ( error ?? 'error' ) ,
194+ } ,
195+ error instanceof Error ? error . stack : undefined ,
196+ ) ;
104197 throw new UnauthorizedException ( 'Invalid token' ) ;
105198 }
106199 }
@@ -192,4 +285,33 @@ export class TokenRolesGuard implements CanActivate {
192285
193286 return Array . from ( normalizedRoles ) ;
194287 }
288+
289+ private buildRequestLogMeta (
290+ request : any ,
291+ controllerName : string ,
292+ handlerName : string ,
293+ ) {
294+ const headers = ( request ?. headers || { } ) as Record <
295+ string ,
296+ string | string [ ] | undefined
297+ > ;
298+ const correlationIdCandidate =
299+ headers [ 'x-request-id' ] ||
300+ headers [ 'x-correlation-id' ] ||
301+ headers [ 'x-trace-id' ] ;
302+ const method =
303+ typeof request ?. method === 'string'
304+ ? request . method . toUpperCase ( )
305+ : undefined ;
306+
307+ return {
308+ controllerName,
309+ handlerName,
310+ method,
311+ path : request ?. originalUrl || request ?. url || request ?. path ,
312+ correlationId : Array . isArray ( correlationIdCandidate )
313+ ? correlationIdCandidate [ 0 ]
314+ : correlationIdCandidate ,
315+ } ;
316+ }
195317}
0 commit comments