Skip to content

Commit 592c809

Browse files
committed
revoke-handler: revoke accessToken
- revoke accessToken implementation - The token being revoked must belong to the requesting client - invalid tokens do not cause an error response
1 parent 3fc1f3e commit 592c809

File tree

5 files changed

+240
-86
lines changed

5 files changed

+240
-86
lines changed

lib/handlers/revoke-handler.js

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
var InvalidArgumentError = require('../errors/invalid-argument-error');
88
var InvalidClientError = require('../errors/invalid-client-error');
9+
var InvalidTokenError = require('../errors/invalid-token-error');
910
var InvalidRequestError = require('../errors/invalid-request-error');
1011
var OAuthError = require('../errors/oauth-error');
1112
var Promise = require('bluebird');
@@ -34,6 +35,10 @@ function RevokeHandler(options) {
3435
throw new InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`');
3536
}
3637

38+
if (!options.model.getAccessToken) {
39+
throw new InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`');
40+
}
41+
3742
if (!options.model.revokeToken) {
3843
throw new InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`');
3944
}
@@ -66,40 +71,57 @@ RevokeHandler.prototype.handle = function(request, response) {
6671
.then(function() {
6772
return this.getClient(request, response);
6873
})
69-
.then(function(client){
74+
.then(function(client) {
7075
return this.handleRevokeToken(request, client);
7176
})
72-
.then(function(){
77+
.catch(function(e) {
78+
if (!(e instanceof OAuthError)) {
79+
e = new ServerError(e);
80+
}
7381
/**
7482
* All necessary information is conveyed in the response code.
7583
*
76-
* @see https://tools.ietf.org/html/rfc7009#section-2.1
84+
* Note: invalid tokens do not cause an error response since the client
85+
* cannot handle such an error in a reasonable way. Moreover, the
86+
* purpose of the revocation request, invalidating the particular token,
87+
* is already achieved.
88+
* @see https://tools.ietf.org/html/rfc7009#section-2.2
7789
*/
78-
return {};
79-
})
80-
.catch(function(e) {
81-
if (!(e instanceof OAuthError)) {
82-
e = new ServerError(e);
90+
if (!(e instanceof InvalidTokenError)) {
91+
this.updateErrorResponse(response, e);
8392
}
8493

85-
this.updateErrorResponse(response, e);
86-
8794
throw e;
8895
});
8996
};
9097

9198
/**
92-
* Handle revoke token
99+
* Revoke a refresh or access token.
100+
*
101+
* Handle the revoking of refresh tokens, and access tokens if supported / desirable
102+
* RFC7009 specifies that "If the server is unable to locate the token using
103+
* the given hint, it MUST extend its search across all of its supported token types"
93104
*/
94105

95106
RevokeHandler.prototype.handleRevokeToken = function(request, client) {
96107
return Promise.bind(this)
97108
.then(function() {
98109
return this.getTokenFromRequest(request);
99-
}).then(function (token){
100-
return this.getRefreshToken(token, client);
101-
}).tap(function (token) {
102-
return this.revokeToken(token);
110+
})
111+
.then(function(token) {
112+
return Promise.any([
113+
this.getAccessToken(token, client),
114+
this.getRefreshToken(token, client)
115+
])
116+
.catch(Promise.AggregateError, function(err) {
117+
err.forEach(function(e) {
118+
throw e;
119+
});
120+
})
121+
.bind(this)
122+
.tap(function(token) {
123+
return this.revokeToken(token);
124+
});
103125
});
104126
};
105127

@@ -196,17 +218,15 @@ RevokeHandler.prototype.getTokenFromRequest = function(request) {
196218
return bodyToken;
197219
};
198220

199-
200221
/**
201222
* Get refresh token.
202223
*/
203224

204225
RevokeHandler.prototype.getRefreshToken = function(token, client) {
205-
206226
return Promise.try(this.model.getRefreshToken, token)
207227
.then(function(token) {
208228
if (!token) {
209-
throw new InvalidRequestError('Invalid request: refresh token is invalid');
229+
throw new InvalidTokenError('Invalid token: refresh token is invalid');
210230
}
211231

212232
if (!token.client) {
@@ -218,31 +238,67 @@ RevokeHandler.prototype.getRefreshToken = function(token, client) {
218238
}
219239

220240
if (token.client.id !== client.id) {
221-
throw new InvalidRequestError('Invalid request: refresh token is invalid');
241+
throw new InvalidTokenError('Invalid token: refresh token client is invalid');
222242
}
223243

224244
if (token.refreshTokenExpiresAt && !(token.refreshTokenExpiresAt instanceof Date)) {
225245
throw new ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance');
226246
}
227247

228248
if (token.refreshTokenExpiresAt && token.refreshTokenExpiresAt < new Date()) {
229-
throw new InvalidRequestError('Invalid request: refresh token has expired');
249+
throw new InvalidTokenError('Invalid token: refresh token has expired');
230250
}
231251

232252
return token;
233253
});
234254
};
235255

236256
/**
237-
* Revoke the refresh token.
257+
* Get the access token from the model.
258+
*/
259+
260+
RevokeHandler.prototype.getAccessToken = function(token, client) {
261+
return Promise.try(this.model.getAccessToken, token)
262+
.then(function(accessToken) {
263+
if (!accessToken) {
264+
throw new InvalidTokenError('Invalid token: access token is invalid');
265+
}
266+
267+
if (!accessToken.client) {
268+
throw new ServerError('Server error: `getAccessToken()` did not return a `client` object');
269+
}
270+
271+
if (!accessToken.user) {
272+
throw new ServerError('Server error: `getAccessToken()` did not return a `user` object');
273+
}
274+
275+
if (accessToken.client.id !== client.id) {
276+
throw new InvalidTokenError('Invalid token: access token is invalid');
277+
}
278+
279+
if (accessToken.accessTokenExpiresAt && !(accessToken.accessTokenExpiresAt instanceof Date)) {
280+
throw new ServerError('Server error: `expires` must be a Date instance');
281+
}
282+
283+
if (accessToken.accessTokenExpiresAt && accessToken.accessTokenExpiresAt < new Date()) {
284+
throw new InvalidTokenError('Invalid token: access token has expired.');
285+
}
286+
287+
return accessToken;
288+
});
289+
};
290+
291+
/**
292+
* Revoke the token.
238293
*
294+
* @see https://tools.ietf.org/html/rfc6749#section-6
239295
*/
240296

241297
RevokeHandler.prototype.revokeToken = function(token) {
242298
return Promise.try(this.model.revokeToken, token)
243-
.then(function(status) {
244-
if (!status) {
245-
throw new InvalidRequestError('Invalid request: refresh token is invalid');
299+
.then(function(token) {
300+
if (!token) {
301+
throw new InvalidTokenError('Invalid token: token is invalid');
246302
}
247303

248304
return token;

0 commit comments

Comments
 (0)