Skip to content
This repository was archived by the owner on Apr 24, 2024. It is now read-only.

Commit 3f86cc0

Browse files
committed
refactor: test decoded basic auth tokens for their VSCHAR pattern
1 parent 391885c commit 3f86cc0

File tree

5 files changed

+73
-4
lines changed

5 files changed

+73
-4
lines changed

lib/consts/client_attributes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const ENUM = {
164164
application_type: () => ['native', 'web'],
165165
};
166166

167-
const noVSCHAR = /[^\x20-\x7E]/;
167+
export const noVSCHAR = /[^\x20-\x7E]/;
168168
// const noNQCHAR = /[^\x21\x23-\x5B\x5D-\x7E]/;
169169
// const noNQSCHAR = /[^\x20-\x21\x23-\x5B\x5D-\x7E]/;
170170

lib/shared/token_auth.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import setWWWAuthenticate from '../helpers/set_www_authenticate.js';
33
import * as JWT from '../helpers/jwt.js';
44
import instance from '../helpers/weak_cache.js';
55
import certificateThumbprint from '../helpers/certificate_thumbprint.js';
6+
import { noVSCHAR } from '../consts/client_attributes.js';
67

78
import rejectDupes from './reject_dupes.js';
89
import getJWTAuthMiddleware from './token_jwt_auth.js';
@@ -11,7 +12,15 @@ const assertionType = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
1112

1213
// see https://tools.ietf.org/html/rfc6749#appendix-B
1314
function decodeAuthToken(token) {
14-
return decodeURIComponent(token.replace(/\+/g, '%20'));
15+
// TODO: in v9.x consider enabling stricter encoding check
16+
// if (token.match(/[^a-zA-Z0-9%+]/)) {
17+
// throw new Error();
18+
// }
19+
const authToken = decodeURIComponent(token.replace(/\+/g, '%20'));
20+
if (noVSCHAR.test(authToken)) {
21+
throw new Error('invalid character found');
22+
}
23+
return authToken;
1524
}
1625

1726
export default function tokenAuth(provider) {

test/client_auth/client_auth.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,14 @@ export default {
145145
response_types: [],
146146
redirect_uris: [],
147147
client_secret_expires_at: 1,
148+
},
149+
// Appendix B
150+
{
151+
token_endpoint_auth_method: 'client_secret_basic',
152+
client_id: ' %&+',
153+
client_secret: ' %&+',
154+
grant_types: ['foo'],
155+
response_types: [],
156+
redirect_uris: [],
148157
}],
149158
};

test/client_auth/client_auth.test.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,24 @@ describe('client authentication options', () => {
281281
});
282282
});
283283

284+
it('accepts the auth (https://tools.ietf.org/html/rfc6749#appendix-B)', function () {
285+
return this.agent.post(route)
286+
.send({
287+
grant_type: 'foo',
288+
})
289+
.type('form')
290+
.auth(' %&+', ' %&+')
291+
.expect(200)
292+
.expect(tokenAuthSucceeded);
293+
});
294+
284295
it('accepts the auth (https://tools.ietf.org/html/rfc6749#appendix-B again)', function () {
285296
return this.agent.post(route)
286297
.send({
287298
grant_type: 'foo',
288299
})
289300
.type('form')
290-
.auth('an%3Aidentifier', 'some+secure+%26+non-standard+secret')
301+
.auth('an:identifier', 'some secure & non-standard secret')
291302
.expect(200)
292303
.expect(tokenAuthSucceeded);
293304
});
@@ -298,7 +309,7 @@ describe('client authentication options', () => {
298309
grant_type: 'foo',
299310
})
300311
.type('form')
301-
.auth('foo with %', 'foo with $')
312+
.set('Authorization', `Basic ${btoa('foo with %:foo with $')}`)
302313
.expect({
303314
error: 'invalid_request',
304315
error_description: 'client_id and client_secret in the authorization header are not properly encoded',

test/test_helper.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { once } from 'node:events';
99
import sinon from 'sinon';
1010
import { dirname } from 'desm';
1111
import flatten from 'lodash/flatten.js';
12+
import { Request } from 'superagent'; // eslint-disable-line import/no-extraneous-dependencies
1213
import { agent as supertest } from 'supertest';
1314
import { expect } from 'chai';
1415
import koaMount from 'koa-mount';
@@ -27,6 +28,45 @@ import instance from '../lib/helpers/weak_cache.js';
2728
import { Account, TestAdapter } from './models.js';
2829
import keys from './keys.js';
2930

31+
const { _auth } = Request.prototype;
32+
33+
function encodeToken(token) {
34+
return encodeURIComponent(token).replace(/(?:[-_.!~*'()]|%20)/g, (substring) => {
35+
switch (substring) {
36+
case '-':
37+
return '%2D';
38+
case '_':
39+
return '%5F';
40+
case '.':
41+
return '%2E';
42+
case '!':
43+
return '%21';
44+
case '~':
45+
return '%7E';
46+
case '*':
47+
return '%2A';
48+
case "'":
49+
return '%27';
50+
case '(':
51+
return '%28';
52+
case ')':
53+
return '%29';
54+
case '%20':
55+
return '+';
56+
default:
57+
throw new Error();
58+
}
59+
});
60+
}
61+
62+
Request.prototype._auth = function (user, pass, options, encoder) {
63+
if (options?.type === 'basic') {
64+
return _auth.call(this, encodeToken(user), encodeToken(pass), options, encoder);
65+
}
66+
67+
return _auth.call(this, user, pass, options, encoder);
68+
};
69+
3070
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
3171

3272
global.i = instance;

0 commit comments

Comments
 (0)