Skip to content

Commit 2807a8f

Browse files
committed
fix: validate ciba notification tokens
1 parent a340869 commit 2807a8f

2 files changed

Lines changed: 93 additions & 2 deletions

File tree

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
import presence from '../../helpers/validate_presence.js';
2+
import { InvalidRequest } from '../../helpers/errors.js';
23

3-
export default function oidcRequired(ctx, next) {
4+
const CLIENT_NOTIFICATION_TOKEN = /^[A-Za-z0-9._~+\x2F-]+=*$/u;
5+
6+
export default function cibaRequired(ctx, next) {
47
const required = new Set(['scope']);
8+
const callbackMode = ctx.oidc.client.backchannelTokenDeliveryMode !== 'poll';
59

6-
if (ctx.oidc.client.backchannelTokenDeliveryMode !== 'poll') {
10+
if (callbackMode) {
711
required.add('client_notification_token');
812
}
913

1014
presence(ctx, ...required);
1115

16+
if (callbackMode) {
17+
const { client_notification_token: clientNotificationToken } = ctx.oidc.params;
18+
if (!CLIENT_NOTIFICATION_TOKEN.test(clientNotificationToken)) {
19+
throw new InvalidRequest('client_notification_token must be a valid Bearer token');
20+
}
21+
22+
if (clientNotificationToken.length > 1024) {
23+
throw new InvalidRequest('client_notification_token must not exceed 1024 characters');
24+
}
25+
} else {
26+
ctx.oidc.params.client_notification_token = undefined;
27+
}
28+
1229
return next();
1330
}

test/ciba/ciba.test.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,24 @@ describe('features.ciba', () => {
202202
expect(verifyUserCode[2]).to.equal('1234');
203203
});
204204

205+
it('ignores client_notification_token when using poll', async function () {
206+
const [, [, request]] = await Promise.all([
207+
this.agent.post(route)
208+
.send({
209+
scope: 'openid',
210+
login_hint: 'accountId',
211+
client_id: 'client',
212+
client_notification_token: 'foo bar',
213+
})
214+
.type('form')
215+
.expect(200)
216+
.expect('content-type', /application\/json/),
217+
once(emitter, 'triggerAuthenticationDevice'),
218+
]);
219+
220+
expect(request.params).not.to.have.property('client_notification_token');
221+
});
222+
205223
it('requested_expiry', async function () {
206224
await this.agent.post(route)
207225
.send({
@@ -485,6 +503,62 @@ describe('features.ciba', () => {
485503
});
486504
});
487505

506+
it('accepts the client_notification_token Bearer token syntax when using ping', async function () {
507+
return this.agent.post(route)
508+
.send({
509+
client_id: 'client-ping',
510+
scope: 'openid',
511+
login_hint: 'accountId',
512+
client_notification_token: 'abc-._~+/==',
513+
})
514+
.type('form')
515+
.expect(200)
516+
.expect('content-type', /application\/json/);
517+
});
518+
519+
it('validates the client_notification_token syntax when using ping', async function () {
520+
const values = [
521+
'foo bar',
522+
'foo:bar',
523+
'foo=bar',
524+
'foo,bar',
525+
];
526+
527+
for (const client_notification_token of values) {
528+
await this.agent.post(route)
529+
.send({
530+
client_id: 'client-ping',
531+
scope: 'openid',
532+
login_hint: 'accountId',
533+
client_notification_token,
534+
})
535+
.type('form')
536+
.expect(400)
537+
.expect('content-type', /application\/json/)
538+
.expect({
539+
error: 'invalid_request',
540+
error_description: 'client_notification_token must be a valid Bearer token',
541+
});
542+
}
543+
});
544+
545+
it('validates the client_notification_token length when using ping', async function () {
546+
return this.agent.post(route)
547+
.send({
548+
client_id: 'client-ping',
549+
scope: 'openid',
550+
login_hint: 'accountId',
551+
client_notification_token: 'a'.repeat(1025),
552+
})
553+
.type('form')
554+
.expect(400)
555+
.expect('content-type', /application\/json/)
556+
.expect({
557+
error: 'invalid_request',
558+
error_description: 'client_notification_token must not exceed 1024 characters',
559+
});
560+
});
561+
488562
it('requires the scope param with openid', async function () {
489563
const spy = sinon.spy();
490564
this.provider.once('backchannel_authentication.error', spy);

0 commit comments

Comments
 (0)