Skip to content

Commit d6c9749

Browse files
committed
Fixes for local issues seen with JWT validation, in case it starts to affect dev and prod
1 parent 89e5652 commit d6c9749

File tree

4 files changed

+490
-16
lines changed

4 files changed

+490
-16
lines changed

src/shared/config/auth.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const AuthConfig = {
88
// JSON array string of allowed issuers
99
validIssuers:
1010
process.env.VALID_ISSUERS ||
11-
'["https://testsachin.topcoder-dev.com/","https://test-sachin-rs256.auth0.com/","https://api.topcoder.com","https://api.topcoder-dev.com","https://topcoder-dev.auth0.com/", "https://auth.topcoder-dev.com/"]',
11+
'["https://testsachin.topcoder-dev.com/","https://test-sachin-rs256.auth0.com/","https://api.topcoder.com","https://api.topcoder-dev.com","https://topcoder-dev.auth0.com/","https://auth.topcoder-dev.com/","https://topcoder.auth0.com/","https://auth.topcoder.com/"]',
1212

1313
// Legacy JWT configuration (kept for backward compatibility)
1414
jwt: {

src/shared/guards/tokenRoles.guard.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,30 @@ import {
77
SetMetadata,
88
} from '@nestjs/common';
99
import { Reflector } from '@nestjs/core';
10+
import { Request } from 'express';
1011
import { JwtService } from '../modules/global/jwt.service';
1112
import { SCOPES_KEY } from '../decorators/scopes.decorator';
13+
import { LoggerService } from '../modules/global/logger.service';
1214
import { UserRole } from '../enums/userRole.enum';
1315

1416
export const ROLES_KEY = 'roles';
1517
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);
1618

1719
@Injectable()
1820
export 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

Comments
 (0)