+ * Given an existing refresh token, this endpoint will revoke it in order
+ * to prevent unauthorized silently user authentication tokens refresh.
+ * Find more information in the API Docs.
+ *
+ *
+ * * var data = {
+ * token: '{REFRESH_TOKEN}'
+ * };
+ *
+ * auth0.tokens.revokeRefreshToken(data, function (err, _) {
+ * if (err) {
+ * // Handle error.
+ * }
+ *
+ * // Do stuff.
+ * });
+ *
+ * @param {Object} data Token data object.
+ * @param {String} data.token User refresh token.
+ * @param {String} [data.client_id] Target client ID.
+ * @param {String} [data.client_secret] Target client secret.
+ * @param {Function} [cb] Callback function.
+ *
+ * @return {Promise|undefined}
+ */
+TokensManager.prototype.revokeRefreshToken = function(data, cb) {
+ if (!data) {
+ throw new ArgumentError('Missing token data object');
+ }
+
+ var hasToken = typeof data.token === 'string' && data.token.trim().length !== 0;
+
+ if (!hasToken) {
+ throw new ArgumentError('token property is required');
+ }
+
+ var hasClientId =
+ (data.client_id && typeof data.client_id === 'string' && data.client_id.trim().length !== 0) ||
+ this.clientId !== '';
+
+ if (!hasClientId) {
+ throw new ArgumentError(
+ 'Neither token data client_id property or constructor clientId property has been set'
+ );
+ }
+
+ var body = extend(
+ {
+ client_id: this.clientId,
+ client_secret: this.clientSecret
+ },
+ data
+ );
+
+ var headers = this.headers;
+
+ // Perform the request.
+ var promise = axios({
+ method: 'POST',
+ url: this.baseUrl + '/oauth/revoke',
+ data: body,
+ headers: headers
+ }).then(({ data }) => data);
+
+ // Use callback if given.
+ if (cb instanceof Function) {
+ promise.then(cb.bind(null, null)).catch(cb);
+ return;
+ }
+
+ return promise;
+};
+
module.exports = TokensManager;
diff --git a/test/auth/tokens-manager.tests.js b/test/auth/tokens-manager.tests.js
index a9a4c9c9b..f77838a51 100644
--- a/test/auth/tokens-manager.tests.js
+++ b/test/auth/tokens-manager.tests.js
@@ -14,7 +14,8 @@ describe('TokensManager', function() {
'Content-Type': 'application/json',
'Test-Header': 'TEST'
},
- clientId: 'CLIENT_ID'
+ clientId: 'CLIENT_ID',
+ clientSecret: 'CLIENT_SECRET'
};
afterEach(function() {
@@ -34,7 +35,7 @@ describe('TokensManager', function() {
});
describe('instance', function() {
- var methods = ['getInfo', 'getDelegationToken'];
+ var methods = ['getInfo', 'getDelegationToken', 'revokeRefreshToken'];
var manager = new TokensManager(validOptions);
methods.forEach(function(methodName) {
@@ -327,4 +328,203 @@ describe('TokensManager', function() {
});
});
});
+
+ describe('#revokeRefreshToken', function() {
+ var path = '/oauth/revoke';
+ var manager = new TokensManager(validOptions);
+
+ beforeEach(function() {
+ this.request = nock(BASE_URL)
+ .post(path)
+ .reply(200);
+ });
+
+ it('should require a token data object', function() {
+ var revokeRefreshToken = manager.revokeRefreshToken.bind(manager);
+
+ expect(revokeRefreshToken).to.throw(ArgumentError, 'Missing token data object');
+ });
+
+ it('should require a token property in the token data object', function() {
+ var data = {};
+ var revokeRefreshToken = manager.revokeRefreshToken.bind(manager, data);
+
+ expect(revokeRefreshToken).to.throw(ArgumentError, 'token property is required');
+ });
+
+ it('should require at least a target client ID', function() {
+ var manager = new TokensManager({
+ baseUrl: BASE_URL,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Test-Header': 'TEST'
+ }
+ });
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+
+ var revokeRefreshToken = manager.revokeRefreshToken.bind(manager, data);
+
+ expect(revokeRefreshToken).to.throw(
+ ArgumentError,
+ 'Neither token data client_id property or constructor clientId property has been set'
+ );
+ });
+
+ it('should accept a callback', function(done) {
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+
+ manager.revokeRefreshToken(data, done.bind(null, null));
+ });
+
+ it('should return a promise when no callback is given', function() {
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+ var returnValue = manager.revokeRefreshToken(data);
+ expect(utilTypes.isPromise(returnValue)).ok;
+ });
+
+ it('should not return a promise when a callback is given', function() {
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+ var returnValue = manager.revokeRefreshToken(data, function() {});
+
+ expect(returnValue).to.equal(undefined);
+ });
+
+ it('should perform a POST request to ' + path, function() {});
+
+ it('should include the data in the body of the request', function(done) {
+ nock.cleanAll();
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+
+ var request = nock(BASE_URL)
+ .post(path, function(body) {
+ for (var property in data) {
+ if (body[property] !== data[property]) {
+ return false;
+ }
+ }
+
+ return true;
+ })
+ .reply();
+
+ manager.revokeRefreshToken(data).then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ });
+ });
+
+ it('should use the TokensManager instance client ID if none specified', function(done) {
+ nock.cleanAll();
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+
+ var request = nock(BASE_URL)
+ .post(path, function(body) {
+ return body.client_id === validOptions.clientId;
+ })
+ .reply();
+
+ manager.revokeRefreshToken(data).then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ });
+ });
+
+ it('should let the user override the default client ID', function(done) {
+ nock.cleanAll();
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN',
+ client_id: 'OVERRIDEN_CLIENT_ID'
+ };
+
+ var request = nock(BASE_URL)
+ .post(path, function(body) {
+ return body.client_id === data.client_id;
+ })
+ .reply();
+
+ manager.revokeRefreshToken(data).then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ });
+ });
+
+ it('should use the TokensManager instance client secret if none specified', function(done) {
+ nock.cleanAll();
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+
+ var request = nock(BASE_URL)
+ .post(path, function(body) {
+ return body.client_secret === validOptions.clientSecret;
+ })
+ .reply();
+
+ manager.revokeRefreshToken(data).then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ });
+ });
+
+ it('should let the user override the default client secret', function(done) {
+ nock.cleanAll();
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN',
+ client_secret: 'OVERRIDEN_CLIENT_SECRET'
+ };
+
+ var request = nock(BASE_URL)
+ .post(path, function(body) {
+ return body.client_secret === data.client_secret;
+ })
+ .reply();
+
+ manager.revokeRefreshToken(data).then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ });
+ });
+
+ it('should include the headers specified in the instance options', function(done) {
+ nock.cleanAll();
+
+ var data = {
+ token: 'TEST_REFRESH_TOKEN'
+ };
+
+ var request = nock(BASE_URL)
+ .post(path)
+ .matchHeader('Test-Header', validOptions.headers['Test-Header'])
+ .reply(200);
+
+ manager.revokeRefreshToken(data).then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ });
+ });
+ });
});