Repository: auth0/node-auth0 (ts)

Issue:
Grant type `/oauth/ro` deprecated
Hi, can you change **grant_type: "/oauth/ro"** to **grant_type:"password"**

Pull request:
Add password grant method
Also supports password realm grant

Closes #186

Patch:
diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js
index 2acaed3dc..5dc7e6e60 100644
--- a/src/auth/OAuthAuthenticator.js
+++ b/src/auth/OAuthAuthenticator.js
@@ -47,23 +47,23 @@ var OAuthAuthenticator = function (options) {
  * @memberOf  module:auth.OAuthAuthenticator.prototype
  *
  * @example <caption>
- *   Given the social provider's access_token and the connection specified, it
- *   will do the authentication on the provider and return a JSON with the
- *   access_token and id_token. Currently, this endpoint only works for
- *   Facebook, Google, Twitter and Weibo. More information in the
- *   <a href="https://auth0.com/docs/auth-api#!#post--oauth-access_token">
+ *   Given the user's credentials and the connection specified, it
+ *   will return a JSON with the access_token and id_token.
+ *   More information in the
+ *   <a href="https://auth0.com/docs/api/authentication#database-ad-ldap-active-">
  *     API Docs
  *   </a>.
  * </caption>
  *
  * var data = {
  *   client_id: '{CLIENT_ID}',  // Optional field.
- *   access_token: '{USER_SOCIAL_ACCESS_TOKEN}',
- *   connection: 'facebook',
+ *   username: '{USERNAME}',
+ *   password: '{PASSWORD}
+ *   connection: '{CONNECTION_NAME}',
  *   scope: 'openid'  // Optional field.
  * };
  *
- * auth0.oauth.socialSignIn(data, function (err, userData) {
+ * auth0.oauth.signIn(data, function (err, userData) {
  *   if (err) {
  *     // Handle error.
  *   }
@@ -106,6 +106,80 @@ OAuthAuthenticator.prototype.signIn = function (userData, cb) {
   return this.oauth.create(params, data);
 };
 
+/**
+ * Sign in using a username and password
+ *
+ * @method    passwordGrant
+ * @memberOf  module:auth.OAuthAuthenticator.prototype
+ *
+ * @example <caption>
+ *   Given the user's credentials perform the OAuth password grant
+ *   or Password Realm grant if a realm is provided,
+ *   it will return a JSON with the access_token and id_token.
+ *   More information in the
+ *   <a href="https://auth0.com/docs/api/authentication#resource-owner-password">
+ *     API Docs
+ *   </a>.
+ * </caption>
+ *
+ * var data = {
+ *   client_id: '{CLIENT_ID}',  // Optional field.
+ *   username: '{USERNAME}',
+ *   password: '{PASSWORD}'
+ *   realm: '{CONNECTION_NAME}', // Optional field.
+ *   scope: 'openid'  // Optional field.
+ * };
+ *
+ * auth0.oauth.token(data, function (err, userData) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(userData);
+ * });
+ *
+ * @param   {Object}    userData              User credentials object.
+ * @param   {String}    userData.username     Username.
+ * @param   {String}    userData.password     User password.
+ * @param   {String}    [userData.realm]      Name of the realm to use to authenticate or the connection name
+ *
+ * @return  {Promise|undefined}
+ */
+OAuthAuthenticator.prototype.passwordGrant = function (userData, cb) {
+  var params = {
+    type: 'token'
+  };
+  var defaultFields = {
+    client_id: this.clientId,
+    grant_type: 'password'
+  };
+  var data = extend(defaultFields, userData);
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.username !== 'string'
+      || data.username.split().length === 0) {
+    throw new ArgumentError('username field is required');
+  }
+
+  if (typeof data.password !== 'string'
+      || data.password.split().length === 0) {
+    throw new ArgumentError('password field is required');
+  }
+
+  if (typeof data.realm === 'string'
+      && data.realm.split().length !== 0) {
+    data.grant_type = 'http://auth0.com/oauth/grant-type/password-realm';
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.oauth.create(params, data, cb);
+  }
+
+  return this.oauth.create(params, data);
+};
 
 /**
  * Sign in using a social provider access token.
diff --git a/src/auth/index.js b/src/auth/index.js
index e7ea2d7c8..679857d5a 100644
--- a/src/auth/index.js
+++ b/src/auth/index.js
@@ -498,11 +498,51 @@ utils.wrapPropertyMethod(AuthenticationClient, 'getProfile', 'users.getInfo');
  *   console.log(response);
  * });
  *
- * @param     {String}  scope   
+ * @param     {String}  scope
  *
  * @return    {Promise|undefined}
  */
 utils.wrapPropertyMethod(AuthenticationClient, 'clientCredentialsGrant', 'oauth.clientCredentialsGrant');
 
+/**
+ * Sign in using a username and password
+ *
+ * @method    passwordGrant
+ * @memberOf  module:auth.OAuthAuthenticator.prototype
+ *
+ * @example <caption>
+ *   Given the user's credentials perform the OAuth password grant
+ *   or Password Realm grant if a realm is provided,
+ *   it will return a JSON with the access_token and id_token.
+ *   More information in the
+ *   <a href="https://auth0.com/docs/api/authentication#resource-owner-password">
+ *     API Docs
+ *   </a>.
+ * </caption>
+ *
+ * var data = {
+ *   client_id: '{CLIENT_ID}',  // Optional field.
+ *   username: '{USERNAME}',
+ *   password: '{PASSWORD}'
+ *   realm: '{CONNECTION_NAME}', // Optional field.
+ *   scope: 'openid'  // Optional field.
+ * };
+ *
+ * auth0.oauth.token(data, function (err, userData) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(userData);
+ * });
+ *
+ * @param   {Object}    userData              User credentials object.
+ * @param   {String}    userData.username     Username.
+ * @param   {String}    userData.password     User password.
+ * @param   {String}    [userData.realm]      Name of the realm to use to authenticate or the connection name
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(AuthenticationClient, 'passwordGrant', 'oauth.passwordGrant');
 
 module.exports = AuthenticationClient;


Test patch:
diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js
index 75b284a44..2b7dedcd3 100644
--- a/test/auth/oauth.tests.js
+++ b/test/auth/oauth.tests.js
@@ -42,7 +42,7 @@ describe('OAuthAuthenticator', function () {
 
 
   describe('instance', function () {
-    var methods = ['signIn', 'socialSignIn'];
+    var methods = ['signIn', 'socialSignIn', 'passwordGrant'];
     var authenticator = new Authenticator(validOptions);
 
     methods.forEach(function (method) {
@@ -257,6 +257,170 @@ describe('OAuthAuthenticator', function () {
     });
   });
 
+  describe('#passwordGrant', function () {
+    var path = '/oauth/token';
+    var userData = {
+      username: 'username',
+      password: 'pwd'
+    };
+
+    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.passwordGrant)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+    it('should require a username', function () {
+      var auth = this.authenticator;
+      var signIn = auth.passwordGrant.bind(auth, {password: 'pwd'});
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'username field is required');
+    });
+
+    it('should require a password', function () {
+      var auth = this.authenticator;
+      var signIn = auth.passwordGrant.bind(auth, {username: 'samples@auth0.com'});
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'password field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .passwordGrant(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .passwordGrant(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
+        .passwordGrant(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
+        .passwordGrant(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
+        .passwordGrant(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should allow the user to specify the realm', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.realm === 'Username-Password-Authentication'
+          && body.grant_type === 'http://auth0.com/oauth/grant-type/password-realm';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .passwordGrant(Object.assign({realm: 'Username-Password-Authentication'}, userData))
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use password as default grant type', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.grant_type === 'password';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .passwordGrant(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
 
   describe('#socialSignIn', function () {
     var path = '/oauth/access_token';