Skip to content

Commit 3d766a7

Browse files
authored
refactor: use native promises and async/await, drop bluebird and promisify-any
Merge pull request #190 from node-oauth/refactor-native-promises
2 parents 9fd04f6 + e1fdc23 commit 3d766a7

28 files changed

+631
-1047
lines changed

.mocharc.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
recursive: true
22
reporter: "spec"
3-
retries: 1
3+
retries: 0
44
slow: 20
55
timeout: 2000
66
ui: "bdd"

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
## Changelog
22

3+
## 5.0.0
4+
5+
- removed `bluebird` and `promisify-any`
6+
- uses native Promises and `async/await` everywhere
7+
- drop support for Node 14 (EOL), setting Node 16 as `engine` in `package.json`
8+
- this is a breaking change, because **it removes callback support** for
9+
`OAuthServer` and your model implementation.
10+
311
## 4.2.0
412
### Fixed
513
- fix(core): Bearer regular expression matching in authenticate handler #105

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ Most users should refer to our [Express (active)](https://github.com/node-oauth/
4545

4646
More examples can be found here: https://github.com/14gasher/oauth-example
4747

48+
## Version 5 notes
49+
50+
Beginning with version `5.x` we removed dual support for callbacks and promises.
51+
With this version there is only support for Promises / async/await.
52+
53+
With this version we also bumped the `engine` to Node 16 as 14 is now deprecated.
54+
4855
## Migrating from OAuthJs and 3.x
4956

5057
Version 4.x should not be hard-breaking, however, there were many improvements and fixes that may

SECURITY.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
Use this section to tell people about which versions of your project are
66
currently being supported with security updates.
77

8-
| Version | Supported |
9-
| ------- | ------------------ |
10-
| 4.x.x | :white_check_mark: |
11-
| 3.x.x | :white_check_mark: but only very critical security issues |
12-
| < 3 | :x: |
8+
| Version | Supported |
9+
|---------|--------------------------------------------------|
10+
| 5.x.x | :white_check_mark: |
11+
| 4.x.x | :white_check_mark: but only high severity issues |
12+
| 3.x.x | :x: |
13+
| < 3 | :x: |
1314

1415
## Reporting a Vulnerability
1516

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

+14-21
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
const InvalidArgumentError = require('../errors/invalid-argument-error');
88
const InvalidScopeError = require('../errors/invalid-scope-error');
9-
const Promise = require('bluebird');
10-
const promisify = require('promisify-any').use(Promise);
119
const isFormat = require('@node-oauth/formats');
1210
const tokenUtil = require('../utils/token-util');
1311

@@ -36,12 +34,10 @@ function AbstractGrantType(options) {
3634
* Generate access token.
3735
*/
3836

39-
AbstractGrantType.prototype.generateAccessToken = function(client, user, scope) {
37+
AbstractGrantType.prototype.generateAccessToken = async function(client, user, scope) {
4038
if (this.model.generateAccessToken) {
41-
return promisify(this.model.generateAccessToken, 3).call(this.model, client, user, scope)
42-
.then(function(accessToken) {
43-
return accessToken || tokenUtil.generateRandomToken();
44-
});
39+
const accessToken = await this.model.generateAccessToken(client, user, scope);
40+
return accessToken || tokenUtil.generateRandomToken();
4541
}
4642

4743
return tokenUtil.generateRandomToken();
@@ -51,12 +47,10 @@ AbstractGrantType.prototype.generateAccessToken = function(client, user, scope)
5147
* Generate refresh token.
5248
*/
5349

54-
AbstractGrantType.prototype.generateRefreshToken = function(client, user, scope) {
50+
AbstractGrantType.prototype.generateRefreshToken = async function(client, user, scope) {
5551
if (this.model.generateRefreshToken) {
56-
return promisify(this.model.generateRefreshToken, 3).call(this.model, client, user, scope)
57-
.then(function(refreshToken) {
58-
return refreshToken || tokenUtil.generateRandomToken();
59-
});
52+
const refreshToken = await this.model.generateRefreshToken(client, user, scope);
53+
return refreshToken || tokenUtil.generateRandomToken();
6054
}
6155

6256
return tokenUtil.generateRandomToken();
@@ -93,16 +87,15 @@ AbstractGrantType.prototype.getScope = function(request) {
9387
/**
9488
* Validate requested scope.
9589
*/
96-
AbstractGrantType.prototype.validateScope = function(user, client, scope) {
90+
AbstractGrantType.prototype.validateScope = async function(user, client, scope) {
9791
if (this.model.validateScope) {
98-
return promisify(this.model.validateScope, 3).call(this.model, user, client, scope)
99-
.then(function (scope) {
100-
if (!scope) {
101-
throw new InvalidScopeError('Invalid scope: Requested scope is invalid');
102-
}
103-
104-
return scope;
105-
});
92+
const validatedScope = await this.model.validateScope(user, client, scope);
93+
94+
if (!validatedScope) {
95+
throw new InvalidScopeError('Invalid scope: Requested scope is invalid');
96+
}
97+
98+
return validatedScope;
10699
} else {
107100
return scope;
108101
}

lib/grant-types/authorization-code-grant-type.js

+92-111
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ const AbstractGrantType = require('./abstract-grant-type');
88
const InvalidArgumentError = require('../errors/invalid-argument-error');
99
const InvalidGrantError = require('../errors/invalid-grant-error');
1010
const InvalidRequestError = require('../errors/invalid-request-error');
11-
const Promise = require('bluebird');
12-
const promisify = require('promisify-any').use(Promise);
1311
const ServerError = require('../errors/server-error');
1412
const isFormat = require('@node-oauth/formats');
1513
const pkce = require('../pkce/pkce');
@@ -45,7 +43,7 @@ class AuthorizationCodeGrantType extends AbstractGrantType {
4543
* @see https://tools.ietf.org/html/rfc6749#section-4.1.3
4644
*/
4745

48-
handle(request, client) {
46+
async handle(request, client) {
4947
if (!request) {
5048
throw new InvalidArgumentError('Missing parameter: `request`');
5149
}
@@ -54,96 +52,87 @@ class AuthorizationCodeGrantType extends AbstractGrantType {
5452
throw new InvalidArgumentError('Missing parameter: `client`');
5553
}
5654

57-
return Promise.bind(this)
58-
.then(function () {
59-
return this.getAuthorizationCode(request, client);
60-
})
61-
.tap(function (code) {
62-
return this.validateRedirectUri(request, code);
63-
})
64-
.tap(function (code) {
65-
return this.revokeAuthorizationCode(code);
66-
})
67-
.then(function (code) {
68-
return this.saveToken(code.user, client, code.authorizationCode, code.scope);
69-
});
55+
const code = await this.getAuthorizationCode(request, client);
56+
await this.validateRedirectUri(request, code);
57+
await this.revokeAuthorizationCode(code);
58+
59+
return this.saveToken(code.user, client, code.authorizationCode, code.scope);
7060
}
7161

7262
/**
7363
* Get the authorization code.
7464
*/
7565

76-
getAuthorizationCode(request, client) {
66+
async getAuthorizationCode(request, client) {
7767
if (!request.body.code) {
7868
throw new InvalidRequestError('Missing parameter: `code`');
7969
}
8070

8171
if (!isFormat.vschar(request.body.code)) {
8272
throw new InvalidRequestError('Invalid parameter: `code`');
8373
}
84-
return promisify(this.model.getAuthorizationCode, 1)
85-
.call(this.model, request.body.code)
86-
.then((code) => {
87-
if (!code) {
88-
throw new InvalidGrantError('Invalid grant: authorization code is invalid');
89-
}
90-
91-
if (!code.client) {
92-
throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object');
93-
}
94-
95-
if (!code.user) {
96-
throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object');
97-
}
98-
99-
if (code.client.id !== client.id) {
100-
throw new InvalidGrantError('Invalid grant: authorization code is invalid');
101-
}
102-
103-
if (!(code.expiresAt instanceof Date)) {
104-
throw new ServerError('Server error: `expiresAt` must be a Date instance');
105-
}
106-
107-
if (code.expiresAt < new Date()) {
108-
throw new InvalidGrantError('Invalid grant: authorization code has expired');
109-
}
110-
111-
if (code.redirectUri && !isFormat.uri(code.redirectUri)) {
112-
throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI');
113-
}
114-
115-
// optional: PKCE code challenge
116-
117-
if (code.codeChallenge) {
118-
if (!request.body.code_verifier) {
119-
throw new InvalidGrantError('Missing parameter: `code_verifier`');
120-
}
121-
122-
const hash = pkce.getHashForCodeChallenge({
123-
method: code.codeChallengeMethod,
124-
verifier: request.body.code_verifier
125-
});
126-
127-
if (!hash) {
128-
// notice that we assume that codeChallengeMethod is already
129-
// checked at an earlier stage when being read from
130-
// request.body.code_challenge_method
131-
throw new ServerError('Server error: `getAuthorizationCode()` did not return a valid `codeChallengeMethod` property');
132-
}
133-
134-
if (code.codeChallenge !== hash) {
135-
throw new InvalidGrantError('Invalid grant: code verifier is invalid');
136-
}
137-
}
138-
else {
139-
if (request.body.code_verifier) {
140-
// No code challenge but code_verifier was passed in.
141-
throw new InvalidGrantError('Invalid grant: code verifier is invalid');
142-
}
143-
}
144-
145-
return code;
74+
75+
const code = await this.model.getAuthorizationCode(request.body.code);
76+
77+
if (!code) {
78+
throw new InvalidGrantError('Invalid grant: authorization code is invalid');
79+
}
80+
81+
if (!code.client) {
82+
throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object');
83+
}
84+
85+
if (!code.user) {
86+
throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object');
87+
}
88+
89+
if (code.client.id !== client.id) {
90+
throw new InvalidGrantError('Invalid grant: authorization code is invalid');
91+
}
92+
93+
if (!(code.expiresAt instanceof Date)) {
94+
throw new ServerError('Server error: `expiresAt` must be a Date instance');
95+
}
96+
97+
if (code.expiresAt < new Date()) {
98+
throw new InvalidGrantError('Invalid grant: authorization code has expired');
99+
}
100+
101+
if (code.redirectUri && !isFormat.uri(code.redirectUri)) {
102+
throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI');
103+
}
104+
105+
// optional: PKCE code challenge
106+
107+
if (code.codeChallenge) {
108+
if (!request.body.code_verifier) {
109+
throw new InvalidGrantError('Missing parameter: `code_verifier`');
110+
}
111+
112+
const hash = pkce.getHashForCodeChallenge({
113+
method: code.codeChallengeMethod,
114+
verifier: request.body.code_verifier
146115
});
116+
117+
if (!hash) {
118+
// notice that we assume that codeChallengeMethod is already
119+
// checked at an earlier stage when being read from
120+
// request.body.code_challenge_method
121+
throw new ServerError('Server error: `getAuthorizationCode()` did not return a valid `codeChallengeMethod` property');
122+
}
123+
124+
if (code.codeChallenge !== hash) {
125+
throw new InvalidGrantError('Invalid grant: code verifier is invalid');
126+
}
127+
}
128+
else {
129+
if (request.body.code_verifier) {
130+
// No code challenge but code_verifier was passed in.
131+
throw new InvalidGrantError('Invalid grant: code verifier is invalid');
132+
}
133+
}
134+
135+
return code;
147136
}
148137

149138
/**
@@ -183,46 +172,38 @@ class AuthorizationCodeGrantType extends AbstractGrantType {
183172
* @see https://tools.ietf.org/html/rfc6749#section-4.1.2
184173
*/
185174

186-
revokeAuthorizationCode(code) {
187-
return promisify(this.model.revokeAuthorizationCode, 1)
188-
.call(this.model, code)
189-
.then((status) => {
190-
if (!status) {
191-
throw new InvalidGrantError('Invalid grant: authorization code is invalid');
192-
}
175+
async revokeAuthorizationCode(code) {
176+
const status = await this.model.revokeAuthorizationCode(code);
193177

194-
return code;
195-
});
178+
if (!status) {
179+
throw new InvalidGrantError('Invalid grant: authorization code is invalid');
180+
}
181+
182+
return code;
196183
}
197184

198185

199186
/**
200187
* Save token.
201188
*/
202189

203-
saveToken(user, client, authorizationCode, scope) {
204-
const fns = [
205-
this.validateScope(user, client, scope),
206-
this.generateAccessToken(client, user, scope),
207-
this.generateRefreshToken(client, user, scope),
208-
this.getAccessTokenExpiresAt(),
209-
this.getRefreshTokenExpiresAt(),
210-
];
211-
212-
return Promise.all(fns)
213-
.bind(this)
214-
.spread(function (scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) {
215-
const token = {
216-
accessToken: accessToken,
217-
authorizationCode: authorizationCode,
218-
accessTokenExpiresAt: accessTokenExpiresAt,
219-
refreshToken: refreshToken,
220-
refreshTokenExpiresAt: refreshTokenExpiresAt,
221-
scope: scope,
222-
};
223-
224-
return promisify(this.model.saveToken, 3).call(this.model, token, client, user);
225-
});
190+
async saveToken(user, client, authorizationCode, scope) {
191+
const validatedScope = await this.validateScope(user, client, scope);
192+
const accessToken = await this.generateAccessToken(client, user, scope);
193+
const refreshToken = await this.generateRefreshToken(client, user, scope);
194+
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt();
195+
const refreshTokenExpiresAt = await this.getRefreshTokenExpiresAt();
196+
197+
const token = {
198+
accessToken: accessToken,
199+
authorizationCode: authorizationCode,
200+
accessTokenExpiresAt: accessTokenExpiresAt,
201+
refreshToken: refreshToken,
202+
refreshTokenExpiresAt: refreshTokenExpiresAt,
203+
scope: validatedScope,
204+
};
205+
206+
return this.model.saveToken(token, client, user);
226207
}
227208
}
228209

0 commit comments

Comments
 (0)