Skip to content

Commit a5d0914

Browse files
committed
Add auth guard for M2M / admin token for topcoder reports
1 parent 07726f4 commit a5d0914

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

src/app-constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const Scopes = {
44
TopgearChallenge: "reports:topgear-challenge",
55
TopgearCancelledChallenge: "reports:topgear-cancelled-challenge",
66
AllReports: "reports:all",
7+
TopcoderReports: "reports:topcoder",
78
TopgearChallengeTechnology: "reports:topgear-challenge-technology",
89
TopgearChallengeStatsByUser: "reports:topgear-challenge-stats-by-user",
910
TopgearChallengeRegistrantDetails:
@@ -18,3 +19,7 @@ export const Scopes = {
1819
SubmissionLinks: "reports:challenge-submission-links",
1920
},
2021
};
22+
23+
export const UserRoles = {
24+
Admin: "Administrator",
25+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
CanActivate,
3+
ExecutionContext,
4+
ForbiddenException,
5+
Injectable,
6+
UnauthorizedException,
7+
} from "@nestjs/common";
8+
9+
import { Scopes, UserRoles } from "../../app-constants";
10+
11+
@Injectable()
12+
export class TopcoderReportsGuard implements CanActivate {
13+
private static readonly adminRoles = new Set(
14+
Object.values(UserRoles).map((role) => role.toLowerCase()),
15+
);
16+
17+
canActivate(context: ExecutionContext): boolean {
18+
const request = context.switchToHttp().getRequest();
19+
const authUser = request.authUser;
20+
21+
if (!authUser) {
22+
throw new UnauthorizedException("You are not authenticated.");
23+
}
24+
25+
if (authUser.isMachine) {
26+
const scopes: string[] = authUser.scopes ?? [];
27+
if (this.hasRequiredScope(scopes)) {
28+
return true;
29+
}
30+
31+
throw new ForbiddenException(
32+
"You do not have the required permissions to access this resource.",
33+
);
34+
}
35+
36+
const roles: string[] = authUser.roles ?? [];
37+
if (this.isAdmin(roles)) {
38+
return true;
39+
}
40+
41+
throw new ForbiddenException(
42+
"You do not have the required permissions to access this resource.",
43+
);
44+
}
45+
46+
private hasRequiredScope(scopes: string[]): boolean {
47+
const normalizedScopes = scopes.map((scope) => scope?.toLowerCase());
48+
return (
49+
normalizedScopes.includes(Scopes.TopcoderReports.toLowerCase()) ||
50+
normalizedScopes.includes(Scopes.AllReports.toLowerCase())
51+
);
52+
}
53+
54+
private isAdmin(roles: string[]): boolean {
55+
return roles.some((role) =>
56+
TopcoderReportsGuard.adminRoles.has(role?.toLowerCase()),
57+
);
58+
}
59+
}

src/reports/topcoder/topcoder-reports.controller.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { Controller, Get, Query, Res } from "@nestjs/common";
2-
import { ApiOperation, ApiTags } from "@nestjs/swagger";
1+
import { Controller, Get, Query, Res, UseGuards } from "@nestjs/common";
2+
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
33
import { Response } from "express";
44
import { TopcoderReportsService } from "./topcoder-reports.service";
55
import { RegistrantCountriesQueryDto } from "./dto/registrant-countries.dto";
6+
import { TopcoderReportsGuard } from "../../auth/guards/topcoder-reports.guard";
67

78
@ApiTags("Topcoder Reports")
9+
@ApiBearerAuth()
10+
@UseGuards(TopcoderReportsGuard)
811
@Controller("/topcoder")
912
export class TopcoderReportsController {
1013
constructor(private readonly reports: TopcoderReportsService) {}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Module } from "@nestjs/common";
2-
import { TopcoderReportsController } from "./topcoder-reports.controller";
32
import { TopcoderReportsService } from "./topcoder-reports.service";
43
import { SqlLoaderService } from "../../common/sql-loader.service";
4+
import { TopcoderReportsController } from "./topcoder-reports.controller";
5+
import { TopcoderReportsGuard } from "../../auth/guards/topcoder-reports.guard";
56

67
@Module({
78
controllers: [TopcoderReportsController],
8-
providers: [TopcoderReportsService, SqlLoaderService],
9+
providers: [TopcoderReportsService, SqlLoaderService, TopcoderReportsGuard],
910
})
1011
export class TopcoderReportsModule {}

0 commit comments

Comments
 (0)