Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[validation-improvements] Improve runtime library validations #290

Merged
merged 1 commit into from
Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## Next
### Improvements
* Valid token presence is verified on access token creation
* Valid tokenType presence is verified on `.revoke` calls

### Maintainance
* Remove dev lodash library
* Update ava test runner to v3
Expand Down
42 changes: 30 additions & 12 deletions lib/access-token.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const debug = require('debug')('access-token');
const Hoek = require('@hapi/hoek');
const debug = require('debug')('simple-oauth2:access-token');
const isDate = require('date-fns/isDate');
const parseISO = require('date-fns/parseISO');
const addSeconds = require('date-fns/addSeconds');
Expand Down Expand Up @@ -32,60 +33,77 @@ function parseToken(token) {
return Object.assign({}, token, tokenProperties);
}

const ACCESS_TOKEN_PROPERTY_NAME = 'access_token';
const REFRESH_TOKEN_PROPERTY_NAME = 'refresh_token';

module.exports = class AccessToken {
static factory(config, client) {
return (token) => new AccessToken(config, client, token);
}

constructor(config, client, token) {
Hoek.assert(config, 'Cannot create access token without client configuration');
Hoek.assert(client, 'Cannot create access token without client instance');
Hoek.assert(token, 'Cannot create access token without a token to parse');

this.config = config;
this.client = client;
this.token = parseToken(token);
}

/**
* Check if the access token is expired or not
* Check if the current access token is definitely expired or not
*
* @returns {Boolean}
*/
expired() {
return isAfter(new Date(), this.token.expires_at);
}

/**
* Refresh the access token
* Refreshes the current access token
*
* @param {Object} params An optional argument for additional API request params.
* @param {String|Array<String>} params.scope A String or array of strings representing the application privileges
* @returns {Promise<AccessToken>}
*/
async refresh(params = {}) {
const refreshParams = Object.assign({}, params, {
refresh_token: this.token.refresh_token,
});

const parameters = GrantParams.forGrant('refresh_token', refreshParams);
const parameters = GrantParams.forGrant(REFRESH_TOKEN_PROPERTY_NAME, refreshParams);
const response = await this.client.request(this.config.auth.tokenPath, parameters.toObject());

return new AccessToken(this.config, this.client, response);
}

/**
* Revoke access or refresh token
* @param {String} tokenType A string containing the type of token to revoke.
* Should be either "access_token" or "refresh_token"
* Revokes either the access or refresh token depending on the {tokenType} value
*
* @param {String} tokenType A string containing the type of token to revoke (access_token or refresh_token)
* @returns {Promise}
*/
async revoke(tokenType) {
const token = tokenType === 'access_token' ? this.token.access_token : this.token.refresh_token;
Hoek.assert(
tokenType === ACCESS_TOKEN_PROPERTY_NAME || tokenType === REFRESH_TOKEN_PROPERTY_NAME,
`Invalid token type. Only ${ACCESS_TOKEN_PROPERTY_NAME} or ${REFRESH_TOKEN_PROPERTY_NAME} are valid values`
);

const options = {
token,
token: this.token[tokenType],
token_type_hint: tokenType,
};

return this.client.request(this.config.auth.revokePath, options);
}

/**
* Revoke both the existing access and refresh tokens
* Revokes the current access and refresh tokens
* @returns {Promise}
*/
async revokeAll() {
await this.revoke('access_token');
await this.revoke('refresh_token');
await this.revoke(ACCESS_TOKEN_PROPERTY_NAME);
await this.revoke(REFRESH_TOKEN_PROPERTY_NAME);
}
};
12 changes: 7 additions & 5 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ const Wreck = require('@hapi/wreck');
const debug = require('debug')('simple-oauth2:client');
const RequestOptions = require('./request-options');

const defaultHttpHeaders = {
Accept: 'application/json',
};

const defaultHttpOptions = {
json: 'strict',
redirects: 20,
headers: {
Accept: 'application/json',
},
headers: defaultHttpHeaders,
};

module.exports = class Client {
Expand All @@ -32,8 +34,8 @@ module.exports = class Client {
debug('Creating request to: (POST) %s', url);
debug('Using request options: %j', options);

const result = await this.client.post(url, options);
const response = await this.client.post(url, options);

return result.payload;
return response.payload;
}
};
21 changes: 21 additions & 0 deletions test/access_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ const scopeOptions = {
},
};

test('@create => throws an error when no token payload is provided', (t) => {
const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);

t.throws(() => oauth2.accessToken.create(), {
message: /Cannot create access token without a token to parse/,
});
});

test('@create => creates a new access token instance', (t) => {
const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);
Expand Down Expand Up @@ -359,6 +368,18 @@ test.serial('@revoke => performs a token revoke with a custom revoke path', asyn
scope.done();
});

test.serial('@revoke => throws an error with an invalid tokenType option', async (t) => {
const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);

const accessTokenResponse = chance.accessToken();
const accessToken = oauth2.accessToken.create(accessTokenResponse);

await t.throwsAsync(() => accessToken.revoke('invalid_value'), {
message: /Invalid token type. Only access_token or refresh_token are valid values/,
});
});

test.serial('@revokeAll => revokes both the access and refresh tokens', async (t) => {
const config = createModuleConfig();
const oauth2 = oauth2Module.create(config);
Expand Down