Skip to content

Commit 9888ef7

Browse files
committed
emerg: enable user-specific additional role grants
1 parent f1f3488 commit 9888ef7

File tree

4 files changed

+78
-3
lines changed

4 files changed

+78
-3
lines changed

src/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;
88

99
type GroupRoleMapping = Record<string, readonly AppRoles[]>;
1010
type AzureRoleMapping = Record<string, readonly AppRoles[]>;
11+
type UserRoleMapping = Record<string, readonly AppRoles[]>;
1112

1213
export type ConfigType = {
1314
GroupRoleMapping: GroupRoleMapping;
1415
AzureRoleMapping: AzureRoleMapping;
16+
UserRoleMapping: UserRoleMapping;
1517
ValidCorsOrigins: ValueOrArray<OriginType> | OriginFunction;
1618
AadValidClientId: string;
1719
};
@@ -51,6 +53,9 @@ const environmentConfig: EnvironmentConfigType = {
5153
"0": allAppRoles, // Dummy Group for development only
5254
"1": [], // Dummy Group for development only
5355
},
56+
UserRoleMapping: {
57+
"[email protected]": [AppRoles.TICKET_SCANNER],
58+
},
5459
AzureRoleMapping: { AutonomousWriters: [AppRoles.EVENTS_MANAGER] },
5560
ValidCorsOrigins: [
5661
"http://localhost:3000",
@@ -71,6 +76,9 @@ const environmentConfig: EnvironmentConfigType = {
7176
AppRoles.SSO_INVITE_USER,
7277
], // Exec
7378
},
79+
UserRoleMapping: {
80+
"[email protected]": [AppRoles.TICKET_SCANNER],
81+
},
7482
AzureRoleMapping: { AutonomousWriters: [AppRoles.EVENTS_MANAGER] },
7583
ValidCorsOrigins: [
7684
"https://acm.illinois.edu",

src/plugins/auth.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ const authPlugin: FastifyPluginAsync = async (fastify, _options) => {
192192
});
193193
}
194194
}
195+
// add user-specific role overrides
196+
if (request.username && fastify.environmentConfig.UserRoleMapping) {
197+
if (fastify.environmentConfig["UserRoleMapping"][request.username]) {
198+
for (const role of fastify.environmentConfig["UserRoleMapping"][
199+
request.username
200+
]) {
201+
userRoles.add(role);
202+
}
203+
}
204+
}
195205
if (
196206
expectedRoles.size > 0 &&
197207
intersection(userRoles, expectedRoles).size === 0

tests/unit/auth.test.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import {
55
} from "@aws-sdk/client-secrets-manager";
66
import { mockClient } from "aws-sdk-client-mock";
77
import init from "../../src/index.js";
8-
import { secretJson, secretObject, jwtPayload } from "./secret.testdata.js";
8+
import {
9+
secretJson,
10+
secretObject,
11+
jwtPayload,
12+
jwtPayloadNoGroups,
13+
} from "./secret.testdata.js";
914
import jwt from "jsonwebtoken";
10-
import { allAppRoles } from "../../src/roles.js";
15+
import { allAppRoles, AppRoles } from "../../src/roles.js";
1116

1217
const ddbMock = mockClient(SecretsManagerClient);
1318

@@ -30,9 +35,16 @@ export function createJwt(date?: Date, group?: string) {
3035
}
3136
return jwt.sign(modifiedPayload, jwt_secret, { algorithm: "HS256" });
3237
}
38+
39+
export function createJwtNoGroups() {
40+
const modifiedPayload = jwtPayloadNoGroups;
41+
return jwt.sign(modifiedPayload, jwt_secret, { algorithm: "HS256" });
42+
}
43+
3344
vi.stubEnv("JwtSigningKey", jwt_secret);
3445

3546
const testJwt = createJwt();
47+
const testJwtNoGroups = createJwtNoGroups();
3648

3749
test("Test happy path", async () => {
3850
ddbMock.on(GetSecretValueCommand).resolves({
@@ -52,3 +64,22 @@ test("Test happy path", async () => {
5264
roles: allAppRoles,
5365
});
5466
});
67+
68+
test("Test user-specific role grants", async () => {
69+
ddbMock.on(GetSecretValueCommand).resolves({
70+
SecretString: secretJson,
71+
});
72+
const response = await app.inject({
73+
method: "GET",
74+
url: "/api/v1/protected",
75+
headers: {
76+
authorization: `Bearer ${testJwtNoGroups}`,
77+
},
78+
});
79+
expect(response.statusCode).toBe(200);
80+
const jsonBody = await response.json();
81+
expect(jsonBody).toEqual({
82+
username: "[email protected]",
83+
roles: [AppRoles.TICKET_SCANNER],
84+
});
85+
});

tests/unit/secret.testdata.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,30 @@ const jwtPayload = {
3636
ver: "1.0",
3737
};
3838

39-
export { secretJson, secretObject, jwtPayload };
39+
const jwtPayloadNoGroups = {
40+
aud: "custom_jwt",
41+
iss: "custom_jwt",
42+
iat: Math.floor(Date.now() / 1000),
43+
nbf: Math.floor(Date.now() / 1000),
44+
exp: Math.floor(Date.now() / 1000) + 3600 * 24, // Token expires after 24 hour
45+
acr: "1",
46+
aio: "AXQAi/8TAAAA",
47+
amr: ["pwd"],
48+
appid: "your-app-id",
49+
appidacr: "1",
50+
51+
groups: [],
52+
idp: "https://login.microsoftonline.com",
53+
ipaddr: "192.168.1.1",
54+
name: "John Doe",
55+
oid: "00000000-0000-0000-0000-000000000000",
56+
rh: "rh-value",
57+
scp: "user_impersonation",
58+
sub: "subject",
59+
tid: "tenant-id",
60+
unique_name: "[email protected]",
61+
uti: "uti-value",
62+
ver: "1.0",
63+
};
64+
65+
export { secretJson, secretObject, jwtPayload, jwtPayloadNoGroups };

0 commit comments

Comments
 (0)