Repository: auth0/node-auth0 (ts)

Issue:
Add a method for the refresh_token grant type
There is an obsolete method for delegation flows that used to (kind of) support the refresh_token grant type.

At this point, there's no API method for refreshing a token.

Please add one!

Pull request description:
Adds support for fetching an auth token using a refresh token
This uses code from [here](https://github.com/auth0/node-auth0/pull/241), with a couple of fixes required to make it work.

Fixes #240 

Patch:
diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js
index 0d2b33d81..d8df93582 100644
--- a/src/auth/OAuthAuthenticator.js
+++ b/src/auth/OAuthAuthenticator.js
@@ -179,6 +179,60 @@ OAuthAuthenticator.prototype.passwordGrant = function(userData, cb) {
   return this.oauthWithIDTokenValidation.create(params, data);
 };
 
+/**
+ * Sign in using a refresh token
+ *
+ * @method    refreshToken
+ * @memberOf  module:auth.OAuthAuthenticator.prototype
+ *
+ * @example <caption>
+ *   Given a refresh token from a previous authentication request
+ *   it will return a JSON with the access_token and id_token.
+ *   More information in the
+ *   <a href="https://auth0.com/docs/api/authentication#refresh-token">
+ *     API Docs
+ *   </a>.
+ * </caption>
+ *
+ * var data = {
+ *   client_id: '{CLIENT_ID}', // Optional field.
+ *   refresh_token: '{REFRESH_TOKEN}',
+ * };
+ *
+ * auth0.oauth.refreshToken(data, function (err, userData) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(userData);
+ * });
+ *
+ * @param   {Object}    userData                User credentials object.
+ * @param   {String}    userData.refresh_token  Refresh token.
+ *
+ * @return  {Promise|undefined}
+ */
+OAuthAuthenticator.prototype.refreshToken = function(userData, cb) {
+  var params = {
+    type: 'token'
+  };
+  var defaultFields = {
+    client_id: this.clientId,
+    grant_type: 'refresh_token'
+  };
+  var data = extend(defaultFields, userData);
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+  if (typeof data.refresh_token !== 'string' || data.refresh_token.split().length === 0) {
+    throw new ArgumentError('refresh_token is required');
+  }
+  if (cb && cb instanceof Function) {
+    return this.oauthWithIDTokenValidation.create(params, data, cb);
+  }
+  return this.oauthWithIDTokenValidation.create(params, data);
+};
+
 /**
  * Sign in using a social provider access token.
  *
diff --git a/src/auth/index.js b/src/auth/index.js
index fa410aa9c..acb70f253 100644
--- a/src/auth/index.js
+++ b/src/auth/index.js
@@ -543,4 +543,39 @@ utils.wrapPropertyMethod(
  */
 utils.wrapPropertyMethod(AuthenticationClient, 'passwordGrant', 'oauth.passwordGrant');
 
+/**
+ * Sign in using a refresh token
+ *
+ * @method    refreshToken
+ * @memberOf  module:auth.AuthenticationClient.prototype
+ *
+ * @example <caption>
+ *   Given a refresh token from a previous authentication request,
+ *   it will return a JSON with the access_token and id_token.
+ *   More information in the
+ *   <a href="https://auth0.com/docs/api/authentication#refresh-token">
+ *     API Docs
+ *   </a>.
+ * </caption>
+ *
+ * var data = {
+ *   client_id: '{CLIENT_ID}', // Optional field.
+ *   refresh_token: '{REFRESH_TOKEN}',
+ * };
+ *
+ * auth0.refreshToken(data, function (err, userData) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(userData);
+ * });
+ *
+ * @param   {Object}    userData                User credentials object.
+ * @param   {String}    userData.refresh_token  Refresh token.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(AuthenticationClient, 'refreshToken', 'oauth.refreshToken');
+
 module.exports = AuthenticationClient;


Test patch:
diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js
index 3dcb436a2..f91d4104e 100644
--- a/test/auth/oauth.tests.js
+++ b/test/auth/oauth.tests.js
@@ -44,7 +44,13 @@ describe('OAuthAuthenticator', function() {
   });
 
   describe('instance', function() {
-    var methods = ['signIn', 'socialSignIn', 'passwordGrant', 'authorizationCodeGrant'];
+    var methods = [
+      'signIn',
+      'socialSignIn',
+      'passwordGrant',
+      'authorizationCodeGrant',
+      'refreshToken'
+    ];
     var authenticator = new Authenticator(validOptions);
 
     methods.forEach(function(method) {
@@ -405,6 +411,96 @@ describe('OAuthAuthenticator', function() {
     });
   });
 
+  describe('#refreshToken', function() {
+    var path = '/oauth/token';
+    var userData = {
+      refresh_token: 'refresh_token'
+    };
+    beforeEach(function() {
+      this.authenticator = new Authenticator(validOptions);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+    it('should require an object as first argument', function() {
+      expect(this.authenticator.refreshToken).to.throw(ArgumentError, 'Missing user data object');
+    });
+    it('should require a refreshToken', function() {
+      var auth = this.authenticator;
+      var refresh = auth.refreshToken.bind(auth, {});
+      expect(refresh).to.throw(ArgumentError, 'refresh_token is required');
+    });
+    it('should accept a callback', function(done) {
+      this.authenticator.refreshToken(userData, done.bind(null, null));
+    });
+    it('should return a promise when no callback is provided', function(done) {
+      this.authenticator
+        .refreshToken(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+    it('should perform a POST request to ' + path, function(done) {
+      var request = this.request;
+      this.authenticator
+        .refreshToken(userData)
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+          done();
+        })
+        .catch(done);
+    });
+    it('should include the user data in the request', function(done) {
+      nock.cleanAll();
+      var request = nock(API_URL)
+        .post(path, function(body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+          return true;
+        })
+        .reply(200);
+      this.authenticator
+        .refreshToken(userData)
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+          done();
+        })
+        .catch(done);
+    });
+    it('should include the Auth0 client ID in the request', function(done) {
+      nock.cleanAll();
+      var request = nock(API_URL)
+        .post(path, function(body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+      this.authenticator
+        .refreshToken(userData)
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+          done();
+        })
+        .catch(done);
+    });
+    it('should use refresh_token as default grant type', function(done) {
+      nock.cleanAll();
+      var request = nock(API_URL)
+        .post(path, function(body) {
+          return body.grant_type === 'refresh_token';
+        })
+        .reply(200);
+      this.authenticator
+        .refreshToken(userData)
+        .then(function() {
+          expect(request.isDone()).to.be.true;
+          done();
+        })
+        .catch(done);
+    });
+  });
+
   describe('#socialSignIn', function() {
     var path = '/oauth/access_token';
     var userData = {