diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js
index 02f813eb7..22a58a475 100644
--- a/src/auth/OAuthAuthenticator.js
+++ b/src/auth/OAuthAuthenticator.js
@@ -17,6 +17,9 @@ function getParamsFromOptions(options) {
req.set('auth0-forwarded-for', options.forwardedFor);
};
}
+ if (options.type) {
+ params.type = options.type;
+ }
return params;
}
@@ -117,7 +120,10 @@ OAuthAuthenticator.prototype.signIn = function(userData, options, cb) {
throw new ArgumentError('Missing user data object');
}
- if (typeof data.connection !== 'string' || data.connection.split().length === 0) {
+ if (
+ params.type === 'ro' &&
+ (typeof data.connection !== 'string' || data.connection.split().length === 0)
+ ) {
throw new ArgumentError('connection field is required');
}
diff --git a/src/auth/PasswordlessAuthenticator.js b/src/auth/PasswordlessAuthenticator.js
index 794649604..38e1122ad 100644
--- a/src/auth/PasswordlessAuthenticator.js
+++ b/src/auth/PasswordlessAuthenticator.js
@@ -48,7 +48,7 @@ var PasswordlessAuthenticator = function(options, oauth) {
* @example
* Given the user credentials (`phone_number` and `code`), it will do the
* authentication on the provider and return a JSON with the `access_token`
- * and `id_token`.
+ * and `id_token` using `/oauth/ro` endpoint.
*
*
* var data = {
@@ -63,6 +63,21 @@ var PasswordlessAuthenticator = function(options, oauth) {
* });
*
* @example
+ * To use `/oauth/token` endpoint, use `otp` and `realm` instead
+ *
+ *
+ * var data = {
+ * username: '{PHONE_NUMBER}',
+ * otp: '{VERIFICATION_CODE}'
+ * };
+ *
+ * auth0.passwordless.signIn(data, function (err) {
+ * if (err) {
+ * // Handle error.
+ * }
+ * });
+ *
+ * @example
* The user data object has the following structure.
*
*
@@ -73,9 +88,11 @@ var PasswordlessAuthenticator = function(options, oauth) {
* }
*
* @param {Object} userData User credentials object.
- * @param {String} userData.username Username.
- * @param {String} userData.password Password.
- * @param {String} [userData.connection=sms] Connection string: "sms" or "email".
+ * @param {String} userData.otp The user's verification code.
+ * @param {String} [userData.realm=sms] Realm string: "sms" or "email".
+ * @param {String} userData.username The user's phone number if realm=sms, or the user's email if realm=email
+ * @param {String} userData.password [DEPRECATED] Password.
+ * @param {String} [userData.connection=sms] [DEPRECATED] Connection string: "sms" or "email".
* @param {Function} [cb] Method callback.
*
* @return {Promise|undefined}
@@ -87,12 +104,6 @@ PasswordlessAuthenticator.prototype.signIn = function(userData, cb) {
};
var data = extend(defaultFields, userData);
- // Don't let the user override the connection nor the grant type.
- if (!data.connection || (data.connection !== 'email' && data.connection !== 'sms')) {
- data.connection = 'sms';
- }
- data.grant_type = 'password';
-
if (!userData || typeof userData !== 'object') {
throw new ArgumentError('Missing user data object');
}
@@ -101,10 +112,29 @@ PasswordlessAuthenticator.prototype.signIn = function(userData, cb) {
throw new ArgumentError('username field (phone number) is required');
}
+ // If otp is provided, attempt to sign in using otp grant
+ if (typeof data.otp === 'string' && data.otp.trim().length > 0) {
+ if (!data.realm || (data.realm !== 'email' && data.realm !== 'sms')) {
+ data.realm = 'sms';
+ }
+ data.grant_type = 'http://auth0.com/oauth/grant-type/passwordless/otp';
+ return this.oauth.signIn(data, { type: 'token' }, cb);
+ }
+
+ // Don't let the user override the connection nor the grant type.
+ if (!data.connection || (data.connection !== 'email' && data.connection !== 'sms')) {
+ data.connection = 'sms';
+ }
+ data.grant_type = 'password';
+
if (typeof data.password !== 'string' || data.password.trim().length === 0) {
throw new ArgumentError('password field (verification code) is required');
}
+ console.warn(
+ 'The oauth/ro endpoint has been deprecated. Please use the realm and otp parameters in this function.'
+ );
+
return this.oauth.signIn(data, cb);
};
diff --git a/test/auth/passwordless.tests.js b/test/auth/passwordless.tests.js
index 67271376d..c694ad545 100644
--- a/test/auth/passwordless.tests.js
+++ b/test/auth/passwordless.tests.js
@@ -48,202 +48,404 @@ describe('PasswordlessAuthenticator', function() {
});
describe('#signIn', function() {
- var path = '/oauth/ro';
- var userData = {
- username: 'username',
- password: 'pwd'
- };
-
- beforeEach(function() {
- var oauth = new OAuth(validOptions);
- this.authenticator = new Authenticator(validOptions, oauth);
- this.request = nock(API_URL)
- .post(path)
- .reply(200);
- });
+ describe('/oauth/ro', function() {
+ var path = '/oauth/ro';
+ var userData = {
+ username: 'username',
+ password: 'pwd'
+ };
+
+ beforeEach(function() {
+ var oauth = new OAuth(validOptions);
+ this.authenticator = new Authenticator(validOptions, oauth);
+ this.request = nock(API_URL)
+ .post(path)
+ .reply(200);
+ });
- it('should require an object as first argument', function() {
- expect(this.authenticator.signIn).to.throw(ArgumentError, 'Missing user data object');
- });
+ it('should require an object as first argument', function() {
+ expect(this.authenticator.signIn).to.throw(ArgumentError, 'Missing user data object');
+ });
- it('should require a phone number', function() {
- var auth = this.authenticator;
- var userData = { password: 'password' };
- var signIn = auth.signIn.bind(auth, userData);
+ it('should require a phone number', function() {
+ var auth = this.authenticator;
+ var userData = { password: 'password' };
+ var signIn = auth.signIn.bind(auth, userData);
- expect(signIn).to.throw(ArgumentError, 'username field (phone number) is required');
- });
+ expect(signIn).to.throw(ArgumentError, 'username field (phone number) is required');
+ });
- it('should require a verification code', function() {
- var auth = this.authenticator;
- var userData = { username: 'username' };
- var signIn = auth.signIn.bind(auth, userData);
+ it('should require a verification code', function() {
+ var auth = this.authenticator;
+ var userData = { username: 'username' };
+ var signIn = auth.signIn.bind(auth, userData);
- expect(signIn).to.throw(ArgumentError, 'password field (verification code) is required');
- });
+ expect(signIn).to.throw(ArgumentError, 'password field (verification code) is required');
+ });
- it('should accept a callback', function(done) {
- this.authenticator.signIn(userData, done.bind(null, null));
- });
+ it('should accept a callback', function(done) {
+ this.authenticator.signIn(userData, done.bind(null, null));
+ });
- it('should return a promise when no callback is provided', function(done) {
- this.authenticator
- .signIn(userData)
- .then(done.bind(null, null))
- .catch(done.bind(null, null));
- });
+ it('should return a promise when no callback is provided', function(done) {
+ this.authenticator
+ .signIn(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;
+ it('should perform a POST request to ' + path, function(done) {
+ var request = this.request;
- this.authenticator
- .signIn(userData)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
- done();
- })
- .catch(done);
- });
+ done();
+ })
+ .catch(done);
+ });
- it('should include the user data in the request', function(done) {
- nock.cleanAll();
+ 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;
+ 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);
+ return true;
+ })
+ .reply(200);
- this.authenticator
- .signIn(userData)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
- done();
- })
- .catch(done);
- });
+ done();
+ })
+ .catch(done);
+ });
- it('should include the Auth0 client ID in the request', function(done) {
- nock.cleanAll();
+ 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);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.client_id === CLIENT_ID;
+ })
+ .reply(200);
- this.authenticator
- .signIn(userData)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
- done();
- })
- .catch(done);
- });
+ done();
+ })
+ .catch(done);
+ });
- it('should use SMS connection', function(done) {
- nock.cleanAll();
+ it('should use SMS connection', function(done) {
+ nock.cleanAll();
- var request = nock(API_URL)
- .post(path, function(body) {
- return body.connection === 'sms';
- })
- .reply(200);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.connection === 'sms';
+ })
+ .reply(200);
- this.authenticator
- .signIn(userData)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
- done();
- })
- .catch(done);
- });
+ done();
+ })
+ .catch(done);
+ });
- it('should use email connection', function(done) {
- nock.cleanAll();
- var data = extend({ connection: 'email' }, userData);
- var request = nock(API_URL)
- .post(path, function(body) {
- return body.connection === 'email';
- })
- .reply(200);
+ it('should use email connection', function(done) {
+ nock.cleanAll();
+ var data = extend({ connection: 'email' }, userData);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.connection === 'email';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(data)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
- this.authenticator
- .signIn(data)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ it('should allow the user to specify the connection as sms or email', function(done) {
+ nock.cleanAll();
- done();
- })
- .catch(done);
- });
+ var data = extend({ connection: 'TEST_CONNECTION' }, userData);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.connection === 'sms' || body.connection === 'email';
+ })
+ .reply(200);
- it('should allow the user to specify the connection as sms or email', function(done) {
- nock.cleanAll();
+ this.authenticator
+ .signIn(data)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
- var data = extend({ connection: 'TEST_CONNECTION' }, userData);
- var request = nock(API_URL)
- .post(path, function(body) {
- return body.connection === 'sms' || body.connection === 'email';
- })
- .reply(200);
+ done();
+ })
+ .catch(done);
+ });
- this.authenticator
- .signIn(data)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ it('should use password as grant type', function(done) {
+ nock.cleanAll();
- done();
- })
- .catch(done);
- });
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.grant_type === 'password';
+ })
+ .reply(200);
- it('should use password as grant type', function(done) {
- nock.cleanAll();
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
- var request = nock(API_URL)
- .post(path, function(body) {
- return body.grant_type === 'password';
- })
- .reply(200);
+ done();
+ })
+ .catch(done);
+ });
- this.authenticator
- .signIn(userData)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ it('should use the openid scope', function(done) {
+ nock.cleanAll();
- done();
- })
- .catch(done);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.scope === 'openid';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
});
- it('should use the openid scope', function(done) {
- nock.cleanAll();
+ describe('/oauth/token', function() {
+ var path = '/oauth/token';
+ var userData = {
+ username: 'username',
+ otp: '000000'
+ };
- var request = nock(API_URL)
- .post(path, function(body) {
- return body.scope === 'openid';
- })
- .reply(200);
+ beforeEach(function() {
+ var oauth = new OAuth(validOptions);
+ this.authenticator = new Authenticator(validOptions, oauth);
+ this.request = nock(API_URL)
+ .post(path)
+ .reply(200);
+ });
- this.authenticator
- .signIn(userData)
- .then(function() {
- expect(request.isDone()).to.be.true;
+ it('should require an object as first argument', function() {
+ expect(this.authenticator.signIn).to.throw(ArgumentError, 'Missing user data object');
+ });
- done();
- })
- .catch(done);
+ it('should require a phone number', function() {
+ var auth = this.authenticator;
+ var userData = { otp: '000000' };
+ var signIn = auth.signIn.bind(auth, userData);
+
+ expect(signIn).to.throw(ArgumentError, 'username field (phone number) is required');
+ });
+
+ it('should require a verification code', function() {
+ var auth = this.authenticator;
+ var userData = { username: 'username' };
+ var signIn = auth.signIn.bind(auth, userData);
+
+ expect(signIn).to.throw(ArgumentError, 'password field (verification code) is required');
+ });
+
+ it('should accept a callback', function(done) {
+ this.authenticator.signIn(userData, done.bind(null, null));
+ });
+
+ it('should return a promise when no callback is provided', function(done) {
+ this.authenticator
+ .signIn(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
+ .signIn(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
+ .signIn(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
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
+
+ it('should use SMS realm', function(done) {
+ nock.cleanAll();
+
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.realm === 'sms';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
+
+ it('should use email realm', function(done) {
+ nock.cleanAll();
+ var data = extend({ realm: 'email' }, userData);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.realm === 'email';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(data)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
+
+ it('should allow the user to specify the realm as sms or email', function(done) {
+ nock.cleanAll();
+
+ var data = extend({ realm: 'TEST_CONNECTION' }, userData);
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.realm === 'sms' || body.realm === 'email';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(data)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
+
+ it('should use otp as grant type', function(done) {
+ nock.cleanAll();
+
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.grant_type === 'http://auth0.com/oauth/grant-type/passwordless/otp';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
+
+ it('should use the openid scope', function(done) {
+ nock.cleanAll();
+
+ var request = nock(API_URL)
+ .post(path, function(body) {
+ return body.scope === 'openid';
+ })
+ .reply(200);
+
+ this.authenticator
+ .signIn(userData)
+ .then(function() {
+ expect(request.isDone()).to.be.true;
+
+ done();
+ })
+ .catch(done);
+ });
});
});