From 5f8a5113e733bbbc34452ef6f589e347598ad9ea Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 30 Sep 2025 10:09:58 +0300 Subject: [PATCH 1/8] debug m2m --- src/shared/topcoder/topcoder-m2m.service.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/shared/topcoder/topcoder-m2m.service.ts b/src/shared/topcoder/topcoder-m2m.service.ts index 06cc33a..7aa7d90 100644 --- a/src/shared/topcoder/topcoder-m2m.service.ts +++ b/src/shared/topcoder/topcoder-m2m.service.ts @@ -39,6 +39,19 @@ export class TopcoderM2MService { const jsonResponse = await response.json(); const m2mToken = jsonResponse.access_token as string; + this.logger.log( + 'M2M', + tokenURL, + JSON.stringify({ + auth0_url: `${ENV_CONFIG.AUTH0_M2M_TOKEN_URL}/oauth/token`, + client_id: ENV_CONFIG.AUTH0_M2M_CLIENT_ID, + client_secret: ENV_CONFIG.AUTH0_M2M_SECRET, + audience: ENV_CONFIG.AUTH0_M2M_AUDIENCE, + grant_type: ENV_CONFIG.AUTH0_M2M_GRANT_TYPE, + }), + m2mToken, + ); + return m2mToken; } catch (error) { this.logger.error('Failed fetching TC M2M Token!', error); From 6ea88157357eefc663b570d852d04ddba88503a8 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 30 Sep 2025 11:00:15 +0300 Subject: [PATCH 2/8] debug logs --- src/shared/topcoder/topcoder-m2m.service.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/shared/topcoder/topcoder-m2m.service.ts b/src/shared/topcoder/topcoder-m2m.service.ts index 7aa7d90..86edb67 100644 --- a/src/shared/topcoder/topcoder-m2m.service.ts +++ b/src/shared/topcoder/topcoder-m2m.service.ts @@ -42,14 +42,11 @@ export class TopcoderM2MService { this.logger.log( 'M2M', tokenURL, - JSON.stringify({ - auth0_url: `${ENV_CONFIG.AUTH0_M2M_TOKEN_URL}/oauth/token`, - client_id: ENV_CONFIG.AUTH0_M2M_CLIENT_ID, - client_secret: ENV_CONFIG.AUTH0_M2M_SECRET, - audience: ENV_CONFIG.AUTH0_M2M_AUDIENCE, - grant_type: ENV_CONFIG.AUTH0_M2M_GRANT_TYPE, - }), m2mToken, + jsonResponse, + response.status, + response.statusText, + response.ok, ); return m2mToken; From 8527d44243d24785a00ea276f4f29c40b1ca5658 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 30 Sep 2025 11:16:27 +0300 Subject: [PATCH 3/8] remove debug logs, add m2m error handling/logging --- src/shared/topcoder/topcoder-m2m.service.ts | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/shared/topcoder/topcoder-m2m.service.ts b/src/shared/topcoder/topcoder-m2m.service.ts index 86edb67..cd6fcc6 100644 --- a/src/shared/topcoder/topcoder-m2m.service.ts +++ b/src/shared/topcoder/topcoder-m2m.service.ts @@ -36,19 +36,27 @@ export class TopcoderM2MService { }), }); + if (!response.ok) { + let jsonError: any; + try { + jsonError = await response.json(); + } catch { + jsonError = null; + } + + this.logger.error( + 'Failed to fetch M2M token', + tokenURL, + response.status, + response.statusText, + jsonError, + ); + return undefined; + } + const jsonResponse = await response.json(); const m2mToken = jsonResponse.access_token as string; - this.logger.log( - 'M2M', - tokenURL, - m2mToken, - jsonResponse, - response.status, - response.statusText, - response.ok, - ); - return m2mToken; } catch (error) { this.logger.error('Failed fetching TC M2M Token!', error); From 9ab6a6d79ee009c1285a4f7358b83ec563d87fee Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Tue, 7 Oct 2025 12:50:38 +1100 Subject: [PATCH 4/8] Allow for admins to create payments, used in system-admin app --- src/api/winnings/winnings.controller.ts | 6 ++-- src/core/auth/guards/auth.guard.ts | 41 +++++++++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/api/winnings/winnings.controller.ts b/src/api/winnings/winnings.controller.ts index 81a64ff..4da3765 100644 --- a/src/api/winnings/winnings.controller.ts +++ b/src/api/winnings/winnings.controller.ts @@ -7,8 +7,8 @@ import { ApiBearerAuth, } from '@nestjs/swagger'; -import { M2mScope } from 'src/core/auth/auth.constants'; -import { M2M, AllowedM2mScope, User } from 'src/core/auth/decorators'; +import { M2mScope, Role } from 'src/core/auth/auth.constants'; +import { AllowedM2mScope, User, Roles } from 'src/core/auth/decorators'; import { ResponseDto, ResponseStatusType } from 'src/dto/api-response.dto'; import { UserInfo } from 'src/dto/user.type'; import { @@ -29,8 +29,8 @@ export class WinningsController { ) {} @Post() - @M2M() @AllowedM2mScope(M2mScope.CreatePayments) + @Roles(Role.PaymentAdmin, Role.PaymentEditor) @ApiOperation({ summary: 'Create winning with payments.', description: 'User must have "create:payments" scope to access.', diff --git a/src/core/auth/guards/auth.guard.ts b/src/core/auth/guards/auth.guard.ts index 921ae5b..b96245e 100644 --- a/src/core/auth/guards/auth.guard.ts +++ b/src/core/auth/guards/auth.guard.ts @@ -23,7 +23,7 @@ export class AuthGuard implements CanActivate { if (isPublic) return true; const req = context.switchToHttp().getRequest(); - const isM2M = this.reflector.getAllAndOverride(IS_M2M_KEY, [ + const routeM2MOnly = this.reflector.getAllAndOverride(IS_M2M_KEY, [ context.getHandler(), context.getClass(), ]); @@ -36,24 +36,45 @@ export class AuthGuard implements CanActivate { }; } - // Regular authentication - check that we have user's email and have verified the id token - if (!isM2M) { + const tokenIsM2M = Boolean(req.m2mTokenScope); + + // If route explicitly requires M2M, enforce M2M + scope + if (routeM2MOnly) { + if (!req.idTokenVerified || !tokenIsM2M) { + throw new UnauthorizedException(); + } + + const allowedM2mScopes = this.reflector.getAllAndOverride( + SCOPES_KEY, + [context.getHandler(), context.getClass()], + ); + const reqScopes = String(req.m2mTokenScope || '').split(' '); + return reqScopes.some((s) => allowedM2mScopes.includes(s as M2mScope)); + } + + // Hybrid (default) route behavior: allow either + // - Verified user JWT (email present), OR + // - Verified M2M token but only if scope matches when scopes are declared on the route + + // User JWT branch + if (!tokenIsM2M) { return Boolean(req.email && req.idTokenVerified); } - // M2M authentication - check scopes - if (!req.idTokenVerified || !req.m2mTokenScope) + // M2M branch on non-M2M-only route: require declared scopes, otherwise deny + if (!req.idTokenVerified) { throw new UnauthorizedException(); + } const allowedM2mScopes = this.reflector.getAllAndOverride( SCOPES_KEY, [context.getHandler(), context.getClass()], ); - - const reqScopes = req.m2mTokenScope.split(' '); - if (reqScopes.some((reqScope) => allowedM2mScopes.includes(reqScope))) { - return true; + if (!allowedM2mScopes || allowedM2mScopes.length === 0) { + // No scopes declared for this route, do not allow M2M by default + return false; } - return false; + const reqScopes = String(req.m2mTokenScope || '').split(' '); + return reqScopes.some((s) => allowedM2mScopes.includes(s as M2mScope)); } } From 6e03959f6be2fbdcbdc89daf8667982913435086 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Tue, 7 Oct 2025 13:06:14 +1100 Subject: [PATCH 5/8] Build fix --- src/api/winnings/winnings.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/winnings/winnings.controller.ts b/src/api/winnings/winnings.controller.ts index 4da3765..d1fe3ec 100644 --- a/src/api/winnings/winnings.controller.ts +++ b/src/api/winnings/winnings.controller.ts @@ -8,7 +8,7 @@ import { } from '@nestjs/swagger'; import { M2mScope, Role } from 'src/core/auth/auth.constants'; -import { AllowedM2mScope, User, Roles } from 'src/core/auth/decorators'; +import { AllowedM2mScope, User, Roles, M2M } from 'src/core/auth/decorators'; import { ResponseDto, ResponseStatusType } from 'src/dto/api-response.dto'; import { UserInfo } from 'src/dto/user.type'; import { From 3b08a9a57b27d7da8991a5ab37ada8861d4f10bd Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 08:27:21 +0300 Subject: [PATCH 6/8] fix roles check --- src/core/auth/guards/roles.guard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/auth/guards/roles.guard.ts b/src/core/auth/guards/roles.guard.ts index 8145cce..53f3494 100644 --- a/src/core/auth/guards/roles.guard.ts +++ b/src/core/auth/guards/roles.guard.ts @@ -17,7 +17,7 @@ export class RolesGuard implements CanActivate { } const request = context.switchToHttp().getRequest(); - const { auth0User } = request; + const { auth0User = {} } = request; const userRoles = Object.keys(auth0User).reduce((roles, key) => { if (key.match(/claims\/roles$/gi)) { return auth0User[key] as string[]; From 72321adb8e99e4e8343a90bfe7a256a98d9e4ea5 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 09:23:48 +0300 Subject: [PATCH 7/8] do not validate m2m token in roles guard --- src/core/auth/guards/roles.guard.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/auth/guards/roles.guard.ts b/src/core/auth/guards/roles.guard.ts index 53f3494..e09d565 100644 --- a/src/core/auth/guards/roles.guard.ts +++ b/src/core/auth/guards/roles.guard.ts @@ -17,6 +17,11 @@ export class RolesGuard implements CanActivate { } const request = context.switchToHttp().getRequest(); + const tokenIsM2M = Boolean(request.m2mTokenScope); + if (tokenIsM2M) { + return true; + } + const { auth0User = {} } = request; const userRoles = Object.keys(auth0User).reduce((roles, key) => { if (key.match(/claims\/roles$/gi)) { From f8ceace83648a00ea53537724ad619a8066f1bae Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Oct 2025 09:25:36 +0300 Subject: [PATCH 8/8] update logic for roles guard --- src/core/auth/guards/roles.guard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/auth/guards/roles.guard.ts b/src/core/auth/guards/roles.guard.ts index e09d565..9f6126e 100644 --- a/src/core/auth/guards/roles.guard.ts +++ b/src/core/auth/guards/roles.guard.ts @@ -19,7 +19,7 @@ export class RolesGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const tokenIsM2M = Boolean(request.m2mTokenScope); if (tokenIsM2M) { - return true; + return Boolean(request.idTokenVerified); } const { auth0User = {} } = request;