diff --git a/example/index.html b/example/index.html index fff7faa5..564a7f75 100644 --- a/example/index.html +++ b/example/index.html @@ -12,6 +12,7 @@ } pre { margin: 0 0 10px 0; + min-height: 300px; } code { min-height: 300px; @@ -39,15 +40,40 @@ diff --git a/src/authentication/db-connection.js b/src/authentication/db-connection.js index dc74e3f7..9131cf4e 100644 --- a/src/authentication/db-connection.js +++ b/src/authentication/db-connection.js @@ -29,7 +29,7 @@ DBConnection.prototype.signup = function (options, cb) { body = objectHelper.toSnakeCase(body, ['auth0Client']); - this.request + return this.request .post(url) .send(body) .end(responseHandler(cb)); @@ -52,7 +52,7 @@ DBConnection.prototype.changePassword = function (options, cb) { body = objectHelper.toSnakeCase(body, ['auth0Client']); - this.request + return this.request .post(url) .send(body) .end(responseHandler(cb)); diff --git a/src/authentication/index.js b/src/authentication/index.js index f1a4be61..a924d249 100644 --- a/src/authentication/index.js +++ b/src/authentication/index.js @@ -91,7 +91,49 @@ Authentication.prototype.buildLogoutUrl = function (options) { return urljoin(this.baseOptions.rootUrl, 'v2', 'logout', '?' + qString); }; -Authentication.prototype.ro = function (options, cb) { +Authentication.prototype.login = function (options, cb) { + assert.check(options, { type: 'object', message: 'options parameter is not valid' }, { + clientID: { optional: true, type: 'string', message: 'clientID option is required' }, + username: { optional: true, type: 'string', message: 'username option is required' }, + password: { optional: true, type: 'string', message: 'password option is required' }, + scope: { optional: true, type: 'string', message: 'scope option is required' }, + audience: { optional: true, type: 'string', message: 'audience option is required' } + }); + assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); + + options.grantType = 'password'; + + return this.oauthToken(options, cb); +}; + +Authentication.prototype.oauthToken = function (options, cb) { + var url; + var body; + + assert.check(options, { type: 'object', message: 'options parameter is not valid' }, { + grantType: { optional: true, type: 'string', message: 'grantType option is required' } + }); + assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); + + url = urljoin(this.baseOptions.rootUrl, 'oauth', 'token'); + + body = objectHelper.merge(this.baseOptions, [ + 'clientID', + 'scope', + 'audience' + ]).with(options); + + body = objectHelper.toSnakeCase(body, ['auth0Client']); + + body.grant_type = body.grant_type || 'password'; + + return this.request + .post(url) + .send(body) + .end(responseHandler(cb)); +}; + +Authentication.prototype.loginWithResourceOwner = function (options, cb) { var url; var body; @@ -106,14 +148,17 @@ Authentication.prototype.ro = function (options, cb) { url = urljoin(this.baseOptions.rootUrl, 'oauth', 'ro'); - body = objectHelper.merge(this.baseOptions, ['clientID']) - .with(options); + body = objectHelper.merge(this.baseOptions, [ + 'clientID', + 'scope', + 'audience' + ]).with(options); body = objectHelper.toSnakeCase(body, ['auth0Client']); body.grant_type = body.grant_type || 'password'; - this.request + return this.request .post(url) .send(body) .end(responseHandler(cb)); @@ -127,7 +172,7 @@ Authentication.prototype.userInfo = function (accessToken, cb) { url = urljoin(this.baseOptions.rootUrl, 'userinfo'); - this.request + return this.request .get(url) .set('Authorization', 'Bearer ' + accessToken) .end(responseHandler(cb)); @@ -149,7 +194,7 @@ Authentication.prototype.delegation = function (options, cb) { body = objectHelper.toSnakeCase(body, ['auth0Client']); - this.request + return this.request .post(url) .send(body) .end(responseHandler(cb)); diff --git a/src/authentication/passwordless-authentication.js b/src/authentication/passwordless-authentication.js index 2bff61c1..3bce4219 100644 --- a/src/authentication/passwordless-authentication.js +++ b/src/authentication/passwordless-authentication.js @@ -83,7 +83,7 @@ PasswordlessAuthentication.prototype.start = function (options, cb) { body = objectHelper.toSnakeCase(body, ['auth0Client']); - this.request + return this.request .post(url) .send(body) .end(responseHandler(cb)); @@ -114,7 +114,7 @@ PasswordlessAuthentication.prototype.verify = function (options, cb) { url = urljoin(this.baseOptions.rootUrl, 'passwordless', 'verify'); - this.request + return this.request .post(url) .send(cleanOption) .end(responseHandler(cb)); diff --git a/src/helper/request-builder.js b/src/helper/request-builder.js index 67560b84..61662354 100644 --- a/src/helper/request-builder.js +++ b/src/helper/request-builder.js @@ -3,6 +3,59 @@ var request = require('superagent'); var base64Url = require('./base64_url'); var version = require('../version'); +// ------------------------------------------------ RequestWrapper + +function RequestWrapper(req) { + this.request = req; + this.method = req.method; + this.url = req.url; + this.body = req._data; + this.headers = req._header; +} + +RequestWrapper.prototype.abort = function () { + this.request.abort(); +}; + +RequestWrapper.prototype.getMethod = function () { + return this.method; +}; + +RequestWrapper.prototype.getBody = function () { + return this.body; +}; + +RequestWrapper.prototype.getUrl = function () { + return this.url; +}; + +RequestWrapper.prototype.getHeaders = function () { + return this.headers; +}; + +// ------------------------------------------------ RequestObj + +function RequestObj(req) { + this.request = req; +} + +RequestObj.prototype.set = function (key, value) { + this.request = this.request.set(key, value); + return this; +}; + +RequestObj.prototype.send = function (body) { + this.request = this.request.send(body); + return this; +}; + +RequestObj.prototype.end = function (cb) { + this.request = this.request.end(cb); + return new RequestWrapper(this.request); +}; + +// ------------------------------------------------ RequestBuilder + function RequestBuilder(options) { this._sendTelemetry = options._sendTelemetry === false ? options._sendTelemetry : true; this._telemetryInfo = options._telemetryInfo || null; @@ -28,15 +81,15 @@ RequestBuilder.prototype.getTelemetryData = function () { }; RequestBuilder.prototype.get = function (url) { - return this.setCommonConfiguration(request.get(url)); + return new RequestObj(this.setCommonConfiguration(request.get(url))); }; RequestBuilder.prototype.post = function (url) { - return this.setCommonConfiguration(request.post(url)); + return new RequestObj(this.setCommonConfiguration(request.post(url))); }; RequestBuilder.prototype.patch = function (url) { - return this.setCommonConfiguration(request.patch(url)); + return new RequestObj(this.setCommonConfiguration(request.patch(url))); }; module.exports = RequestBuilder; diff --git a/src/helper/response-handler.js b/src/helper/response-handler.js index 02587b19..5352c696 100644 --- a/src/helper/response-handler.js +++ b/src/helper/response-handler.js @@ -1,7 +1,28 @@ function wrapCallback(cb) { return function (err, data) { + if (err) { - return cb(err); + var data = { + original: err + }; + + if (err.response && err.response.statusCode) { + data.status_code = err.response.statusCode; + } + + if (err.response && err.response.statusText) { + data.status_text = err.response.statusText; + } + + if (err.response && err.response.body) { + err = err.response.body; + } + + data.code = err.error || err.code || err.error_code || null; + data.description = err.error_description || err.description || err.error || null; + data.name = err.name || null; + + return cb(data); } return cb(null, data.body || data.text); diff --git a/src/management/index.js b/src/management/index.js index dce1de24..445538cb 100644 --- a/src/management/index.js +++ b/src/management/index.js @@ -30,7 +30,7 @@ Management.prototype.getUser = function (userId, cb) { url = urljoin(this.baseOptions.rootUrl, 'users', userId); - this.request + return this.request .get(url) .end(responseHandler(cb)); }; @@ -44,24 +44,24 @@ Management.prototype.patchUserMetadata = function (userId, userMetadata, cb) { url = urljoin(this.baseOptions.rootUrl, 'users', userId); - this.request + return this.request .patch(url) .send({ user_metadata: userMetadata }) .end(responseHandler(cb)); }; -Management.prototype.linkUsers = function (userId, secondaryUserToken, cb) { +Management.prototype.linkUser = function (userId, secondaryUserToken, cb) { var url; /* eslint-disable */ assert.check(userId, { type: 'string', message: 'userId parameter is not valid' }); - assert.check(secondaryUserToken, { type: 'string', + assert.check(secondaryUserToken, { type: 'string', message: 'secondaryUserToken parameter is not valid' }); assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); /* eslint-enable */ url = urljoin(this.baseOptions.rootUrl, 'users', userId, 'identities'); - this.request + return this.request .post(url) .send({ link_with: secondaryUserToken }) .end(responseHandler(cb)); diff --git a/src/web-auth/index.js b/src/web-auth/index.js index e6e21fb5..c58579e8 100644 --- a/src/web-auth/index.js +++ b/src/web-auth/index.js @@ -7,6 +7,7 @@ var objectHelper = require('../helper/object'); var Authentication = require('../authentication'); var Redirect = require('./redirect'); var SilentAuthenticationHandler = require('./silent-authentication-handler'); +var windowHelper = require('../helper/window'); function WebAuth(options) { /* eslint-disable */ @@ -30,8 +31,8 @@ function WebAuth(options) { this.baseOptions.tenant = this.baseOptions.domain.split('.')[0]; - this.authentication = new Authentication(this.baseOptions); - this.redirect = new Redirect(this.authentication, this.baseOptions); + this.client = new Authentication(this.baseOptions); + this.redirect = new Redirect(this.client, this.baseOptions); } WebAuth.prototype.parseHash = function (hash) { @@ -92,7 +93,7 @@ WebAuth.prototype.parseHash = function (hash) { WebAuth.prototype.renewAuth = function (options, cb) { var handler; - var usePostMessage = options.usePostMessage || false; + var usePostMessage = !!options.usePostMessage; var params = objectHelper.merge(this.baseOptions, [ 'clientID', @@ -114,20 +115,44 @@ WebAuth.prototype.renewAuth = function (options, cb) { params = objectHelper.toSnakeCase(params, ['auth0Client']); - handler = new SilentAuthenticationHandler(this, this.authentication.buildAuthorizeUrl(params)); + handler = new SilentAuthenticationHandler(this, this.client.buildAuthorizeUrl(params)); handler.login(usePostMessage, cb); }; WebAuth.prototype.changePassword = function (options, cb) { - this.authentication.dbConnection.changePassword(options, cb); + return this.client.dbConnection.changePassword(options, cb); }; WebAuth.prototype.passwordlessStart = function (options, cb) { - this.authentication.passwordless.start(options, cb); + return this.client.passwordless.start(options, cb); +}; + +WebAuth.prototype.signup = function (options, cb) { + return this.client.dbConnection.signup(options, cb); +}; + +WebAuth.prototype.login = function (options) { + windowHelper.redirect(this.client.buildAuthorizeUrl(options)); +}; + +WebAuth.prototype.logout = function (options) { + windowHelper.redirect(this.client.buildLogoutUrl(options)); +}; + +WebAuth.prototype.passwordlessVerify = function (options, cb) { + var _this = this; + return this.client.passwordless.verify(options, function (err) { + if (err) { + return cb(err); + } + windowHelper.redirect(_this.client.passwordless.buildVerifyUrl(options)); + }); }; + // popup.login +// popup.authorize // popup.passwordlessVerify -// popup.signup +// popup.signupAndLogin module.exports = WebAuth; diff --git a/src/web-auth/redirect.js b/src/web-auth/redirect.js index 3785f97b..bbdae436 100644 --- a/src/web-auth/redirect.js +++ b/src/web-auth/redirect.js @@ -1,22 +1,14 @@ var windowHelper = require('../helper/window'); var UsernamePassword = require('./username-password'); -function Redirect(authentication, options) { +function Redirect(client, options) { this.baseOptions = options; - this.authentication = authentication; + this.client = client; } -Redirect.prototype.authorize = function (options) { - windowHelper.redirect(this.authentication.buildAuthorizeUrl(options)); -}; - -Redirect.prototype.logout = function (options) { - windowHelper.redirect(this.authentication.buildLogoutUrl(options)); -}; - Redirect.prototype.login = function (options, cb) { var usernamePassword = new UsernamePassword(this.baseOptions); - usernamePassword.login(options, function (err, data) { + return usernamePassword.login(options, function (err, data) { if (err) { return cb(err); } @@ -24,13 +16,9 @@ Redirect.prototype.login = function (options, cb) { }); }; -Redirect.prototype.signup = function (options, cb) { - this.authentication.dbConnection.signup(options, cb); -}; - Redirect.prototype.signupAndLogin = function (options, cb) { var _this = this; - this.authentication.dbConnection.signup(options, function (err) { + return this.client.dbConnection.signup(options, function (err) { if (err) { return cb(err); } @@ -38,14 +26,4 @@ Redirect.prototype.signupAndLogin = function (options, cb) { }); }; -Redirect.prototype.passwordlessVerify = function (options, cb) { - var _this = this; - this.authentication.passwordless.verify(options, function (err) { - if (err) { - return cb(err); - } - windowHelper.redirect(_this.authentication.passwordless.buildVerifyUrl(options)); - }); -}; - module.exports = Redirect; diff --git a/src/web-auth/username-password.js b/src/web-auth/username-password.js index 71fe5770..946f6695 100644 --- a/src/web-auth/username-password.js +++ b/src/web-auth/username-password.js @@ -31,7 +31,7 @@ UsernamePassword.prototype.login = function (options, cb) { body = objectHelper.toSnakeCase(body, ['auth0Client']); - this.request + return this.request .post(url) .send(body) .end(responseHandler(cb)); diff --git a/test/authentication/ro.test.js b/test/authentication/ro.test.js index 78b015eb..829e5cfa 100644 --- a/test/authentication/ro.test.js +++ b/test/authentication/ro.test.js @@ -53,7 +53,7 @@ describe('auth0.authentication', function () { }); }); - this.auth0.ro({ + this.auth0.loginWithResourceOwner({ username: 'the username', password: 'the password', connection: 'the_connection', @@ -91,14 +91,22 @@ describe('auth0.authentication', function () { }); }); - this.auth0.ro({ + this.auth0.loginWithResourceOwner({ username: 'the username', password: 'the password', connection: 'the_connection', scope: 'openid' }, function (err, data) { expect(data).to.be(undefined); - expect(err).to.eql({ error: 'unauthorized', error_description: 'invalid username' }); + expect(err).to.eql({ + original: { + error: 'unauthorized', + error_description: 'invalid username' + }, + code: 'unauthorized', + description: 'invalid username', + name: null + }); done(); }); }); @@ -131,7 +139,7 @@ describe('auth0.authentication', function () { }); }); - this.auth0.ro({ + this.auth0.loginWithResourceOwner({ clientID: '123', username: 'the username', password: 'the password', diff --git a/test/management/management.test.js b/test/management/management.test.js index b15c5567..1700d341 100644 --- a/test/management/management.test.js +++ b/test/management/management.test.js @@ -203,7 +203,7 @@ describe('auth0.Management', function () { it('should check that userId is valid', function() { expect(() => { - this.auth0.linkUsers(); + this.auth0.linkUser(); }).to.throwException(function (e) { expect(e.message).to.be('userId parameter is not valid'); }); @@ -211,7 +211,7 @@ describe('auth0.Management', function () { it('should check that secondaryUserToken is valid', function() { expect(() => { - this.auth0.linkUsers('...'); + this.auth0.linkUser('...'); }).to.throwException(function (e) { expect(e.message).to.be('secondaryUserToken parameter is not valid'); }); @@ -219,7 +219,7 @@ describe('auth0.Management', function () { it('should check that cb is valid', function() { expect(() => { - this.auth0.linkUsers('...', '...'); + this.auth0.linkUser('...', '...'); }).to.throwException(function (e) { expect(e.message).to.be('cb parameter is not valid'); }); @@ -277,7 +277,7 @@ describe('auth0.Management', function () { }); }); - this.auth0.linkUsers('twitter|191919191919191', 'the_second_token', function(err, user) { + this.auth0.linkUser('twitter|191919191919191', 'the_second_token', function(err, user) { expect(err).to.be(null); expect(user).to.eql([{ 'connection': 'twitter', diff --git a/test/web-auth/redirect.test.js b/test/web-auth/redirect.test.js index c1cab8b1..c06f82d4 100644 --- a/test/web-auth/redirect.test.js +++ b/test/web-auth/redirect.test.js @@ -52,7 +52,7 @@ describe('auth0.WebAuth.redirect', function () { }); }); - this.auth0.redirect.signup({ + this.auth0.signup({ connection: 'the_connection', email: 'me@example.com', password: '123456' @@ -165,8 +165,7 @@ describe('auth0.WebAuth.redirect', function () { cb({ 'name': 'ValidationError', 'code': 'invalid_user_password', - 'description': 'Wrong email or password.', - 'statusCode': 400 + 'description': 'Wrong email or password.' }); } }); @@ -188,10 +187,14 @@ describe('auth0.WebAuth.redirect', function () { scope: 'openid' }, function (err) { expect(err).to.eql({ + 'original': { + 'name': 'ValidationError', + 'code': 'invalid_user_password', + 'description': 'Wrong email or password.' + }, 'name': 'ValidationError', 'code': 'invalid_user_password', - 'description': 'Wrong email or password.', - 'statusCode': 400 + 'description': 'Wrong email or password.' }); done(); }); @@ -233,10 +236,14 @@ describe('auth0.WebAuth.redirect', function () { }, cb: function (cb) { cb({ - 'name': 'ValidationError', - 'code': 'invalid_user_password', - 'description': 'Wrong email or password.', - 'statusCode': 400 + response: { + body: { + 'name': 'ValidationError', + 'code': 'invalid_user_password', + 'description': 'Wrong email or password.' + }, + 'statusCode': 400 + } }); } }); @@ -274,10 +281,20 @@ describe('auth0.WebAuth.redirect', function () { }, function (err, data) { expect(data).to.be(undefined); expect(err).to.eql({ + 'original': { + 'response': { + 'body': { + 'name': 'ValidationError', + 'code': 'invalid_user_password', + 'description': 'Wrong email or password.' + }, + 'statusCode': 400 + } + }, 'name': 'ValidationError', 'code': 'invalid_user_password', 'description': 'Wrong email or password.', - 'statusCode': 400 + 'status_code': 400 }); done(); }); @@ -300,10 +317,13 @@ describe('auth0.WebAuth.redirect', function () { }, cb: function (cb) { cb({ - "name":"BadRequestError", - "code":"user_exists", - "description":"The user already exists.", - "statusCode":400 + response: { + "statusCode":400, + body: { + "code":"user_exists", + "description":"The user already exists." + } + } }); } }); @@ -317,10 +337,19 @@ describe('auth0.WebAuth.redirect', function () { }, function (err, data) { expect(data).to.be(undefined); expect(err).to.eql({ - "name":"BadRequestError", + original: { + response: { + "statusCode":400, + body: { + "code":"user_exists", + "description":"The user already exists." + } + } + }, + "name":null, "code":"user_exists", "description":"The user already exists.", - "statusCode":400 + "status_code":400 }); done(); }); @@ -368,7 +397,7 @@ describe('auth0.WebAuth.redirect', function () { }); }); - this.auth0.redirect.passwordlessVerify({ + this.auth0.passwordlessVerify({ connection: 'the_connection', phoneNumber: '123456', type: 'sms', @@ -418,7 +447,7 @@ describe('auth0.WebAuth.redirect', function () { }); }); - this.auth0.redirect.passwordlessVerify({ + this.auth0.passwordlessVerify({ connection: 'the_connection', phoneNumber: '123456', type: 'sms', @@ -462,15 +491,20 @@ describe('auth0.WebAuth.redirect', function () { }); }); - this.auth0.redirect.passwordlessVerify({ + this.auth0.passwordlessVerify({ connection: 'the_connection', phoneNumber: '123456', type: 'sms', verificationCode: 'abc' }, function (err) { expect(err).to.eql({ - error: 'some_error_code', - error_description: 'Some error description' + original: { + error: 'some_error_code', + error_description: 'Some error description' + }, + name: null, + code: 'some_error_code', + description: 'Some error description' }); done(); }); @@ -490,12 +524,12 @@ describe('auth0.WebAuth.redirect', function () { }); it('should redirect to authorize', function () { - this.auth0.redirect.authorize({connection: 'facebook'}) + this.auth0.login({connection: 'facebook'}) expect(global.window.location).to.be('https://me.auth0.com/authorize?client_id=...&response_type=code&redirect_uri=http%3A%2F%2Fpage.com%2Fcallback&connection=facebook'); }); it('should redirect to logout', function () { - this.auth0.redirect.logout({redirect_to: 'http://example.com/logout'}) + this.auth0.logout({redirect_to: 'http://example.com/logout'}) expect(global.window.location).to.be('https://me.auth0.com/v2/logout?client_id=...&redirect_to=http%3A%2F%2Fexample.com%2Flogout'); }); });