Skip to content

Commit 6c670e2

Browse files
authored
Merge branch 'development' into dependabot/npm_and_yarn/eslint-8.54.0
2 parents 32fe5fe + 9562aa9 commit 6c670e2

13 files changed

+98
-117
lines changed

index.d.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ declare namespace OAuth2Server {
148148
* Validate requested scope. Calls Model#validateScope() if implemented.
149149
*
150150
*/
151-
validateScope(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
151+
validateScope(user: User, client: Client, scope?: string[]): Promise<string[] | Falsey>;
152152

153153
/**
154154
* Retrieve info from the request and client and return token
@@ -314,7 +314,7 @@ declare namespace OAuth2Server {
314314
* Invoked to check if the requested scope is valid for a particular client/user combination.
315315
*
316316
*/
317-
validateScope?(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
317+
validateScope?(user: User, client: Client, scope?: string[]): Promise<string[] | Falsey>;
318318

319319
/**
320320
* Invoked to check if the provided `redirectUri` is valid for a particular `client`.
@@ -340,7 +340,7 @@ declare namespace OAuth2Server {
340340
* Invoked to check if the requested scope is valid for a particular client/user combination.
341341
*
342342
*/
343-
validateScope?(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
343+
validateScope?(user: User, client: Client, scope?: string[]): Promise<string[] | Falsey>;
344344
}
345345

346346
interface RefreshTokenModel extends BaseModel, RequestAuthenticationModel {
@@ -374,7 +374,7 @@ declare namespace OAuth2Server {
374374
* Invoked to check if the requested scope is valid for a particular client/user combination.
375375
*
376376
*/
377-
validateScope?(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
377+
validateScope?(user: User, client: Client, scope?: string[]): Promise<string[] | Falsey>;
378378
}
379379

380380
interface ExtensionModel extends BaseModel, RequestAuthenticationModel {}

lib/handlers/authenticate-handler.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const Request = require('../request');
1313
const Response = require('../response');
1414
const ServerError = require('../errors/server-error');
1515
const UnauthorizedRequestError = require('../errors/unauthorized-request-error');
16+
const { parseScope } = require('../utils/scope-util');
1617

1718
/**
1819
* Constructor.
@@ -46,7 +47,7 @@ class AuthenticateHandler {
4647
this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader;
4748
this.allowBearerTokensInQueryString = options.allowBearerTokensInQueryString;
4849
this.model = options.model;
49-
this.scope = options.scope;
50+
this.scope = parseScope(options.scope);
5051
}
5152

5253
/**

lib/handlers/token-handler.js

+6
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ class TokenHandler {
266266
updateSuccessResponse (response, tokenType) {
267267
response.body = tokenType.valueOf();
268268

269+
// for compliance reasons we rebuild the internal scope to be a string
270+
// https://datatracker.ietf.org/doc/html/rfc6749.html#section-5.1
271+
if (response.body.scope) {
272+
response.body.scope = response.body.scope.join(' ');
273+
}
274+
269275
response.set('Cache-Control', 'no-store');
270276
response.set('Pragma', 'no-cache');
271277
}

lib/utils/scope-util.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
const isFormat = require('@node-oauth/formats');
22
const InvalidScopeError = require('../errors/invalid-scope-error');
3+
const whiteSpace = /\s+/g;
34

45
module.exports = {
56
parseScope: function (requestedScope) {
6-
if (!isFormat.nqschar(requestedScope)) {
7+
if (requestedScope == null) {
8+
return undefined;
9+
}
10+
11+
if (typeof requestedScope !== 'string') {
712
throw new InvalidScopeError('Invalid parameter: `scope`');
813
}
914

10-
if (requestedScope == null) {
11-
return undefined;
15+
// XXX: this prevents spaced-only strings to become
16+
// treated as valid nqchar by making them empty strings
17+
requestedScope = requestedScope.trim();
18+
19+
if(!isFormat.nqschar(requestedScope)) {
20+
throw new InvalidScopeError('Invalid parameter: `scope`');
1221
}
1322

14-
return requestedScope.split(' ');
23+
return requestedScope.split(whiteSpace);
1524
}
1625
};

lib/validator/is.js

-90
This file was deleted.

test/compliance/client-credential-workflow_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ describe('ClientCredentials Workflow Compliance (4.4)', function () {
9090
response.body.token_type.should.equal('Bearer');
9191
response.body.access_token.should.equal(token.accessToken);
9292
response.body.expires_in.should.be.a('number');
93-
response.body.scope.should.eql(['read', 'write']);
93+
response.body.scope.should.eql('read write');
9494
('refresh_token' in response.body).should.equal(false);
9595

9696
token.accessToken.should.be.a('string');

test/compliance/password-grant-type_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe('PasswordGrantType Compliance', function () {
101101
response.body.access_token.should.equal(token.accessToken);
102102
response.body.refresh_token.should.equal(token.refreshToken);
103103
response.body.expires_in.should.be.a('number');
104-
response.body.scope.should.eql(['read', 'write']);
104+
response.body.scope.should.eql('read write');
105105

106106
token.accessToken.should.be.a('string');
107107
token.refreshToken.should.be.a('string');

test/compliance/refresh-token-grant-type_test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ describe('RefreshTokenGrantType Compliance', function () {
124124
refreshResponse.body.access_token.should.equal(token.accessToken);
125125
refreshResponse.body.refresh_token.should.equal(token.refreshToken);
126126
refreshResponse.body.expires_in.should.be.a('number');
127-
refreshResponse.body.scope.should.eql(['read', 'write']);
127+
refreshResponse.body.scope.should.eql('read write');
128128

129129
token.accessToken.should.be.a('string');
130130
token.refreshToken.should.be.a('string');
@@ -223,7 +223,7 @@ describe('RefreshTokenGrantType Compliance', function () {
223223
refreshResponse.body.access_token.should.equal(token.accessToken);
224224
refreshResponse.body.refresh_token.should.equal(token.refreshToken);
225225
refreshResponse.body.expires_in.should.be.a('number');
226-
refreshResponse.body.scope.should.eql(['read']);
226+
refreshResponse.body.scope.should.eql('read');
227227
});
228228
});
229229
});

test/helpers/model.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ function createModel (db) {
1010
}
1111

1212
async function saveToken (token, client, user) {
13+
if (token.scope && !Array.isArray(token.scope)) {
14+
throw new Error('Scope should internally be an array');
15+
}
1316
const meta = {
1417
clientId: client.id,
1518
userId: user.id,
@@ -38,7 +41,9 @@ function createModel (db) {
3841
if (!meta) {
3942
return false;
4043
}
41-
44+
if (meta.scope && !Array.isArray(meta.scope)) {
45+
throw new Error('Scope should internally be an array');
46+
}
4247
return {
4348
accessToken,
4449
accessTokenExpiresAt: meta.accessTokenExpiresAt,
@@ -54,7 +59,9 @@ function createModel (db) {
5459
if (!meta) {
5560
return false;
5661
}
57-
62+
if (meta.scope && !Array.isArray(meta.scope)) {
63+
throw new Error('Scope should internally be an array');
64+
}
5865
return {
5966
refreshToken,
6067
refreshTokenExpiresAt: meta.refreshTokenExpiresAt,
@@ -71,6 +78,9 @@ function createModel (db) {
7178
}
7279

7380
async function verifyScope (token, scope) {
81+
if (!Array.isArray(scope)) {
82+
throw new Error('Scope should internally be an array');
83+
}
7484
return scope.every(s => scopes.includes(s));
7585
}
7686

test/integration/handlers/authenticate-handler_test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('AuthenticateHandler integration', function() {
9393
addAcceptedScopesHeader: true,
9494
addAuthorizedScopesHeader: true,
9595
model: model,
96-
scope: ['foobar']
96+
scope: 'foobar'
9797
});
9898

9999
grantType.scope.should.eql(['foobar']);
@@ -254,7 +254,7 @@ describe('AuthenticateHandler integration', function() {
254254
return true;
255255
}
256256
};
257-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: ['foo'] });
257+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' });
258258
const request = new Request({
259259
body: {},
260260
headers: { 'Authorization': 'Bearer foo' },
@@ -522,7 +522,7 @@ describe('AuthenticateHandler integration', function() {
522522
return false;
523523
}
524524
};
525-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: ['foo'] });
525+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' });
526526

527527
return handler.verifyScope(['foo'])
528528
.then(should.fail)
@@ -539,7 +539,7 @@ describe('AuthenticateHandler integration', function() {
539539
return true;
540540
}
541541
};
542-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: ['foo'] });
542+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' });
543543

544544
handler.verifyScope(['foo']).should.be.an.instanceOf(Promise);
545545
});
@@ -551,7 +551,7 @@ describe('AuthenticateHandler integration', function() {
551551
return true;
552552
}
553553
};
554-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: ['foo'] });
554+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'foo' });
555555

556556
handler.verifyScope(['foo']).should.be.an.instanceOf(Promise);
557557
});
@@ -576,7 +576,7 @@ describe('AuthenticateHandler integration', function() {
576576
getAccessToken: function() {},
577577
verifyScope: function() {}
578578
};
579-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model, scope: ['foo', 'bar'] });
579+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: false, model: model, scope: 'foo bar' });
580580
const response = new Response({ body: {}, headers: {} });
581581

582582
handler.updateResponse(response, { scope: ['foo', 'biz'] });
@@ -602,7 +602,7 @@ describe('AuthenticateHandler integration', function() {
602602
getAccessToken: function() {},
603603
verifyScope: function() {}
604604
};
605-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model, scope: ['foo', 'bar'] });
605+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: false, addAuthorizedScopesHeader: true, model: model, scope: 'foo bar' });
606606
const response = new Response({ body: {}, headers: {} });
607607

608608
handler.updateResponse(response, { scope: ['foo', 'biz'] });

test/integration/handlers/token-handler_test.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ describe('TokenHandler integration', function() {
329329
});
330330

331331
it('should not return custom attributes in a bearer token if the allowExtendedTokenAttributes is not set', function() {
332-
const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: ['foobar'], user: {}, foo: 'bar' };
332+
const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: ['baz'], user: {}, foo: 'bar' };
333333
const model = {
334334
getClient: function() { return { grants: ['password'] }; },
335335
getUser: function() { return {}; },
@@ -357,14 +357,14 @@ describe('TokenHandler integration', function() {
357357
should.exist(response.body.access_token);
358358
should.exist(response.body.refresh_token);
359359
should.exist(response.body.token_type);
360-
should.exist(response.body.scope);
360+
response.body.scope.should.eql('baz');
361361
should.not.exist(response.body.foo);
362362
})
363363
.catch(should.fail);
364364
});
365365

366366
it('should return custom attributes in a bearer token if the allowExtendedTokenAttributes is set', function() {
367-
const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: ['foobar'], user: {}, foo: 'bar' };
367+
const token = { accessToken: 'foo', client: {}, refreshToken: 'bar', scope: ['baz'], user: {}, foo: 'bar' };
368368
const model = {
369369
getClient: function() { return { grants: ['password'] }; },
370370
getUser: function() { return {}; },
@@ -392,7 +392,7 @@ describe('TokenHandler integration', function() {
392392
should.exist(response.body.access_token);
393393
should.exist(response.body.refresh_token);
394394
should.exist(response.body.token_type);
395-
should.exist(response.body.scope);
395+
response.body.scope.should.eql('baz');
396396
should.exist(response.body.foo);
397397
})
398398
.catch(should.fail);

test/unit/handlers/authenticate-handler_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe('AuthenticateHandler', function() {
166166
getAccessToken: function() {},
167167
verifyScope: sinon.stub().returns(true)
168168
};
169-
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: ['bar'] });
169+
const handler = new AuthenticateHandler({ addAcceptedScopesHeader: true, addAuthorizedScopesHeader: true, model: model, scope: 'bar' });
170170

171171
return handler.verifyScope(['foo'])
172172
.then(function() {

0 commit comments

Comments
 (0)