Skip to content

Commit 0007da0

Browse files
committed
CCM-10422: init
1 parent 90e120b commit 0007da0

File tree

2 files changed

+96
-18
lines changed

2 files changed

+96
-18
lines changed

utils/utils/src/__tests__/lambda-cognito-authorizer.test.ts

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,24 +107,60 @@ describe('LambdaCognitoAuthorizer', () => {
107107
expect(mockLogger.logMessages).toEqual([]);
108108
});
109109

110-
test('returns success on valid token without notify-client-id', async () => {
110+
test('returns success on valid token when expected resource is specified', async () => {
111111
const jwt = sign(
112112
{
113113
token_use: 'access',
114114
client_id: 'user-pool-client-id',
115115
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
116+
'nhs-notify:client-id': 'nhs-notify-client-id',
116117
},
117118
'key',
118119
{
119120
keyid: 'key-id',
120121
}
121122
);
122123

123-
const res = await authorizer.authorize(userPoolId, userPoolClientId, jwt);
124+
const res = await authorizer.authorize(
125+
userPoolId,
126+
userPoolClientId,
127+
jwt,
128+
'nhs-notify-client-id'
129+
);
130+
131+
expect(res).toEqual({
132+
success: true,
133+
subject: 'sub',
134+
clientId: 'nhs-notify-client-id',
135+
});
136+
expect(mockLogger.logMessages).toEqual([]);
137+
});
138+
139+
test('returns success on valid token when expected resource owner is the user (not client)', async () => {
140+
const jwt = sign(
141+
{
142+
token_use: 'access',
143+
client_id: 'user-pool-client-id',
144+
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
145+
'nhs-notify:client-id': 'nhs-notify-client-id',
146+
},
147+
'key',
148+
{
149+
keyid: 'key-id',
150+
}
151+
);
152+
153+
const res = await authorizer.authorize(
154+
userPoolId,
155+
userPoolClientId,
156+
jwt,
157+
'sub'
158+
);
124159

125160
expect(res).toEqual({
126161
success: true,
127162
subject: 'sub',
163+
clientId: 'nhs-notify-client-id',
128164
});
129165
expect(mockLogger.logMessages).toEqual([]);
130166
});
@@ -152,7 +188,7 @@ describe('LambdaCognitoAuthorizer', () => {
152188
token_use: 'access',
153189
client_id: 'user-pool-client-id',
154190
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
155-
clientId: 'nhs-notify-client-id',
191+
'nhs-notify:client-id': 'nhs-notify-client-id',
156192
},
157193
'key'
158194
);
@@ -174,7 +210,7 @@ describe('LambdaCognitoAuthorizer', () => {
174210
token_use: 'access',
175211
client_id: 'user-pool-client-id-2',
176212
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
177-
clientId: 'nhs-notify-client-id',
213+
'nhs-notify:client-id': 'nhs-notify-client-id',
178214
},
179215
'key',
180216
{
@@ -200,7 +236,7 @@ describe('LambdaCognitoAuthorizer', () => {
200236
token_use: 'access',
201237
client_id: 'user-pool-client-id',
202238
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id-2',
203-
clientId: 'nhs-notify-client-id',
239+
'nhs-notify:client-id': 'nhs-notify-client-id',
204240
},
205241
'key',
206242
{
@@ -226,7 +262,7 @@ describe('LambdaCognitoAuthorizer', () => {
226262
token_use: 'id',
227263
client_id: 'user-pool-client-id',
228264
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
229-
clientId: 'nhs-notify-client-id',
265+
'nhs-notify:client-id': 'nhs-notify-client-id',
230266
},
231267
'key',
232268
{
@@ -245,6 +281,36 @@ describe('LambdaCognitoAuthorizer', () => {
245281
);
246282
});
247283

284+
test('returns failure when no NHS Notify client ID is present in the access token', async () => {
285+
const jwt = sign(
286+
{
287+
token_use: 'access',
288+
client_id: 'user-pool-client-id',
289+
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
290+
},
291+
'key',
292+
{
293+
keyid: 'key-id',
294+
}
295+
);
296+
297+
const res = await authorizer.authorize(userPoolId, userPoolClientId, jwt);
298+
299+
expect(res).toEqual({ success: false });
300+
expect(mockLogger.logMessages).toContainEqual(
301+
expect.objectContaining({
302+
level: 'error',
303+
message: expect.stringContaining('Failed to authorize'),
304+
issues: expect.arrayContaining([
305+
expect.objectContaining({
306+
path: ['nhs-notify:client-id'],
307+
message: 'Invalid input: expected string, received undefined',
308+
}),
309+
]),
310+
})
311+
);
312+
});
313+
248314
test('returns failure on Cognito not validating the token', async () => {
249315
const cognitoErrorUserPool = 'user-pool-id-cognito-error';
250316

@@ -253,7 +319,7 @@ describe('LambdaCognitoAuthorizer', () => {
253319
token_use: 'access',
254320
client_id: 'user-pool-client-id',
255321
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id-cognito-error',
256-
clientId: 'nhs-notify-client-id',
322+
'nhs-notify:client-id': 'nhs-notify-client-id',
257323
},
258324
'key',
259325
{
@@ -285,7 +351,7 @@ describe('LambdaCognitoAuthorizer', () => {
285351
token_use: 'access',
286352
client_id: 'user-pool-client-id',
287353
iss: `https://cognito-idp.eu-west-2.amazonaws.com/${iss}`,
288-
clientId: 'nhs-notify-client-id',
354+
'nhs-notify:client-id': 'nhs-notify-client-id',
289355
},
290356
'key',
291357
{
@@ -312,7 +378,7 @@ describe('LambdaCognitoAuthorizer', () => {
312378
token_use: 'access',
313379
client_id: 'user-pool-client-id',
314380
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id-cognito-no-sub',
315-
clientId: 'nhs-notify-client-id',
381+
'nhs-notify:client-id': 'nhs-notify-client-id',
316382
},
317383
'key',
318384
{
@@ -335,13 +401,13 @@ describe('LambdaCognitoAuthorizer', () => {
335401
);
336402
});
337403

338-
test('returns failure when sub on Cognito UserAttributes does not match expected subject', async () => {
404+
test('returns failure when sub on Cognito UserAttributes does not match expected subject or clientId', async () => {
339405
const jwt = sign(
340406
{
341407
token_use: 'access',
342408
client_id: 'user-pool-client-id',
343409
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
344-
clientId: 'nhs-notify-client-id',
410+
'nhs-notify:client-id': 'nhs-notify-client-id',
345411
},
346412
'key',
347413
{
@@ -360,7 +426,7 @@ describe('LambdaCognitoAuthorizer', () => {
360426
expect(mockLogger.logMessages).toContainEqual(
361427
expect.objectContaining({
362428
level: 'warn',
363-
message: 'Subject path does not match expected subject',
429+
message: 'Neither subject nor clientId matches expected resource owner',
364430
})
365431
);
366432
});
@@ -372,7 +438,7 @@ describe('LambdaCognitoAuthorizer', () => {
372438
client_id: 'user-pool-client-id',
373439
iss: 'https://cognito-idp.eu-west-2.amazonaws.com/user-pool-id',
374440
exp: 1_640_995_200,
375-
clientId: 'nhs-notify-client-id',
441+
'nhs-notify:client-id': 'nhs-notify-client-id',
376442
},
377443
'key',
378444
{

utils/utils/src/lambda-cognito-authorizer.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const $AccessToken = z.object({
1212
client_id: z.string(),
1313
iss: z.string(),
1414
token_use: z.string(),
15-
'nhs-notify:client-id': z.string().optional(),
15+
'nhs-notify:client-id': z.string(),
1616
});
1717

1818
export class LambdaCognitoAuthorizer {
@@ -25,7 +25,7 @@ export class LambdaCognitoAuthorizer {
2525
userPoolId: string,
2626
userPoolClientId: string,
2727
jwt: string,
28-
expectedSubject?: string
28+
expectedSubjectOrClientId?: string
2929
): Promise<
3030
{ success: true; subject: string; clientId?: string } | { success: false }
3131
> {
@@ -90,14 +90,26 @@ export class LambdaCognitoAuthorizer {
9090
return { success: false };
9191
}
9292

93-
if (expectedSubject !== undefined && expectedSubject !== sub) {
94-
this.logger.warn('Subject path does not match expected subject');
93+
if (
94+
expectedSubjectOrClientId !== undefined &&
95+
(expectedSubjectOrClientId !== sub ||
96+
expectedSubjectOrClientId !== notifyClientId)
97+
) {
98+
this.logger.warn(
99+
'Neither subject nor clientId matches expected resource owner'
100+
);
95101
return { success: false };
96102
}
97103

98104
return { success: true, subject: sub, clientId: notifyClientId };
99105
} catch (error) {
100-
this.logger.error('Failed to authorize:', error);
106+
this.logger
107+
.child(
108+
error instanceof Error && 'issues' in error
109+
? { issues: error.issues }
110+
: {}
111+
)
112+
.error('Failed to authorize:', error);
101113
return { success: false };
102114
}
103115
}

0 commit comments

Comments
 (0)