Skip to content

Commit a42dc06

Browse files
rewrote the scope validation
1 parent 028e020 commit a42dc06

30 files changed

+355
-309
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- this is a breaking change, because **it removes callback support** for
99
`OAuthServer` and your model implementation.
1010
- fixed missing await in calling generateAuthorizationCode in AuthorizeHandler
11+
- validate scope as an array of strings
1112

1213
## 4.2.0
1314
### Fixed
@@ -52,7 +53,7 @@
5253
- Upgrades all code from ES5 to ES6, where possible.
5354

5455
## 4.1.0
55-
### Changed
56+
### Changed
5657
* Bump dev dependencies to resolve vulnerabilities
5758
* Replaced jshint with eslint along with should and chai
5859
* Use sha256 when generating tokens

docs/api/oauth2-server.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Authenticates a request.
7373
+------------------------------------------------+-----------------+-----------------------------------------------------------------------+
7474
| [options={}] | Object | Handler options. |
7575
+------------------------------------------------+-----------------+-----------------------------------------------------------------------+
76-
| [options.scope=undefined] | String | The scope(s) to authenticate. |
76+
| [options.scope=undefined] | String[] | The scope(s) to authenticate. |
7777
+------------------------------------------------+-----------------+-----------------------------------------------------------------------+
7878
| [options.addAcceptedScopesHeader=true] | Boolean | Set the ``X-Accepted-OAuth-Scopes`` HTTP header on response objects. |
7979
+------------------------------------------------+-----------------+-----------------------------------------------------------------------+

docs/model/spec.rst

Lines changed: 110 additions & 116 deletions
Large diffs are not rendered by default.

index.d.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,13 @@ declare namespace OAuth2Server {
118118
* Generate access token. Calls Model#generateAccessToken() if implemented.
119119
*
120120
*/
121-
generateAccessToken(client: Client, user: User, scope: string | string[]): Promise<string>;
121+
generateAccessToken(client: Client, user: User, scope: string[]): Promise<string>;
122122

123123
/**
124124
* Generate refresh token. Calls Model#generateRefreshToken() if implemented.
125125
*
126126
*/
127-
generateRefreshToken(client: Client, user: User, scope: string | string[]): Promise<string>;
127+
generateRefreshToken(client: Client, user: User, scope: string[]): Promise<string>;
128128

129129
/**
130130
* Get access token expiration date.
@@ -142,13 +142,13 @@ declare namespace OAuth2Server {
142142
* Get scope from the request body.
143143
*
144144
*/
145-
getScope(request: Request): string;
145+
getScope(request: Request): string[];
146146

147147
/**
148148
* Validate requested scope. Calls Model#validateScope() if implemented.
149149
*
150150
*/
151-
validateScope(user: User, client: Client, scope: string | string[]): Promise<string | 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
@@ -168,7 +168,7 @@ declare namespace OAuth2Server {
168168
/**
169169
* The scope(s) to authenticate.
170170
*/
171-
scope?: string | string[] | undefined;
171+
scope?: string[] | undefined;
172172

173173
/**
174174
* Set the X-Accepted-OAuth-Scopes HTTP header on response objects.
@@ -245,7 +245,7 @@ declare namespace OAuth2Server {
245245
* Invoked to generate a new access token.
246246
*
247247
*/
248-
generateAccessToken?(client: Client, user: User, scope: string | string[]): Promise<string>;
248+
generateAccessToken?(client: Client, user: User, scope: string[]): Promise<string>;
249249

250250
/**
251251
* Invoked to retrieve a client using a client id or a client id/client secret combination, depending on the grant type.
@@ -272,21 +272,21 @@ declare namespace OAuth2Server {
272272
* Optional, if a custom authenticateHandler is used or if there is no scope part of the request.
273273
*
274274
*/
275-
verifyScope(token: Token, scope: string | string[]): Promise<boolean>;
275+
verifyScope?(token: Token, scope: string[]): Promise<boolean>;
276276
}
277277

278278
interface AuthorizationCodeModel extends BaseModel, RequestAuthenticationModel {
279279
/**
280280
* Invoked to generate a new refresh token.
281281
*
282282
*/
283-
generateRefreshToken?(client: Client, user: User, scope: string | string[]): Promise<string>;
283+
generateRefreshToken?(client: Client, user: User, scope: string[]): Promise<string>;
284284

285285
/**
286286
* Invoked to generate a new authorization code.
287287
*
288288
*/
289-
generateAuthorizationCode?(client: Client, user: User, scope: string | string[]): Promise<string>;
289+
generateAuthorizationCode?(client: Client, user: User, scope: string[]): Promise<string>;
290290

291291
/**
292292
* Invoked to retrieve an existing authorization code previously saved through Model#saveAuthorizationCode().
@@ -314,8 +314,8 @@ 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 | string[]): Promise<string | string[] | Falsey>;
318-
317+
validateScope?(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
318+
319319
/**
320320
* Invoked to check if the provided `redirectUri` is valid for a particular `client`.
321321
*
@@ -328,7 +328,7 @@ declare namespace OAuth2Server {
328328
* Invoked to generate a new refresh token.
329329
*
330330
*/
331-
generateRefreshToken?(client: Client, user: User, scope: string | string[]): Promise<string>;
331+
generateRefreshToken?(client: Client, user: User, scope: string[]): Promise<string>;
332332

333333
/**
334334
* Invoked to retrieve a user using a username/password combination.
@@ -340,15 +340,15 @@ 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 | string[]): Promise<string | string[] | Falsey>;
343+
validateScope?(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
344344
}
345345

346346
interface RefreshTokenModel extends BaseModel, RequestAuthenticationModel {
347347
/**
348348
* Invoked to generate a new refresh token.
349349
*
350350
*/
351-
generateRefreshToken?(client: Client, user: User, scope: string | string[]): Promise<string>;
351+
generateRefreshToken?(client: Client, user: User, scope: string[]): Promise<string>;
352352

353353
/**
354354
* Invoked to retrieve an existing refresh token previously saved through Model#saveToken().
@@ -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 | string[]): Promise<string | string[] | Falsey>;
377+
validateScope?(user: User, client: Client, scope: string[]): Promise<string[] | Falsey>;
378378
}
379379

380380
interface ExtensionModel extends BaseModel, RequestAuthenticationModel {}
@@ -406,7 +406,7 @@ declare namespace OAuth2Server {
406406
authorizationCode: string;
407407
expiresAt: Date;
408408
redirectUri: string;
409-
scope?: string | string[] | undefined;
409+
scope?: string[] | undefined;
410410
client: Client;
411411
user: User;
412412
codeChallenge?: string;
@@ -422,7 +422,7 @@ declare namespace OAuth2Server {
422422
accessTokenExpiresAt?: Date | undefined;
423423
refreshToken?: string | undefined;
424424
refreshTokenExpiresAt?: Date | undefined;
425-
scope?: string | string[] | undefined;
425+
scope?: string[] | undefined;
426426
client: Client;
427427
user: User;
428428
[key: string]: any;
@@ -434,7 +434,7 @@ declare namespace OAuth2Server {
434434
interface RefreshToken {
435435
refreshToken: string;
436436
refreshTokenExpiresAt?: Date | undefined;
437-
scope?: string | string[] | undefined;
437+
scope?: string[] | undefined;
438438
client: Client;
439439
user: User;
440440
[key: string]: any;

lib/grant-types/abstract-grant-type.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
const InvalidArgumentError = require('../errors/invalid-argument-error');
88
const InvalidScopeError = require('../errors/invalid-scope-error');
9-
const isFormat = require('@node-oauth/formats');
109
const tokenUtil = require('../utils/token-util');
10+
const { parseScope } = require('../utils/scope-util');
1111

1212
class AbstractGrantType {
1313
constructor (options) {
@@ -73,11 +73,7 @@ class AbstractGrantType {
7373
* Get scope from the request body.
7474
*/
7575
getScope (request) {
76-
if (!isFormat.nqschar(request.body.scope)) {
77-
throw new InvalidArgumentError('Invalid parameter: `scope`');
78-
}
79-
80-
return request.body.scope;
76+
return parseScope(request.body.scope);
8177
}
8278

8379
/**

lib/grant-types/refresh-token-grant-type.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const InvalidGrantError = require('../errors/invalid-grant-error');
1010
const InvalidRequestError = require('../errors/invalid-request-error');
1111
const ServerError = require('../errors/server-error');
1212
const isFormat = require('@node-oauth/formats');
13+
const InvalidScopeError = require('../errors/invalid-scope-error');
1314

1415
/**
1516
* Constructor.
@@ -55,7 +56,9 @@ class RefreshTokenGrantType extends AbstractGrantType {
5556
token = await this.getRefreshToken(request, client);
5657
token = await this.revokeToken(token);
5758

58-
return this.saveToken(token.user, client, token.scope);
59+
const scope = this.getScope(request, token);
60+
61+
return this.saveToken(token.user, client, scope);
5962
}
6063

6164
/**
@@ -142,6 +145,33 @@ class RefreshTokenGrantType extends AbstractGrantType {
142145

143146
return this.model.saveToken(token, client, user);
144147
}
148+
149+
getScope (request, token) {
150+
const requestedScope = super.getScope(request);
151+
const originalScope = token.scope;
152+
153+
if (!originalScope && !requestedScope) {
154+
return;
155+
}
156+
157+
if (!originalScope && requestedScope) {
158+
throw new InvalidScopeError('Invalid scope: Unable to add extra scopes');
159+
}
160+
161+
if (!requestedScope) {
162+
return originalScope;
163+
}
164+
165+
const valid = requestedScope.every(scope => {
166+
return originalScope.includes(scope);
167+
});
168+
169+
if (!valid) {
170+
throw new InvalidScopeError('Invalid scope: Unable to add extra scopes');
171+
}
172+
173+
return requestedScope;
174+
}
145175
}
146176

147177
/**

lib/handlers/authenticate-handler.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,21 +235,23 @@ class AuthenticateHandler {
235235
if (!scope) {
236236
throw new InsufficientScopeError('Insufficient scope: authorized scope is insufficient');
237237
}
238-
239-
return scope;
240238
}
241239

242240
/**
243241
* Update response.
244242
*/
245243

246244
updateResponse (response, accessToken) {
245+
if (accessToken.scope == null) {
246+
return;
247+
}
248+
247249
if (this.scope && this.addAcceptedScopesHeader) {
248-
response.set('X-Accepted-OAuth-Scopes', this.scope);
250+
response.set('X-Accepted-OAuth-Scopes', this.scope.join(' '));
249251
}
250252

251253
if (this.scope && this.addAuthorizedScopesHeader) {
252-
response.set('X-OAuth-Scopes', accessToken.scope);
254+
response.set('X-OAuth-Scopes', accessToken.scope.join(' '));
253255
}
254256
}
255257
}

lib/handlers/authorize-handler.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const isFormat = require('@node-oauth/formats');
2020
const tokenUtil = require('../utils/token-util');
2121
const url = require('url');
2222
const pkce = require('../pkce/pkce');
23+
const { parseScope } = require('../utils/scope-util');
2324

2425
/**
2526
* Response types.
@@ -226,11 +227,7 @@ class AuthorizeHandler {
226227
getScope (request) {
227228
const scope = request.body.scope || request.query.scope;
228229

229-
if (!isFormat.nqschar(scope)) {
230-
throw new InvalidScopeError('Invalid parameter: `scope`');
231-
}
232-
233-
return scope;
230+
return parseScope(scope);
234231
}
235232

236233
/**

lib/server.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ class OAuth2Server {
3030
*/
3131

3232
authenticate (request, response, options) {
33-
if (typeof options === 'string') {
34-
options = {scope: options};
35-
}
36-
3733
options = Object.assign({
3834
addAcceptedScopesHeader: true,
3935
addAuthorizedScopesHeader: true,

lib/utils/scope-util.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const isFormat = require('@node-oauth/formats');
2+
const InvalidScopeError = require('../errors/invalid-scope-error');
3+
4+
module.exports = {
5+
parseScope: function (requestedScope) {
6+
if (!isFormat.nqschar(requestedScope)) {
7+
throw new InvalidScopeError('Invalid parameter: `scope`');
8+
}
9+
10+
if (requestedScope == null) {
11+
return undefined;
12+
}
13+
14+
return requestedScope.split(' ');
15+
}
16+
};

0 commit comments

Comments
 (0)