├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── report-a-bug.md ├── stale.yml └── PULL_REQUEST_TEMPLATE.md ├── src ├── version.js ├── helper │ ├── times.js │ ├── storage │ │ ├── dummy.js │ │ ├── cookie.js │ │ └── handler.js │ ├── warn.js │ ├── error.js │ ├── url.js │ ├── ssodata.js │ ├── window.js │ ├── storage.js │ ├── random.js │ ├── plugins.js │ ├── object-assign.js │ ├── base64_url.js │ ├── assert.js │ ├── parameters-whitelist.js │ ├── response-handler.js │ ├── iframe-handler.js │ ├── popup-handler.js │ ├── request-builder.js │ └── object.js ├── index.js ├── web-auth │ ├── username-password.js │ ├── transaction-manager.js │ ├── redirect.js │ ├── web-message-handler.js │ ├── silent-authentication-handler.js │ ├── captcha.js │ ├── hosted-pages.js │ └── cross-origin-authentication.js ├── authentication │ ├── db-connection.js │ └── passwordless-authentication.js └── management │ └── index.js ├── scripts ├── .eslintrc ├── utils │ └── attribute.js ├── jsdocs.js ├── ci.sh └── release.sh ├── .prettierrc ├── .eslintignore ├── docs ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff ├── scripts │ ├── linenumber.js │ └── prettify │ │ └── lang-css.js └── styles │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── .babelrc ├── bower.json ├── .nycrc ├── example ├── callback.html ├── callback_popup.html ├── callback-cross-auth.html ├── test.html └── login.html ├── codecov.yml ├── .vscode └── settings.json ├── .gitignore ├── plugins └── cordova │ ├── plugin-handler.js │ ├── index.js │ └── popup-handler.js ├── test ├── imports-test.test.js ├── mock │ ├── mock-auth0-plugin.js │ └── request-mock.js ├── helper │ ├── warn.test.js │ ├── base64_url.test.js │ ├── assert.test.js │ ├── window.test.js │ ├── ssodata.test.js │ ├── random.test.js │ ├── storage.cookie.test.js │ ├── storage.test.js │ ├── popup-handler.test.js │ ├── storage-handler.test.js │ └── response-handler.test.js ├── web-auth │ ├── extensibility.test.js │ └── captcha.test.js └── authentication │ ├── ro.test.js │ └── db-connection.test.js ├── .jsdoc.json ├── .eslintrc.json ├── LICENSE ├── .circleci └── config.yml ├── SECURITY-NOTICE.md ├── integration ├── selenium.js └── redirect_authorize.test.js ├── rollup.config.js └── package.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0/dx-sdks-engineer 2 | -------------------------------------------------------------------------------- /src/version.js: -------------------------------------------------------------------------------- 1 | module.exports = { raw: '9.15.0' }; 2 | -------------------------------------------------------------------------------- /scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | } 5 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 80 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/* 2 | **/node_modules/* 3 | /build/* 4 | /dist/* 5 | /test/* 6 | -------------------------------------------------------------------------------- /src/helper/times.js: -------------------------------------------------------------------------------- 1 | export var MINUTES_15 = 1 / 96; 2 | export var MINUTES_30 = 1 / 48; 3 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zog/auth0.js/master/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["babel-plugin-istanbul"], 5 | "presets": ["@babel/preset-env"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "experimental bower support", 3 | "name": "auth0.js", 4 | "main": "dist/auth0.js", 5 | "dependencies": {}, 6 | "ignore": ["test"] 7 | } 8 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "reporter" : ["text-summary", "lcov"], 3 | "include" : ["src/**/*.js", "plugins/**/*.js"], 4 | "require" : ["@babel/register"], 5 | "sourceMap" : false, 6 | "instrument" : false, 7 | "all" : true 8 | } -------------------------------------------------------------------------------- /example/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /scripts/utils/attribute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var attributeName = process.argv[2] || 'name'; 4 | var path = process.argv[3] || '../../package.json'; 5 | var attributes = require(path); 6 | var value = attributes[attributeName]; 7 | if (value !== undefined) { 8 | console.log(value); 9 | } 10 | -------------------------------------------------------------------------------- /src/helper/storage/dummy.js: -------------------------------------------------------------------------------- 1 | function DummyStorage() {} 2 | 3 | DummyStorage.prototype.getItem = function() { 4 | return null; 5 | }; 6 | 7 | DummyStorage.prototype.removeItem = function() {}; 8 | 9 | DummyStorage.prototype.setItem = function() {}; 10 | 11 | export default DummyStorage; 12 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...100" 5 | status: 6 | patch: 7 | default: 8 | if_no_uploads: error 9 | changes: true 10 | project: 11 | default: 12 | target: auto 13 | if_no_uploads: error 14 | comment: false -------------------------------------------------------------------------------- /src/helper/warn.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | function Warn(options) { 4 | this.disableWarnings = options.disableWarnings; 5 | } 6 | 7 | Warn.prototype.warning = function(message) { 8 | if (this.disableWarnings) { 9 | return; 10 | } 11 | 12 | console.warn(message); 13 | }; 14 | 15 | export default Warn; 16 | -------------------------------------------------------------------------------- /src/helper/error.js: -------------------------------------------------------------------------------- 1 | function buildResponse(error, description) { 2 | return { 3 | error: error, 4 | errorDescription: description 5 | }; 6 | } 7 | 8 | function invalidToken(description) { 9 | return buildResponse('invalid_token', description); 10 | } 11 | 12 | export default { 13 | buildResponse: buildResponse, 14 | invalidToken: invalidToken 15 | }; 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.formatOnSave": true, 4 | "prettier.printWidth": 100, 5 | "prettier.singleQuote": true, 6 | "search.exclude": { 7 | "**/node_modules": true, 8 | "**/bower_components": true, 9 | "**/docs": true, 10 | "**/build": true, 11 | "**/dist": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Authentication from './authentication'; 2 | import Management from './management'; 3 | import WebAuth from './web-auth'; 4 | import version from './version'; 5 | 6 | export { Authentication, Management, WebAuth, version }; 7 | 8 | export default { 9 | Authentication: Authentication, 10 | Management: Management, 11 | WebAuth: WebAuth, 12 | version: version 13 | }; 14 | -------------------------------------------------------------------------------- /src/helper/url.js: -------------------------------------------------------------------------------- 1 | // given a URL, extract the origin. Taken from: https://github.com/firebase/firebase-simple-login/blob/d2cb95b9f812d8488bdbfba51c3a7c153ba1a074/js/src/simple-login/transports/WinChan.js#L25-L30 2 | function extractOrigin(url) { 3 | if (!/^https?:\/\//.test(url)) url = window.location.href; 4 | var m = /^(https?:\/\/[-_a-zA-Z.0-9:]+)/.exec(url); 5 | if (m) return m[1]; 6 | return url; 7 | } 8 | 9 | export default { 10 | extractOrigin: extractOrigin 11 | }; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Auth0 Community 4 | url: https://community.auth0.com/c/sdks/auth0js/59 5 | about: Discuss this SDK in the Auth0 Community forums 6 | - name: Library Documentation 7 | url: https://auth0.com/docs/libraries/auth0js/v9 8 | about: Read the library docs on Auth0.com 9 | - name: Auth0.js API docs 10 | url: https://auth0.github.io/auth0.js/index.html 11 | about: Read the SDK API documentation 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by http://gitignore.io 2 | browserstack.json 3 | 4 | ### Node ### 5 | lib-cov 6 | *.seed 7 | *.log 8 | *.csv 9 | *.dat 10 | *.out 11 | *.pid 12 | *.gz 13 | 14 | pids 15 | logs 16 | results 17 | 18 | npm-debug.log 19 | node_modules 20 | bower_components 21 | 22 | release 23 | 24 | .DS_Store 25 | 26 | build 27 | dist 28 | 29 | .gitignore 30 | coverage 31 | test-results.xml 32 | .nyc_output 33 | 34 | .idea/ 35 | 36 | # jsdocs 37 | out/ 38 | 39 | # Yarn lock file 40 | yarn.lock 41 | -------------------------------------------------------------------------------- /example/callback_popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /scripts/jsdocs.js: -------------------------------------------------------------------------------- 1 | if (process.platform === 'win32') { 2 | console.error('Must be run on a Unix OS'); 3 | process.exit(1); 4 | } 5 | 6 | var library = require('../package.json'); 7 | var execSync = require('child_process').execSync; 8 | var fs = require('fs'); 9 | 10 | execSync('npm run jsdocs', { stdio: 'inherit' }); 11 | if (fs.existsSync('docs')) { 12 | execSync('rm -r docs', { stdio: 'inherit' }); 13 | } 14 | execSync(`mv out/auth0-js/${library.version}/ docs`, { stdio: 'inherit' }); 15 | execSync('git add docs'); 16 | -------------------------------------------------------------------------------- /example/callback-cross-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /plugins/cordova/plugin-handler.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | import PopupHandler from './popup-handler'; 3 | 4 | function PluginHandler(webAuth) { 5 | this.webAuth = webAuth; 6 | } 7 | 8 | PluginHandler.prototype.processParams = function(params) { 9 | params.redirectUri = urljoin('https://' + params.domain, 'mobile'); 10 | delete params.owp; 11 | return params; 12 | }; 13 | 14 | PluginHandler.prototype.getPopupHandler = function() { 15 | return new PopupHandler(this.webAuth); 16 | }; 17 | 18 | export default PluginHandler; 19 | -------------------------------------------------------------------------------- /src/helper/ssodata.js: -------------------------------------------------------------------------------- 1 | import Storage from './storage'; 2 | 3 | function SSODataStorage(options) { 4 | this.storage = new Storage(options); 5 | } 6 | 7 | SSODataStorage.prototype.set = function(connection, sub) { 8 | var ssodata = { 9 | lastUsedConnection: connection, 10 | lastUsedSub: sub 11 | }; 12 | this.storage.setItem('auth0.ssodata', JSON.stringify(ssodata)); 13 | }; 14 | SSODataStorage.prototype.get = function() { 15 | var ssodata = this.storage.getItem('auth0.ssodata'); 16 | if (!ssodata) { 17 | return; 18 | } 19 | return JSON.parse(ssodata); 20 | }; 21 | 22 | export default SSODataStorage; 23 | -------------------------------------------------------------------------------- /src/helper/window.js: -------------------------------------------------------------------------------- 1 | import objectHelper from './object'; 2 | 3 | function redirect(url) { 4 | getWindow().location = url; 5 | } 6 | 7 | function getDocument() { 8 | return getWindow().document; 9 | } 10 | 11 | function getWindow() { 12 | return window; 13 | } 14 | 15 | function getOrigin() { 16 | var location = getWindow().location; 17 | var origin = location.origin; 18 | 19 | if (!origin) { 20 | origin = objectHelper.getOriginFromUrl(location.href); 21 | } 22 | 23 | return origin; 24 | } 25 | 26 | export default { 27 | redirect: redirect, 28 | getDocument: getDocument, 29 | getWindow: getWindow, 30 | getOrigin: getOrigin 31 | }; 32 | -------------------------------------------------------------------------------- /src/helper/storage.js: -------------------------------------------------------------------------------- 1 | import StorageHandler from './storage/handler'; 2 | 3 | function Storage(options) { 4 | this.handler = new StorageHandler(options); 5 | } 6 | 7 | Storage.prototype.getItem = function(key) { 8 | var value = this.handler.getItem(key); 9 | try { 10 | return JSON.parse(value); 11 | } catch (_) { 12 | return value; 13 | } 14 | }; 15 | Storage.prototype.removeItem = function(key) { 16 | return this.handler.removeItem(key); 17 | }; 18 | Storage.prototype.setItem = function(key, value, options) { 19 | var json = JSON.stringify(value); 20 | return this.handler.setItem(key, json, options); 21 | }; 22 | 23 | export default Storage; 24 | -------------------------------------------------------------------------------- /test/imports-test.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | 3 | import * as auth0 from '../src/index'; 4 | import auth0Default from '../src/index'; 5 | 6 | describe('Exports the correct objects', () => { 7 | it('should export raw objects', () => { 8 | expect(Object.keys(auth0)).to.be.eql([ 9 | 'Authentication', 10 | 'Management', 11 | 'WebAuth', 12 | 'version', 13 | 'default' 14 | ]); 15 | }); 16 | it('should export default object', () => { 17 | expect(Object.keys(auth0Default)).to.be.eql([ 18 | 'Authentication', 19 | 'Management', 20 | 'WebAuth', 21 | 'version' 22 | ]); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/mock/mock-auth0-plugin.js: -------------------------------------------------------------------------------- 1 | import version from '../../src/version'; 2 | 3 | function MockPlugin(configuration) { 4 | configuration = configuration || {}; 5 | 6 | this.version = configuration.version || version.raw; 7 | this.handler = configuration.handler || null; 8 | this.extensibilityPoints = configuration.extensibilityPoints || []; 9 | } 10 | 11 | MockPlugin.prototype.supports = function(extensibilityPoint) { 12 | return this.extensibilityPoints.indexOf(extensibilityPoint) > -1; 13 | }; 14 | 15 | MockPlugin.prototype.setWebAuth = function(webAuth) { 16 | this.webAuth = webAuth; 17 | }; 18 | 19 | MockPlugin.prototype.init = function() { 20 | return this.handler; 21 | }; 22 | 23 | module.exports = MockPlugin; 24 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "source": { 7 | "include": ["src", "package.json", "README.md"], 8 | "includePattern": ".js$", 9 | "excludePattern": "(node_modules/|docs)" 10 | }, 11 | "plugins": [ 12 | "./node_modules/jsdoc/plugins/markdown" 13 | ], 14 | "templates": { 15 | "cleverLinks": false, 16 | "monospaceLinks": true, 17 | "useLongnameInNav": false 18 | }, 19 | "opts": { 20 | "destination": "./out/", 21 | "encoding": "utf8", 22 | "private": false, 23 | "recurse": true, 24 | "template": "./node_modules/minami" 25 | } 26 | } -------------------------------------------------------------------------------- /src/helper/random.js: -------------------------------------------------------------------------------- 1 | import windowHelper from './window'; 2 | 3 | function randomString(length) { 4 | // eslint-disable-next-line 5 | var bytes = new Uint8Array(length); 6 | var result = []; 7 | var charset = 8 | '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~'; 9 | 10 | var cryptoObj = 11 | windowHelper.getWindow().crypto || windowHelper.getWindow().msCrypto; 12 | if (!cryptoObj) { 13 | return null; 14 | } 15 | 16 | var random = cryptoObj.getRandomValues(bytes); 17 | 18 | for (var a = 0; a < random.length; a++) { 19 | result.push(charset[random[a] % charset.length]); 20 | } 21 | 22 | return result.join(''); 23 | } 24 | 25 | export default { 26 | randomString: randomString 27 | }; 28 | -------------------------------------------------------------------------------- /src/helper/storage/cookie.js: -------------------------------------------------------------------------------- 1 | import Cookie from 'js-cookie'; 2 | import objectHelper from '../object'; 3 | import windowHandler from '../window'; 4 | function CookieStorage() {} 5 | 6 | CookieStorage.prototype.getItem = function(key) { 7 | return Cookie.get(key); 8 | }; 9 | 10 | CookieStorage.prototype.removeItem = function(key) { 11 | Cookie.remove(key); 12 | }; 13 | 14 | CookieStorage.prototype.setItem = function(key, value, options) { 15 | var params = objectHelper.extend( 16 | { 17 | expires: 1 // 1 day 18 | }, 19 | options 20 | ); 21 | 22 | if (windowHandler.getWindow().location.protocol === 'https:') { 23 | params.secure = true; 24 | } 25 | 26 | Cookie.set(key, value, params); 27 | }; 28 | 29 | export default CookieStorage; 30 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /test/helper/warn.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | 3 | import sinon from 'sinon'; 4 | 5 | import Warn from '../../src/helper/warn'; 6 | 7 | describe('helpers warn', function() { 8 | afterEach(function() { 9 | console.warn.restore(); 10 | }); 11 | 12 | it('should show a warning in the console', function() { 13 | sinon.stub(console, 'warn').callsFake(function(message) { 14 | expect(message).to.be('the message'); 15 | }); 16 | 17 | var warn = new Warn({}); 18 | warn.warning('the message'); 19 | }); 20 | 21 | it('should not show a warning in the console', function() { 22 | sinon.stub(console, 'warn').callsFake(function(message) { 23 | throw Error('warn was called'); 24 | }); 25 | 26 | var warn = new Warn({ disableWarnings: true }); 27 | warn.warning('the message'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /plugins/cordova/index.js: -------------------------------------------------------------------------------- 1 | import version from '../../src/version'; 2 | import windowHandler from '../../src/helper/window'; 3 | import PluginHandler from './plugin-handler'; 4 | 5 | function CordovaPlugin() { 6 | this.webAuth = null; 7 | this.version = version.raw; 8 | this.extensibilityPoints = ['popup.authorize', 'popup.getPopupHandler']; 9 | } 10 | 11 | CordovaPlugin.prototype.setWebAuth = function(webAuth) { 12 | this.webAuth = webAuth; 13 | }; 14 | 15 | CordovaPlugin.prototype.supports = function(extensibilityPoint) { 16 | var _window = windowHandler.getWindow(); 17 | return ( 18 | (!!_window.cordova || !!_window.electron) && 19 | this.extensibilityPoints.indexOf(extensibilityPoint) > -1 20 | ); 21 | }; 22 | 23 | CordovaPlugin.prototype.init = function() { 24 | return new PluginHandler(this.webAuth); 25 | }; 26 | 27 | export default CordovaPlugin; 28 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["auth0-base/prettier"], 3 | "plugins": ["compat"], 4 | "env": { 5 | "browser": true 6 | }, 7 | "rules": { 8 | "func-names": "off", 9 | "no-underscore-dangle": "off", 10 | "no-prototype-builtins": "off", 11 | "no-param-reassign": "off", 12 | "consistent-return": "off", 13 | "vars-on-top": "off", 14 | "compat/compat": "error", 15 | "no-var": 0, 16 | "object-shorthand": 0, 17 | "prefer-template": 0, 18 | "prefer-arrow-callback": 0, 19 | "no-plusplus": 0, 20 | "import/newline-after-import": 0, 21 | "no-useless-return": 0, 22 | "import/no-mutable-exports": 0, 23 | "import/first": 0, 24 | "prefer-spread": 0, 25 | "prefer-rest-params": 0, 26 | "lines-around-directive": 0, 27 | "valid-typeof": 0 28 | }, 29 | "settings": { 30 | "polyfills": ["Object.assign"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/helper/base64_url.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import base64url from '../../src/helper/base64_url'; 3 | 4 | var tests = [ 5 | { decoded: 'test', encoded: 'dGVzdA==' }, 6 | { decoded: JSON.stringify({ foo: 'bar' }), encoded: 'eyJmb28iOiJiYXIifQ==' }, 7 | { decoded: 'a', encoded: 'YQ==' } 8 | ]; 9 | 10 | describe('helpers base64_url', function() { 11 | describe('encode', function() { 12 | tests.forEach(function(s) { 13 | it('should encode ' + s.decoded + ' to ' + s.encoded, function() { 14 | expect(base64url.encode(s.decoded)).to.be(s.encoded); 15 | }); 16 | }); 17 | }); 18 | describe('decode', function() { 19 | tests.forEach(function(s) { 20 | it('should decode ' + s.decoded, function() { 21 | expect(base64url.decode(s.encoded)).to.be(s.decoded); 22 | }); 23 | }); 24 | it('should handle special case when `str.length % 4 !== 0`', () => { 25 | expect(base64url.decode('YW=')).to.be('a'); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/mock/request-mock.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | 3 | var RequestMock = function(options, method, url) { 4 | this.options = options; 5 | this.method = method; 6 | this.url = url; 7 | this._header = {}; 8 | }; 9 | 10 | RequestMock.prototype.send = function(body) { 11 | this._data = body; 12 | expect(body).to.eql(this.options.body); 13 | return this; 14 | }; 15 | 16 | RequestMock.prototype.set = function(key, value) { 17 | expect(this.options.headers).to.have.key(key); 18 | expect(value).to.eql(this.options.headers[key]); 19 | this._header[key] = value; 20 | delete this.options.headers[key]; 21 | return this; 22 | }; 23 | 24 | RequestMock.prototype.abort = function() {}; 25 | 26 | RequestMock.prototype.withCredentials = function() { 27 | return this; 28 | }; 29 | 30 | RequestMock.prototype.end = function(cb) { 31 | expect(this.options.headers).to.eql({}); 32 | this.options.cb(cb); 33 | return this; 34 | }; 35 | 36 | RequestMock.prototype.retry = function(times) { 37 | this.willRetry = times; 38 | return this; 39 | }; 40 | 41 | module.exports = RequestMock; 42 | -------------------------------------------------------------------------------- /src/helper/plugins.js: -------------------------------------------------------------------------------- 1 | import version from '../version'; 2 | 3 | function PluginHandler(webAuth, plugins) { 4 | this.plugins = plugins; 5 | 6 | for (var a = 0; a < this.plugins.length; a++) { 7 | if (this.plugins[a].version !== version.raw) { 8 | var pluginName = ''; 9 | 10 | if (this.plugins[a].constructor && this.plugins[a].constructor.name) { 11 | pluginName = this.plugins[a].constructor.name; 12 | } 13 | 14 | throw new Error( 15 | 'Plugin ' + 16 | pluginName + 17 | ' version (' + 18 | this.plugins[a].version + 19 | ') ' + 20 | 'is not compatible with the SDK version (' + 21 | version.raw + 22 | ')' 23 | ); 24 | } 25 | 26 | this.plugins[a].setWebAuth(webAuth); 27 | } 28 | } 29 | 30 | PluginHandler.prototype.get = function(extensibilityPoint) { 31 | for (var a = 0; a < this.plugins.length; a++) { 32 | if (this.plugins[a].supports(extensibilityPoint)) { 33 | return this.plugins[a].init(); 34 | } 35 | } 36 | 37 | return null; 38 | }; 39 | 40 | export default PluginHandler; 41 | -------------------------------------------------------------------------------- /src/helper/object-assign.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-continue */ 2 | 3 | function get() { 4 | if (!Object.assign) { 5 | return objectAssignPolyfill; 6 | } 7 | 8 | return Object.assign; 9 | } 10 | 11 | function objectAssignPolyfill(target) { 12 | if (target === undefined || target === null) { 13 | throw new TypeError('Cannot convert first argument to object'); 14 | } 15 | 16 | var to = Object(target); 17 | for (var i = 1; i < arguments.length; i++) { 18 | var nextSource = arguments[i]; 19 | if (nextSource === undefined || nextSource === null) { 20 | continue; 21 | } 22 | 23 | var keysArray = Object.keys(Object(nextSource)); 24 | for ( 25 | var nextIndex = 0, len = keysArray.length; 26 | nextIndex < len; 27 | nextIndex++ 28 | ) { 29 | var nextKey = keysArray[nextIndex]; 30 | var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); 31 | if (desc !== undefined && desc.enumerable) { 32 | to[nextKey] = nextSource[nextKey]; 33 | } 34 | } 35 | } 36 | return to; 37 | } 38 | 39 | export default { 40 | get: get, 41 | objectAssignPolyfill: objectAssignPolyfill 42 | }; 43 | -------------------------------------------------------------------------------- /src/helper/base64_url.js: -------------------------------------------------------------------------------- 1 | import base64 from 'base64-js'; 2 | 3 | function padding(str) { 4 | var mod = str.length % 4; 5 | var pad = 4 - mod; 6 | 7 | if (mod === 0) { 8 | return str; 9 | } 10 | 11 | return str + new Array(1 + pad).join('='); 12 | } 13 | 14 | function stringToByteArray(str) { 15 | var arr = new Array(str.length); 16 | for (var a = 0; a < str.length; a++) { 17 | arr[a] = str.charCodeAt(a); 18 | } 19 | return arr; 20 | } 21 | 22 | function byteArrayToString(array) { 23 | var result = ''; 24 | for (var i = 0; i < array.length; i++) { 25 | result += String.fromCharCode(array[i]); 26 | } 27 | return result; 28 | } 29 | 30 | function encode(str) { 31 | return base64 32 | .fromByteArray(stringToByteArray(str)) 33 | .replace(/\+/g, '-') // Convert '+' to '-' 34 | .replace(/\//g, '_'); // Convert '/' to '_' 35 | } 36 | 37 | function decode(str) { 38 | str = padding(str) 39 | .replace(/-/g, '+') // Convert '-' to '+' 40 | .replace(/_/g, '/'); // Convert '_' to '/' 41 | 42 | return byteArrayToString(base64.toByteArray(str)); 43 | } 44 | 45 | export default { 46 | encode: encode, 47 | decode: decode 48 | }; 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-current Auth0, Inc. (http://auth0.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 30 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | daysUntilClose: 7 8 | 9 | # Only issues or pull requests with all of these labels are considered by StaleBot. Defaults to `[]` (disabled) 10 | onlyLabels: 11 | - 'needs more info' 12 | 13 | # Ignore issues in projects 14 | exemptProjects: true 15 | 16 | # Ignore issues and PRs in milestones 17 | exemptMilestones: true 18 | 19 | # Set to true to ignore issues with an assignee 20 | exemptAssignees: true 21 | 22 | # Label to use when marking as stale 23 | staleLabel: closed:stale 24 | 25 | # Comment to post when marking as stale. Set to `false` to disable 26 | markComment: > 27 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ 28 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:12-browsers 6 | environment: 7 | LANG: en_US.UTF-8 8 | steps: 9 | - checkout 10 | - restore-cache: 11 | name: Restore Package Cache 12 | keys: 13 | - npm-packages-{{ checksum "package-lock.json" }} 14 | - run: 15 | name: Install Dependencies 16 | command: npm install 17 | - save-cache: 18 | name: Save Package Cache 19 | key: npm-packages-{{ checksum "package-lock.json" }} 20 | paths: 21 | - ~/.cache/npm 22 | - run: 23 | name: Build 24 | command: npm run build 25 | - run: 26 | name: Check for JS incompatibility 27 | command: npm run test:es-check:es5 && npm run test:es-check:es2015:module 28 | - run: 29 | name: Lint 30 | command: npm run lint 31 | - run: 32 | name: Run Tests 33 | command: npm run ci:test 34 | environment: 35 | MOCHA_FILE: junit/test-results.xml 36 | when: always 37 | - run: 38 | name: Generate Coverage 39 | command: npm run ci:coverage 40 | - store_artifacts: 41 | path: dist 42 | - store_artifacts: 43 | path: junit 44 | - store_test_results: 45 | path: junit 46 | -------------------------------------------------------------------------------- /test/helper/assert.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import assert from '../../src/helper/assert'; 5 | 6 | describe('helpers assert', function() { 7 | describe('isArray native', function() { 8 | it('should say it is a valid isArray', function() { 9 | expect(assert.isArray([1, 2, 3])).to.be.ok(); 10 | expect(assert.isArray([])).to.be.ok(); 11 | }); 12 | 13 | it('should say it is NOT a valid isArray', function() { 14 | expect(assert.isArray({})).to.not.be.ok(); 15 | expect(assert.isArray('hello')).to.not.be.ok(); 16 | expect(assert.isArray(123)).to.not.be.ok(); 17 | }); 18 | }); 19 | 20 | describe('isArray polyfill', function() { 21 | beforeEach(function() { 22 | sinon.stub(assert, 'supportsIsArray').callsFake(function(message) { 23 | return false; 24 | }); 25 | }); 26 | 27 | afterEach(function() { 28 | assert.supportsIsArray.restore(); 29 | }); 30 | it('should show an error in the console', function() { 31 | expect(assert.isArray([1, 2, 3])).to.be.ok(); 32 | expect(assert.isArray([])).to.be.ok(); 33 | }); 34 | it('should say it is NOT a valid isArray', function() { 35 | expect(assert.isArray({})).to.not.be.ok(); 36 | expect(assert.isArray('hello')).to.not.be.ok(); 37 | expect(assert.isArray(123)).to.not.be.ok(); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/helper/window.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | 3 | import windowHelper from '../../src/helper/window'; 4 | 5 | describe('helpers window', function() { 6 | beforeEach(function() { 7 | global.window = { location: '' }; 8 | global.window.document = { body: {} }; 9 | }); 10 | 11 | afterEach(function() { 12 | delete global.window; 13 | }); 14 | 15 | it('should redirect', function() { 16 | windowHelper.redirect('http://example.com'); 17 | expect(global.window.location).to.be('http://example.com'); 18 | }); 19 | 20 | it('should return the window.document object', function() { 21 | var _document = windowHelper.getDocument(); 22 | expect(_document).to.eql({ body: {} }); 23 | }); 24 | 25 | it('should return the window object', function() { 26 | var _window = windowHelper.getWindow(); 27 | expect(_window).to.eql({ document: { body: {} }, location: '' }); 28 | }); 29 | 30 | describe('getOrigin', function() { 31 | it('should use window.location.origin when available', function() { 32 | global.window = { location: { origin: 'origin' } }; 33 | expect(windowHelper.getOrigin()).to.be('origin'); 34 | }); 35 | 36 | it('should build current origin when location.origin is not available', function() { 37 | global.window = { location: { href: 'http://hostname:30/foobar' } }; 38 | expect(windowHelper.getOrigin()).to.be('http://hostname:30'); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | Please describe both what is changing and why this is important. Include: 4 | 5 | - Endpoints added, deleted, deprecated, or changed 6 | - Classes and methods added, deleted, deprecated, or changed 7 | - Screenshots of new or changed UI, if applicable 8 | - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released) 9 | - Any alternative designs or approaches considered 10 | 11 | ### References 12 | 13 | Please include relevant links supporting this change such as a: 14 | 15 | - support ticket 16 | - community post 17 | - StackOverflow post 18 | - support forum thread 19 | 20 | ### Testing 21 | 22 | Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors. 23 | 24 | - [ ] This change adds unit test coverage 25 | - [ ] This change adds integration test coverage 26 | 27 | ### Checklist 28 | 29 | - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 30 | - [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 31 | - [ ] All tests and linters described in the [Develop section](https://github.com/auth0/auth0.js#develop) run without errors 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or a feature for this project 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 11 | 12 | **Thank you in advance for helping us to improve this library!** Your attention to detail here is greatly appreciated and will help us respond as quickly as possible. For general support or usage questions, use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com/). Finally, to avoid duplicates, please search existing Issues before submitting one here. 13 | 14 | By submitting an Issue to this repository, you agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 15 | 16 | ### Describe the problem you'd like to have solved 17 | 18 | > A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 19 | 20 | ### Describe the ideal solution 21 | 22 | > A clear and concise description of what you want to happen. 23 | 24 | ## Alternatives and current work-arounds 25 | 26 | > A clear and concise description of any alternatives you've considered or any work-arounds that are currently in place. 27 | 28 | ### Additional context 29 | 30 | > Add any other context or screenshots about the feature request here. 31 | -------------------------------------------------------------------------------- /src/web-auth/username-password.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | 3 | import objectHelper from '../helper/object'; 4 | import RequestBuilder from '../helper/request-builder'; 5 | import responseHandler from '../helper/response-handler'; 6 | import windowHelper from '../helper/window'; 7 | import TransactionManager from './transaction-manager'; 8 | 9 | function UsernamePassword(options) { 10 | this.baseOptions = options; 11 | this.request = new RequestBuilder(options); 12 | this.transactionManager = new TransactionManager(this.baseOptions); 13 | } 14 | 15 | UsernamePassword.prototype.login = function(options, cb) { 16 | var url; 17 | var body; 18 | 19 | url = urljoin(this.baseOptions.rootUrl, 'usernamepassword', 'login'); 20 | 21 | options.username = options.username || options.email; // eslint-disable-line 22 | 23 | options = objectHelper.blacklist(options, ['email', 'onRedirecting']); // eslint-disable-line 24 | 25 | body = objectHelper 26 | .merge(this.baseOptions, [ 27 | 'clientID', 28 | 'redirectUri', 29 | 'tenant', 30 | 'responseType', 31 | 'responseMode', 32 | 'scope', 33 | 'audience' 34 | ]) 35 | .with(options); 36 | body = this.transactionManager.process(body); 37 | 38 | body = objectHelper.toSnakeCase(body, ['auth0Client']); 39 | 40 | return this.request 41 | .post(url) 42 | .send(body) 43 | .end(responseHandler(cb)); 44 | }; 45 | 46 | UsernamePassword.prototype.callback = function(formHtml) { 47 | var div; 48 | var form; 49 | var _document = windowHelper.getDocument(); 50 | 51 | div = _document.createElement('div'); 52 | div.innerHTML = formHtml; 53 | form = _document.body.appendChild(div).children[0]; 54 | 55 | form.submit(); 56 | }; 57 | 58 | export default UsernamePassword; 59 | -------------------------------------------------------------------------------- /test/helper/ssodata.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import Storage from '../../src/helper/storage'; 5 | import SSODataStorage from '../../src/helper/ssodata'; 6 | 7 | describe('helpers', function() { 8 | var ssodata; 9 | beforeEach(function() { 10 | ssodata = new SSODataStorage({}); 11 | }); 12 | describe('ssodata', function() { 13 | afterEach(function() { 14 | if (Storage.prototype.setItem.restore) { 15 | Storage.prototype.setItem.restore(); 16 | } 17 | if (Storage.prototype.getItem.restore) { 18 | Storage.prototype.getItem.restore(); 19 | } 20 | }); 21 | describe('get', function() { 22 | it('when there is data', function() { 23 | var expectedObject = { foo: 'bar' }; 24 | sinon.stub(Storage.prototype, 'getItem').callsFake(function() { 25 | return JSON.stringify(expectedObject); 26 | }); 27 | var data = ssodata.get(); 28 | expect(data).to.be.eql(expectedObject); 29 | }); 30 | it('when there is no data', function() { 31 | sinon.stub(Storage.prototype, 'getItem').callsFake(function() { 32 | return undefined; 33 | }); 34 | var data = ssodata.get(); 35 | expect(data).to.be(undefined); 36 | }); 37 | }); 38 | describe('set', function() { 39 | it('sets ssodata', function(done) { 40 | sinon 41 | .stub(Storage.prototype, 'setItem') 42 | .callsFake(function(key, value) { 43 | expect(key).to.be('auth0.ssodata'); 44 | expect(JSON.parse(value)).to.be.eql({ 45 | lastUsedConnection: 'connection', 46 | lastUsedSub: 'sub' 47 | }); 48 | done(); 49 | }); 50 | ssodata.set('connection', 'sub'); 51 | }); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/helper/assert.js: -------------------------------------------------------------------------------- 1 | var toString = Object.prototype.toString; 2 | 3 | function attribute(o, attr, type, text) { 4 | type = type === 'array' ? 'object' : type; 5 | if (o && typeof o[attr] !== type) { 6 | throw new Error(text); 7 | } 8 | } 9 | 10 | function variable(o, type, text) { 11 | if (typeof o !== type) { 12 | throw new Error(text); 13 | } 14 | } 15 | 16 | function value(o, values, text) { 17 | if (values.indexOf(o) === -1) { 18 | throw new Error(text); 19 | } 20 | } 21 | 22 | function check(o, config, attributes) { 23 | if (!config.optional || o) { 24 | variable(o, config.type, config.message); 25 | } 26 | if (config.type === 'object' && attributes) { 27 | var keys = Object.keys(attributes); 28 | 29 | for (var index = 0; index < keys.length; index++) { 30 | var a = keys[index]; 31 | if (!attributes[a].optional || o[a]) { 32 | if (!attributes[a].condition || attributes[a].condition(o)) { 33 | attribute(o, a, attributes[a].type, attributes[a].message); 34 | if (attributes[a].values) { 35 | value(o[a], attributes[a].values, attributes[a].value_message); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * Wrap `Array.isArray` Polyfill for IE9 45 | * source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray 46 | * 47 | * @param {Array} array 48 | * @private 49 | */ 50 | function isArray(array) { 51 | if (this.supportsIsArray()) { 52 | return Array.isArray(array); 53 | } 54 | 55 | return toString.call(array) === '[object Array]'; 56 | } 57 | 58 | function supportsIsArray() { 59 | return Array.isArray != null; 60 | } 61 | 62 | export default { 63 | check: check, 64 | attribute: attribute, 65 | variable: variable, 66 | value: value, 67 | isArray: isArray, 68 | supportsIsArray: supportsIsArray 69 | }; 70 | -------------------------------------------------------------------------------- /test/helper/random.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import windowHelper from '../../src/helper/window'; 5 | import random from '../../src/helper/random'; 6 | 7 | describe('helpers random', function() { 8 | describe('randomString with crypto', function() { 9 | before(function() { 10 | sinon.stub(windowHelper, 'getWindow').callsFake(function() { 11 | return { 12 | crypto: { 13 | getRandomValues: function() { 14 | return [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; 15 | } 16 | } 17 | }; 18 | }); 19 | }); 20 | 21 | after(function() { 22 | windowHelper.getWindow.restore(); 23 | }); 24 | 25 | it('return the a random string', function() { 26 | var string = random.randomString(10); 27 | expect(string).to.eql('ABCDEFGHIJ'); 28 | }); 29 | }); 30 | 31 | describe('randomString with msCrypto', function() { 32 | before(function() { 33 | sinon.stub(windowHelper, 'getWindow').callsFake(function() { 34 | return { 35 | msCrypto: { 36 | getRandomValues: function() { 37 | return [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; 38 | } 39 | } 40 | }; 41 | }); 42 | }); 43 | 44 | after(function() { 45 | windowHelper.getWindow.restore(); 46 | }); 47 | 48 | it('return the a random string', function() { 49 | var string = random.randomString(10); 50 | expect(string).to.eql('ABCDEFGHIJ'); 51 | }); 52 | }); 53 | 54 | describe('randomString without crypto', function() { 55 | before(function() { 56 | sinon.stub(windowHelper, 'getWindow').callsFake(function() { 57 | return {}; 58 | }); 59 | }); 60 | 61 | after(function() { 62 | windowHelper.getWindow.restore(); 63 | }); 64 | 65 | it('return the a random string', function() { 66 | var string = random.randomString(10); 67 | expect(string).to.be(null); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug 3 | about: Have you found a bug or issue? Create a bug report for this SDK 4 | title: '' 5 | labels: bug report 6 | assignees: '' 7 | --- 8 | 9 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 10 | 11 | **Thank you in advance for helping us to improve this library!** Please read through the template below and answer all relevant questions. Your additional work here is greatly appreciated and will help us respond as quickly as possible. For general support or usage questions, use the [Auth0 Community](https://community.auth0.com/) or [Auth0 Support](https://support.auth0.com/). Finally, to avoid duplicates, please search existing Issues before submitting one here. 12 | 13 | By submitting an Issue to this repository, you agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 14 | 15 | ### Describe the problem 16 | 17 | > Provide a clear and concise description of the issue 18 | 19 | ### What was the expected behavior? 20 | 21 | > Tell us about the behavior you expected to see 22 | 23 | ### Reproduction 24 | 25 | > Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. 26 | > **Note**: If clear, reproducable steps or the smallest sample app demonstrating misbehavior cannot be provided, we may not be able to follow up on this bug report. 27 | 28 | - Step 1.. 29 | - Step 2.. 30 | - ... 31 | 32 | > Where applicable, please include: 33 | > 34 | > - The smallest possible sample app that reproduces the undesirable behavior 35 | > - Log files (redact/remove sensitive information) 36 | > - Application settings (redact/remove sensitive information) 37 | > - Screenshots 38 | 39 | ### Environment 40 | 41 | > Please provide the following: 42 | 43 | - **Version of Auth0.js used:** 44 | - **Which browsers have you tested in?** 45 | - **Other modules/plugins/libraries that might be involved:** 46 | -------------------------------------------------------------------------------- /src/helper/storage/handler.js: -------------------------------------------------------------------------------- 1 | import windowHandler from '../window'; 2 | import DummyStorage from './dummy'; 3 | import CookieStorage from './cookie'; 4 | import Warn from '../warn'; 5 | 6 | function StorageHandler(options) { 7 | this.warn = new Warn({}); 8 | this.storage = new CookieStorage(); 9 | if (options.__tryLocalStorageFirst !== true) { 10 | return; 11 | } 12 | try { 13 | // some browsers throw an error when trying to access localStorage 14 | // when localStorage is disabled. 15 | var localStorage = windowHandler.getWindow().localStorage; 16 | if (localStorage) { 17 | this.storage = localStorage; 18 | } 19 | } catch (e) { 20 | this.warn.warning(e); 21 | this.warn.warning("Can't use localStorage. Using CookieStorage instead."); 22 | } 23 | } 24 | 25 | StorageHandler.prototype.failover = function() { 26 | if (this.storage instanceof DummyStorage) { 27 | this.warn.warning('DummyStorage: ignore failover'); 28 | return; 29 | } else if (this.storage instanceof CookieStorage) { 30 | this.warn.warning('CookieStorage: failing over DummyStorage'); 31 | this.storage = new DummyStorage(); 32 | } else { 33 | this.warn.warning('LocalStorage: failing over CookieStorage'); 34 | this.storage = new CookieStorage(); 35 | } 36 | }; 37 | 38 | StorageHandler.prototype.getItem = function(key) { 39 | try { 40 | return this.storage.getItem(key); 41 | } catch (e) { 42 | this.warn.warning(e); 43 | this.failover(); 44 | return this.getItem(key); 45 | } 46 | }; 47 | 48 | StorageHandler.prototype.removeItem = function(key) { 49 | try { 50 | return this.storage.removeItem(key); 51 | } catch (e) { 52 | this.warn.warning(e); 53 | this.failover(); 54 | return this.removeItem(key); 55 | } 56 | }; 57 | 58 | StorageHandler.prototype.setItem = function(key, value, options) { 59 | try { 60 | return this.storage.setItem(key, value, options); 61 | } catch (e) { 62 | this.warn.warning(e); 63 | this.failover(); 64 | return this.setItem(key, value, options); 65 | } 66 | }; 67 | 68 | export default StorageHandler; 69 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /src/helper/parameters-whitelist.js: -------------------------------------------------------------------------------- 1 | // For future reference:, 2 | // The only parameters that should be allowed are parameters 3 | // defined by the specification, or existing parameters that we 4 | // need for compatibility 5 | 6 | import objectHelper from './object'; 7 | 8 | var tokenParams = [ 9 | // auth0 10 | 'realm', 11 | 'audience', 12 | 'otp', 13 | // oauth2 14 | 'client_id', 15 | 'client_secret', 16 | 'redirect_uri', 17 | 'scope', 18 | 'code', 19 | 'grant_type', 20 | 'username', 21 | 'password', 22 | 'refresh_token', 23 | 'assertion', 24 | 'client_assertion', 25 | 'client_assertion_type', 26 | 'code_verifier' 27 | ]; 28 | 29 | var authorizeParams = [ 30 | // auth0 31 | 'connection', 32 | 'connection_scope', 33 | 'auth0Client', 34 | 'owp', 35 | 'device', 36 | 'realm', 37 | 'organization', 38 | 'invitation', 39 | 40 | 'protocol', 41 | '_csrf', 42 | '_intstate', 43 | 'login_ticket', 44 | 45 | // oauth2 46 | 'client_id', 47 | 'response_type', 48 | 'response_mode', 49 | 'redirect_uri', 50 | 'audience', 51 | 'scope', 52 | 'state', 53 | 'nonce', 54 | 'display', 55 | 'prompt', 56 | 'screen_hint', 57 | 'max_age', 58 | 'ui_locales', 59 | 'claims_locales', 60 | 'id_token_hint', 61 | 'login_hint', 62 | 'acr_values', 63 | 'claims', 64 | 'registration', 65 | 'request', 66 | 'request_uri', 67 | 'code_challenge', 68 | 'code_challenge_method', 69 | 70 | // ADDITIONAL_PARAMETERS: 71 | // https://auth0.com/docs/api/authentication?javascript#social 72 | 'access_type', 73 | 'display' 74 | ]; 75 | 76 | function oauthAuthorizeParams(warn, params) { 77 | var notAllowed = objectHelper.getKeysNotIn(params, authorizeParams); 78 | 79 | if (notAllowed.length > 0) { 80 | warn.warning( 81 | 'Following parameters are not allowed on the `/authorize` endpoint: [' + 82 | notAllowed.join(',') + 83 | ']' 84 | ); 85 | } 86 | 87 | return params; 88 | } 89 | 90 | function oauthTokenParams(warn, params) { 91 | return objectHelper.pick(params, tokenParams); 92 | } 93 | 94 | export default { 95 | oauthTokenParams: oauthTokenParams, 96 | oauthAuthorizeParams: oauthAuthorizeParams 97 | }; 98 | -------------------------------------------------------------------------------- /SECURITY-NOTICE.md: -------------------------------------------------------------------------------- 1 | # Security vulnerability details for auth0.js < 9.3 2 | A vulnerability has been discovered in the auth0.js library affecting versions < 9.3. This vulnerability allows an attacker to bypass the CSRF check from the `state` parameter if it's missing from the authorization response (`https://yourwebsite/#access_token={attacker_access_token}&expires_in=7200&token_type=Bearer`, leaving the client vulnerable to CSRF attacks. 3 | 4 | Developers using the auth0.js library versions < 9.3 need to upgrade to the latest version. 5 | 6 | Updated packages are available on npm. To ensure delivery of additional bug fixes moving forward, please make sure your `package.json` file is updated to take patch and minor level updates of our libraries. For example: 7 | 8 | { 9 | "dependencies": { 10 | "auth0-js": "^9.3.0" 11 | } 12 | } 13 | 14 | ### Upgrade Notes 15 | 16 | This fix patches the library that your application runs, but will not impact your users, their current state, or any existing sessions. 17 | 18 | You can read more details regarding the vulnerability [here](https://auth0.com/docs/security/bulletins/cve-2018-7307). 19 | 20 | 21 | 22 | # Security vulnerability details for auth0.js < 8.12 23 | A vulnerability has been discovered in the auth0.js library affecting versions < 8.12. This vulnerability allows an attacker to acquire authenticated users’ tokens and invoke services on the user’s behalf if the target site or application uses a popup callback page with `auth0.popup.callback()`. 24 | 25 | Developers using the auth0.js library versions < 8.12 need to upgrade to the latest version. 26 | 27 | Updated packages are available on npm. To ensure delivery of additional bug fixes moving forward, please make sure your `package.json` file is updated to take patch and minor level updates of our libraries. For example: 28 | 29 | ``` 30 | { 31 | "dependencies": { 32 | "auth0-js": "^9.0.0" 33 | } 34 | } 35 | ``` 36 | 37 | ### Upgrade Notes 38 | 39 | This fix patches the library that your application runs, but will not impact your users, their current state, or any existing sessions. 40 | 41 | You can read more details regarding the vulnerability [here](https://auth0.com/docs/security/bulletins/cve-2017-17068). 42 | -------------------------------------------------------------------------------- /integration/selenium.js: -------------------------------------------------------------------------------- 1 | var webdriver = require('selenium-webdriver'); 2 | 3 | var username = process.env.SAUCELABS_USER; 4 | var accessKey = process.env.SAUCELABS_KEY; 5 | 6 | var capabilities = [ 7 | { 8 | browserName: 'chrome', 9 | platform: 'Windows 10', 10 | version: '55.0' 11 | }, 12 | { 13 | browserName: 'chrome', 14 | platform: 'Windows 10', 15 | version: 'beta' 16 | }, 17 | { 18 | browserName: 'firefox', 19 | platform: 'Windows 10', 20 | version: '50.0' 21 | }, 22 | // { 23 | // 'browserName': 'firefox', 24 | // 'platform': 'Windows 10', 25 | // 'version': 'beta' 26 | // } 27 | // , 28 | // { 29 | // 'browserName': 'internet explorer', 30 | // 'platform': 'Windows 7', 31 | // 'version': '11.0' 32 | // } 33 | // , 34 | // { 35 | // 'browserName': 'internet explorer', 36 | // 'platform': 'Windows 7', 37 | // 'version': '10.0' 38 | // } 39 | // , 40 | // { 41 | // 'browserName': 'MicrosoftEdge', 42 | // 'platform': 'Windows 10', 43 | // 'version': '13.10586' 44 | // } 45 | // , 46 | { 47 | browserName: 'safari', 48 | platform: 'macOS 10.12', 49 | version: '10.0' 50 | } 51 | ]; 52 | 53 | var By = webdriver.By; 54 | var until = webdriver.until; 55 | 56 | module.exports = { 57 | runTests: tests => { 58 | capabilities.forEach(capability => { 59 | var browser = 60 | capability.browserName + 61 | ' ' + 62 | capability.version + 63 | ' ' + 64 | capability.platform; 65 | 66 | tests(name => { 67 | var driver = new webdriver.Builder() 68 | .withCapabilities(capability) 69 | .usingServer( 70 | 'http://' + 71 | username + 72 | ':' + 73 | accessKey + 74 | '@ondemand.saucelabs.com:80/wd/hub' 75 | ) 76 | .build(); 77 | driver.executeScript('sauce:job-name=' + name); 78 | return { 79 | start: () => { 80 | driver.get('https://auth0.github.io/auth0.js/example/test.html'); 81 | driver.wait(until.elementLocated(By.id('loaded')), 10000); 82 | return driver; 83 | }, 84 | finish: () => { 85 | driver.executeScript('sauce:job-result=passed'); 86 | return driver.quit(); 87 | } 88 | }; 89 | }, browser); 90 | }); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /example/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Auth0.js Demo Examples 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 23 | 24 |
25 |
26 | 27 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/web-auth/transaction-manager.js: -------------------------------------------------------------------------------- 1 | import random from '../helper/random'; 2 | import Storage from '../helper/storage'; 3 | import windowHelper from '../helper/window'; 4 | import * as times from '../helper/times'; 5 | 6 | var DEFAULT_NAMESPACE = 'com.auth0.auth.'; 7 | 8 | function TransactionManager(options) { 9 | var transaction = options.transaction || {}; 10 | this.namespace = transaction.namespace || DEFAULT_NAMESPACE; 11 | this.keyLength = transaction.keyLength || 32; 12 | this.storage = new Storage(options); 13 | this.options = options; 14 | } 15 | 16 | TransactionManager.prototype.process = function(options) { 17 | if (!options.responseType) { 18 | throw new Error('responseType is required'); 19 | } 20 | var lastUsedConnection = options.realm || options.connection; 21 | var responseTypeIncludesIdToken = 22 | options.responseType.indexOf('id_token') !== -1; 23 | 24 | var transaction = this.generateTransaction( 25 | options.appState, 26 | options.state, 27 | options.nonce, 28 | lastUsedConnection, 29 | responseTypeIncludesIdToken, 30 | options.organization 31 | ); 32 | 33 | if (!options.state) { 34 | options.state = transaction.state; 35 | } 36 | 37 | if (responseTypeIncludesIdToken && !options.nonce) { 38 | options.nonce = transaction.nonce; 39 | } 40 | 41 | return options; 42 | }; 43 | 44 | TransactionManager.prototype.generateTransaction = function( 45 | appState, 46 | state, 47 | nonce, 48 | lastUsedConnection, 49 | generateNonce, 50 | organization 51 | ) { 52 | state = state || random.randomString(this.keyLength); 53 | nonce = nonce || (generateNonce ? random.randomString(this.keyLength) : null); 54 | 55 | var isHostedLoginPage = 56 | windowHelper.getWindow().location.host === this.options.domain; 57 | 58 | if (!isHostedLoginPage) { 59 | var transactionPayload = { 60 | nonce: nonce, 61 | appState: appState, 62 | state: state, 63 | lastUsedConnection: lastUsedConnection 64 | }; 65 | 66 | if (organization) { 67 | transactionPayload.organization = organization; 68 | } 69 | 70 | this.storage.setItem(this.namespace + state, transactionPayload, { 71 | expires: times.MINUTES_30 72 | }); 73 | } 74 | 75 | return { 76 | state: state, 77 | nonce: nonce 78 | }; 79 | }; 80 | 81 | TransactionManager.prototype.getStoredTransaction = function(state) { 82 | var transactionData; 83 | 84 | transactionData = this.storage.getItem(this.namespace + state); 85 | this.clearTransaction(state); 86 | return transactionData; 87 | }; 88 | 89 | TransactionManager.prototype.clearTransaction = function(state) { 90 | this.storage.removeItem(this.namespace + state); 91 | }; 92 | 93 | export default TransactionManager; 94 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | 5 | NPM_TAG=${2:-"beta"} 6 | MATCHER=${3:-"*"} 7 | 8 | NPM_NAME=$(node scripts/utils/attribute.js name) 9 | VERSION=$(node scripts/utils/attribute.js version) 10 | 11 | NPM_BIN=$(npm bin) 12 | STABLE=$($NPM_BIN/semver $VERSION -r "*") 13 | 14 | # Enable failing on exit status here because semver exits with 1 when the range 15 | # doesn't match. 16 | set -e 17 | 18 | new_line() 19 | { 20 | echo "" 21 | } 22 | 23 | verbose() 24 | { 25 | echo -e " \033[36m→\033[0m $1" 26 | } 27 | 28 | verbose_item() 29 | { 30 | echo -e " \033[96m∙\033[0m $1" 31 | } 32 | 33 | success() 34 | { 35 | echo -e " \033[1;32m✔︎\033[0m $1" 36 | } 37 | 38 | cdn_release() 39 | { 40 | npm run publish:cdn 41 | new_line 42 | success "$NPM_NAME ($1) uploaded to cdn" 43 | } 44 | 45 | bower_release() 46 | { 47 | # Check if tag exists 48 | TAG_NAME="v$VERSION" 49 | TAG_EXISTS=$(git tag -l "$TAG_NAME") 50 | 51 | if [ ! -z "$TAG_EXISTS" ]; then 52 | verbose "There is already a tag $TAG_EXISTS in git. Skipping git deploy." 53 | else 54 | verbose "Deploying $VERSION to git" 55 | 56 | LAST_COMMIT=$(git log -1 --pretty=%B) 57 | # removing dist and build folders from gitignore so it gets pushed to the tag 58 | grep -v -e '^dist$' -e '^dist/$' .gitignore > /tmp/.gitignore 59 | grep -v -e '^build$' -e '^build/$' .gitignore > /tmp/.gitignore 60 | mv /tmp/.gitignore .gitignore 61 | git add --force dist/* 62 | git add --force build/* 63 | git commit -am "$TAG_NAME" 64 | git tag "$TAG_NAME" -m "$LAST_COMMIT" 65 | git push origin $TAG_NAME 66 | success "$NPM_NAME version ready for bower" 67 | fi 68 | } 69 | 70 | npm_release() 71 | { 72 | verbose "Checking if version $1 of $NPM_NAME is already available in npm…" 73 | 74 | NPM_EXISTS=$(npm info -s $NPM_NAME@$1 version) 75 | 76 | if [ ! -z "$NPM_EXISTS" ] && [ "$NPM_EXISTS" == "$1" ]; then 77 | verbose "There is already a version $NPM_EXISTS in npm. Skipping npm publish…" 78 | else 79 | if [ ! -z "$STABLE" ]; then 80 | verbose "Deploying $1 to npm" 81 | npm publish 82 | else 83 | verbose "Deploying $1 to npm with tag $NPM_TAG" 84 | npm publish --tag "$NPM_TAG" 85 | fi 86 | success "$NPM_NAME uploaded to npm registry" 87 | fi 88 | } 89 | 90 | # Lint 91 | npm run lint 92 | 93 | # Test 94 | npm run ci:test 95 | 96 | # Clean 97 | rm -rf dist 98 | rm -rf build 99 | 100 | # Build 101 | npm run build 102 | 103 | # Release 104 | git checkout -b dist 105 | bower_release 106 | new_line 107 | npm_release "$VERSION" 108 | new_line 109 | cdn_release "$VERSION" 110 | git checkout master 111 | git branch -D dist 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /src/helper/response-handler.js: -------------------------------------------------------------------------------- 1 | import error from './error'; 2 | import objectHelper from './object'; 3 | 4 | function wrapCallback(cb, options) { 5 | options = options || {}; 6 | options.ignoreCasing = options.ignoreCasing ? options.ignoreCasing : false; 7 | 8 | return function(err, data) { 9 | var errObj; 10 | 11 | if (!err && !data) { 12 | return cb(error.buildResponse('generic_error', 'Something went wrong')); 13 | } 14 | 15 | if (!err && data.err) { 16 | err = data.err; 17 | data = null; 18 | } 19 | 20 | if (!err && data.error) { 21 | err = data; 22 | data = null; 23 | } 24 | 25 | if (err) { 26 | errObj = { 27 | original: err 28 | }; 29 | 30 | objectHelper.updatePropertyOn( 31 | errObj, 32 | 'original.response.req._data.password', 33 | '*****' 34 | ); 35 | 36 | if (err.response && err.response.statusCode) { 37 | errObj.statusCode = err.response.statusCode; 38 | } 39 | 40 | if (err.response && err.response.statusText) { 41 | errObj.statusText = err.response.statusText; 42 | } 43 | 44 | if (err.response && err.response.body) { 45 | err = err.response.body; 46 | } 47 | 48 | if (err.err) { 49 | err = err.err; 50 | } 51 | 52 | errObj.code = 53 | err.code || err.error || err.error_code || err.status || null; 54 | 55 | errObj.description = 56 | err.errorDescription || 57 | err.error_description || 58 | err.description || 59 | err.error || 60 | err.details || 61 | err.err || 62 | null; 63 | 64 | if (options.forceLegacyError) { 65 | errObj.error = errObj.code; 66 | errObj.error_description = errObj.description; 67 | } 68 | 69 | if (err.error_codes && err.error_details) { 70 | errObj.errorDetails = { 71 | codes: err.error_codes, 72 | details: err.error_details 73 | }; 74 | } 75 | 76 | if (err.name) { 77 | errObj.name = err.name; 78 | } 79 | 80 | if (err.policy) { 81 | errObj.policy = err.policy; 82 | } 83 | 84 | return cb(errObj); 85 | } 86 | 87 | if ( 88 | data.type && 89 | (data.type === 'text/html' || data.type === 'text/plain') 90 | ) { 91 | return cb(null, data.text); 92 | } 93 | 94 | if (options.ignoreCasing) { 95 | return cb(null, data.body || data); 96 | } 97 | 98 | return cb( 99 | null, 100 | objectHelper.toCamelCase(data.body || data, [], { 101 | keepOriginal: options.keepOriginalCasing 102 | }) 103 | ); 104 | }; 105 | } 106 | 107 | export default wrapCallback; 108 | -------------------------------------------------------------------------------- /src/web-auth/redirect.js: -------------------------------------------------------------------------------- 1 | import CrossOriginAuthentication from './cross-origin-authentication'; 2 | import Warn from '../helper/warn'; 3 | 4 | function Redirect(auth0, options) { 5 | this.webAuth = auth0; 6 | this.baseOptions = options; 7 | this.crossOriginAuthentication = new CrossOriginAuthentication( 8 | auth0, 9 | this.baseOptions 10 | ); 11 | 12 | this.warn = new Warn({ 13 | disableWarnings: !!options._disableDeprecationWarnings 14 | }); 15 | } 16 | 17 | /** 18 | * Logs in the user with username and password using the cross origin authentication (/co/authenticate) flow. You can use either `username` or `email` to identify the user, but `username` will take precedence over `email`. 19 | * Some browsers might not be able to successfully authenticate if 3rd party cookies are disabled in your browser. [See here for more information.]{@link https://auth0.com/docs/cross-origin-authentication}. 20 | * After the /co/authenticate call, you'll have to use the {@link parseHash} function at the `redirectUri` specified in the constructor. 21 | * 22 | * @method loginWithCredentials 23 | * @deprecated This method will be released in the next major version. Use `webAuth.login` instead. 24 | * @param {Object} options options used in the {@link authorize} call after the login_ticket is acquired 25 | * @param {String} [options.username] Username (mutually exclusive with email) 26 | * @param {String} [options.email] Email (mutually exclusive with username) 27 | * @param {String} options.password Password 28 | * @param {String} [options.connection] Connection used to authenticate the user, it can be a realm name or a database connection name 29 | * @param {crossOriginLoginCallback} cb Callback function called only when an authentication error, like invalid username or password, occurs. For other types of errors, there will be a redirect to the `redirectUri`. 30 | */ 31 | Redirect.prototype.loginWithCredentials = function(options, cb) { 32 | options.realm = options.realm || options.connection; 33 | delete options.connection; 34 | this.crossOriginAuthentication.login(options, cb); 35 | }; 36 | 37 | /** 38 | * Signs up a new user and automatically logs the user in after the signup. 39 | * 40 | * @method signupAndLogin 41 | * @param {Object} options 42 | * @param {String} options.email user email address 43 | * @param {String} options.password user password 44 | * @param {String} options.connection name of the connection where the user will be created 45 | * @param {crossOriginLoginCallback} cb 46 | */ 47 | Redirect.prototype.signupAndLogin = function(options, cb) { 48 | var _this = this; 49 | return this.webAuth.client.dbConnection.signup(options, function(err) { 50 | if (err) { 51 | return cb(err); 52 | } 53 | options.realm = options.realm || options.connection; 54 | delete options.connection; 55 | return _this.webAuth.login(options, cb); 56 | }); 57 | }; 58 | 59 | export default Redirect; 60 | -------------------------------------------------------------------------------- /test/helper/storage.cookie.test.js: -------------------------------------------------------------------------------- 1 | import CookieLibrary from 'js-cookie'; 2 | import expect from 'expect.js'; 3 | import sinon from 'sinon'; 4 | 5 | import CookieStorage from '../../src/helper/storage/cookie'; 6 | import windowHandler from '../../src/helper/window'; 7 | 8 | var cookieStorage = new CookieStorage(); 9 | const KEY = 'foo'; 10 | const VALUE = 'bar'; 11 | 12 | describe('storage.cookies', function() { 13 | beforeEach(function() { 14 | sinon.stub(CookieLibrary, 'get').callsFake(function(key) { 15 | expect(key).to.be(KEY); 16 | return VALUE; 17 | }); 18 | sinon.stub(CookieLibrary, 'set').callsFake(function(key, value) { 19 | expect(key).to.be(KEY); 20 | expect(value).to.be(VALUE); 21 | }); 22 | sinon.stub(CookieLibrary, 'remove').callsFake(function(key) { 23 | expect(key).to.be(KEY); 24 | }); 25 | }); 26 | afterEach(function() { 27 | CookieLibrary.get.restore(); 28 | CookieLibrary.set.restore(); 29 | CookieLibrary.remove.restore(); 30 | }); 31 | describe('getItem', function() { 32 | it('calls Cookie.get', function() { 33 | const value = cookieStorage.getItem(KEY); 34 | expect(value).to.be(VALUE); 35 | }); 36 | }); 37 | describe('removeItem', function() { 38 | it('calls Cookie.remove', function(done) { 39 | cookieStorage.removeItem(KEY); 40 | done(); 41 | }); 42 | }); 43 | describe('setItem', function() { 44 | beforeEach(function() { 45 | sinon.stub(windowHandler, 'getWindow').callsFake(function() { 46 | return { 47 | location: { 48 | protocol: 'http:' 49 | } 50 | }; 51 | }); 52 | }); 53 | 54 | afterEach(function() { 55 | windowHandler.getWindow.restore(); 56 | }); 57 | 58 | it('calls Cookie.set with default values', function() { 59 | cookieStorage.setItem(KEY, VALUE); 60 | 61 | expect(CookieLibrary.set.firstCall.args).to.be.eql([ 62 | 'foo', 63 | 'bar', 64 | { expires: 1 } 65 | ]); 66 | }); 67 | 68 | it('calls Cookie.set with custom values', function() { 69 | cookieStorage.setItem(KEY, VALUE, { expires: 2, test: true }); 70 | 71 | expect(CookieLibrary.set.firstCall.args).to.be.eql([ 72 | 'foo', 73 | 'bar', 74 | { expires: 2, test: true } 75 | ]); 76 | }); 77 | 78 | it('sets the secure flag on cookies when using the https protocol', function() { 79 | windowHandler.getWindow.restore(); 80 | sinon.stub(windowHandler, 'getWindow').callsFake(function() { 81 | return { 82 | location: { 83 | protocol: 'https:' 84 | } 85 | }; 86 | }); 87 | 88 | cookieStorage.setItem(KEY, VALUE, { expires: 2, test: true }); 89 | 90 | expect(CookieLibrary.set.firstCall.args).to.be.eql([ 91 | 'foo', 92 | 'bar', 93 | { expires: 2, test: true, secure: true } 94 | ]); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/web-auth/web-message-handler.js: -------------------------------------------------------------------------------- 1 | import IframeHandler from '../helper/iframe-handler'; 2 | import objectHelper from '../helper/object'; 3 | import windowHelper from '../helper/window'; 4 | import Warn from '../helper/warn'; 5 | 6 | function runWebMessageFlow(authorizeUrl, options, callback) { 7 | var handler = new IframeHandler({ 8 | url: authorizeUrl, 9 | eventListenerType: 'message', 10 | callback: function(eventData) { 11 | callback(null, eventData); 12 | }, 13 | timeout: options.timeout, 14 | eventValidator: { 15 | isValid: function(eventData) { 16 | return ( 17 | eventData.event.data.type === 'authorization_response' && 18 | options.state === eventData.event.data.response.state 19 | ); 20 | } 21 | }, 22 | timeoutCallback: function() { 23 | callback({ 24 | error: 'timeout', 25 | error_description: 'Timeout during executing web_message communication', 26 | state: options.state 27 | }); 28 | } 29 | }); 30 | handler.init(); 31 | } 32 | 33 | function WebMessageHandler(webAuth) { 34 | this.webAuth = webAuth; 35 | this.warn = new Warn(webAuth.baseOptions); 36 | } 37 | 38 | WebMessageHandler.prototype.run = function(options, cb) { 39 | var _this = this; 40 | options.responseMode = 'web_message'; 41 | options.prompt = 'none'; 42 | 43 | var currentOrigin = windowHelper.getOrigin(); 44 | var redirectUriOrigin = objectHelper.getOriginFromUrl(options.redirectUri); 45 | if (redirectUriOrigin && currentOrigin !== redirectUriOrigin) { 46 | return cb({ 47 | error: 'origin_mismatch', 48 | error_description: 49 | "The redirectUri's origin (" + 50 | redirectUriOrigin + 51 | ") should match the window's origin (" + 52 | currentOrigin + 53 | ').' 54 | }); 55 | } 56 | 57 | runWebMessageFlow( 58 | this.webAuth.client.buildAuthorizeUrl(options), 59 | options, 60 | function(err, eventData) { 61 | var error = err; 62 | if (!err && eventData.event.data.response.error) { 63 | error = eventData.event.data.response; 64 | } 65 | if (!error) { 66 | var parsedHash = eventData.event.data.response; 67 | return _this.webAuth.validateAuthenticationResponse( 68 | options, 69 | parsedHash, 70 | cb 71 | ); 72 | } 73 | if ( 74 | error.error === 'consent_required' && 75 | windowHelper.getWindow().location.hostname === 'localhost' 76 | ) { 77 | _this.warn.warning( 78 | "Consent Required. Consent can't be skipped on localhost. Read more here: https://auth0.com/docs/api-auth/user-consent#skipping-consent-for-first-party-clients" 79 | ); 80 | } 81 | _this.webAuth.transactionManager.clearTransaction(error.state); 82 | return cb(objectHelper.pick(error, ['error', 'error_description'])); 83 | } 84 | ); 85 | }; 86 | 87 | export default WebMessageHandler; 88 | -------------------------------------------------------------------------------- /src/helper/iframe-handler.js: -------------------------------------------------------------------------------- 1 | import windowHelper from './window'; 2 | 3 | function IframeHandler(options) { 4 | this.url = options.url; 5 | this.callback = options.callback; 6 | this.timeout = options.timeout || 60 * 1000; 7 | this.timeoutCallback = options.timeoutCallback || null; 8 | this.eventListenerType = options.eventListenerType || 'message'; 9 | this.iframe = null; 10 | this.timeoutHandle = null; 11 | this._destroyTimeout = null; 12 | this.transientMessageEventListener = null; 13 | this.proxyEventListener = null; 14 | // If no event identifier specified, set default 15 | this.eventValidator = options.eventValidator || { 16 | isValid: function() { 17 | return true; 18 | } 19 | }; 20 | 21 | if (typeof this.callback !== 'function') { 22 | throw new Error('options.callback must be a function'); 23 | } 24 | } 25 | 26 | IframeHandler.prototype.init = function() { 27 | var _this = this; 28 | var _window = windowHelper.getWindow(); 29 | 30 | this.iframe = _window.document.createElement('iframe'); 31 | this.iframe.style.display = 'none'; 32 | 33 | // Workaround to avoid using bind that does not work in IE8 34 | this.proxyEventListener = function(e) { 35 | _this.eventListener(e); 36 | }; 37 | 38 | switch (this.eventListenerType) { 39 | case 'message': 40 | this.eventSourceObject = _window; 41 | break; 42 | case 'load': 43 | this.eventSourceObject = this.iframe; 44 | break; 45 | default: 46 | throw new Error( 47 | 'Unsupported event listener type: ' + this.eventListenerType 48 | ); 49 | } 50 | 51 | this.eventSourceObject.addEventListener( 52 | this.eventListenerType, 53 | this.proxyEventListener, 54 | false 55 | ); 56 | 57 | _window.document.body.appendChild(this.iframe); 58 | 59 | this.iframe.src = this.url; 60 | 61 | this.timeoutHandle = setTimeout(function() { 62 | _this.timeoutHandler(); 63 | }, this.timeout); 64 | }; 65 | 66 | IframeHandler.prototype.eventListener = function(event) { 67 | var eventData = { event: event, sourceObject: this.eventSourceObject }; 68 | 69 | if (!this.eventValidator.isValid(eventData)) { 70 | return; 71 | } 72 | 73 | this.destroy(); 74 | this.callback(eventData); 75 | }; 76 | 77 | IframeHandler.prototype.timeoutHandler = function() { 78 | this.destroy(); 79 | if (this.timeoutCallback) { 80 | this.timeoutCallback(); 81 | } 82 | }; 83 | 84 | IframeHandler.prototype.destroy = function() { 85 | var _this = this; 86 | 87 | clearTimeout(this.timeoutHandle); 88 | 89 | this._destroyTimeout = setTimeout(function() { 90 | _this.eventSourceObject.removeEventListener( 91 | _this.eventListenerType, 92 | _this.proxyEventListener, 93 | false 94 | ); 95 | 96 | if (_this.iframe.parentNode) { 97 | _this.iframe.parentNode.removeChild(_this.iframe); 98 | } 99 | }, 0); 100 | }; 101 | 102 | export default IframeHandler; 103 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const resolve = require('rollup-plugin-node-resolve'); 2 | const commonjs = require('rollup-plugin-commonjs'); 3 | const replace = require('rollup-plugin-replace'); 4 | const { terser } = require('rollup-plugin-terser'); 5 | const serve = require('rollup-plugin-serve'); 6 | const livereload = require('rollup-plugin-livereload'); 7 | const license = require('rollup-plugin-license'); 8 | const json = require('rollup-plugin-json'); 9 | const { argv } = require('yargs'); 10 | 11 | const pkg = require('./package.json'); 12 | 13 | const isProduction = argv.prod === true; 14 | 15 | const OUTPUT_PATH = 'dist'; 16 | 17 | const getPlugins = prod => [ 18 | resolve({ 19 | browser: true 20 | }), 21 | commonjs(), 22 | json(), 23 | replace({ 24 | __DEV__: prod ? 'false' : 'true', 25 | 'process.env.NODE_ENV': prod ? "'production'" : "'development'" 26 | }), 27 | prod && 28 | terser({ 29 | compress: { warnings: false }, 30 | output: { comments: false }, 31 | mangle: false 32 | }), 33 | license({ 34 | banner: ` 35 | <%= pkg.name %> v<%= pkg.version %> 36 | Author: Auth0 37 | Date: <%= moment().format('YYYY-MM-DD') %> 38 | License: MIT 39 | ` 40 | }) 41 | ]; 42 | 43 | const prodFiles = [ 44 | { 45 | input: 'src/index.js', 46 | output: [ 47 | { 48 | name: 'auth0', 49 | file: pkg.main, 50 | format: 'umd', 51 | sourcemap: true, 52 | exports: 'named' 53 | }, 54 | { 55 | file: pkg.module, 56 | format: 'es', 57 | sourcemap: true 58 | } 59 | ], 60 | plugins: getPlugins(isProduction) 61 | }, 62 | { 63 | input: 'plugins/cordova/index.js', 64 | output: { 65 | name: 'CordovaAuth0Plugin', 66 | file: `${OUTPUT_PATH}/cordova-auth0-plugin.min.js`, 67 | format: 'umd', 68 | sourcemap: true, 69 | exports: 'default' 70 | }, 71 | plugins: getPlugins(isProduction) 72 | } 73 | ]; 74 | const devFiles = [ 75 | { 76 | input: 'src/index.js', 77 | output: { 78 | name: 'auth0', 79 | file: `${OUTPUT_PATH}/auth0.js`, 80 | format: 'umd', 81 | sourcemap: isProduction ? false : 'inline', 82 | exports: 'named' 83 | }, 84 | plugins: [ 85 | ...getPlugins(false), 86 | !isProduction && 87 | serve({ 88 | contentBase: ['dist', 'example'], 89 | open: true, 90 | port: 3000 91 | }), 92 | !isProduction && livereload() 93 | ] 94 | }, 95 | { 96 | input: 'plugins/cordova/index.js', 97 | output: { 98 | name: 'CordovaAuth0Plugin', 99 | file: `${OUTPUT_PATH}/cordova-auth0-plugin.js`, 100 | format: 'umd', 101 | sourcemap: isProduction ? false : 'inline', 102 | exports: 'default' 103 | }, 104 | plugins: getPlugins(false) 105 | } 106 | ]; 107 | 108 | const finalFiles = [...devFiles]; 109 | if (isProduction) { 110 | finalFiles.push(...prodFiles); 111 | } 112 | export default finalFiles; 113 | -------------------------------------------------------------------------------- /src/helper/popup-handler.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-syntax */ 2 | /* eslint-disable guard-for-in */ 3 | import WinChan from 'winchan'; 4 | 5 | import windowHandler from './window'; 6 | import objectHelper from './object'; 7 | import qs from 'qs'; 8 | 9 | function PopupHandler() { 10 | this._current_popup = null; 11 | } 12 | 13 | PopupHandler.prototype.calculatePosition = function(options) { 14 | var width = options.width || 500; 15 | var height = options.height || 600; 16 | var _window = windowHandler.getWindow(); 17 | 18 | var screenX = 19 | typeof _window.screenX !== 'undefined' 20 | ? _window.screenX 21 | : _window.screenLeft; 22 | var screenY = 23 | typeof _window.screenY !== 'undefined' 24 | ? _window.screenY 25 | : _window.screenTop; 26 | 27 | var outerWidth = 28 | typeof _window.outerWidth !== 'undefined' 29 | ? _window.outerWidth 30 | : _window.document.body.clientWidth; 31 | 32 | var outerHeight = 33 | typeof _window.outerHeight !== 'undefined' 34 | ? _window.outerHeight 35 | : _window.document.body.clientHeight; 36 | 37 | var left = options.left || screenX + (outerWidth - width) / 2; 38 | var top = options.top || screenY + (outerHeight - height) / 2; 39 | 40 | return { width: width, height: height, left: left, top: top }; 41 | }; 42 | 43 | PopupHandler.prototype.preload = function(options) { 44 | var _this = this; 45 | var _window = windowHandler.getWindow(); 46 | var popupPosition = this.calculatePosition(options.popupOptions || {}); 47 | var popupOptions = objectHelper 48 | .merge(popupPosition) 49 | .with(options.popupOptions); 50 | var url = options.url || 'about:blank'; 51 | var windowFeatures = qs.stringify(popupOptions, { 52 | encode: false, 53 | delimiter: ',' 54 | }); 55 | 56 | if (this._current_popup && !this._current_popup.closed) { 57 | return this._current_popup; 58 | } 59 | 60 | this._current_popup = _window.open(url, 'auth0_signup_popup', windowFeatures); 61 | 62 | this._current_popup.kill = function() { 63 | this.close(); 64 | _this._current_popup = null; 65 | }; 66 | 67 | return this._current_popup; 68 | }; 69 | 70 | PopupHandler.prototype.load = function(url, relayUrl, options, cb) { 71 | var _this = this; 72 | var popupPosition = this.calculatePosition(options.popupOptions || {}); 73 | var popupOptions = objectHelper 74 | .merge(popupPosition) 75 | .with(options.popupOptions); 76 | 77 | var winchanOptions = objectHelper 78 | .merge({ 79 | url: url, 80 | relay_url: relayUrl, 81 | window_features: qs.stringify(popupOptions, { 82 | delimiter: ',', 83 | encode: false 84 | }), 85 | popup: this._current_popup 86 | }) 87 | .with(options); 88 | 89 | var popup = WinChan.open(winchanOptions, function(err, data) { 90 | // Ignores messages sent by browser extensions. 91 | if (err && err.name === 'SyntaxError') { 92 | return; 93 | } 94 | _this._current_popup = null; 95 | return cb(err, data); 96 | }); 97 | 98 | popup.focus(); 99 | 100 | return popup; 101 | }; 102 | 103 | export default PopupHandler; 104 | -------------------------------------------------------------------------------- /test/helper/storage.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import StorageHandler from '../../src/helper/storage/handler'; 5 | import Storage from '../../src/helper/storage'; 6 | 7 | describe('helpers storage', function() { 8 | var storage; 9 | beforeEach(function() { 10 | storage = new Storage({}); 11 | }); 12 | describe('setItem', function() { 13 | beforeEach(function() { 14 | sinon.spy(StorageHandler.prototype, 'setItem'); 15 | }); 16 | afterEach(function() { 17 | StorageHandler.prototype.setItem.restore(); 18 | }); 19 | it('should call setItem when value is a string', function() { 20 | storage.setItem('data', 'text', { options: true }); 21 | expect(StorageHandler.prototype.setItem.firstCall.args).to.be.eql([ 22 | 'data', 23 | '"text"', 24 | { options: true } 25 | ]); 26 | }); 27 | it('should call setItem with a JSON string when value is an object', function() { 28 | storage.setItem('data', { myProp: true }, { options: true }); 29 | expect(StorageHandler.prototype.setItem.firstCall.args).to.be.eql([ 30 | 'data', 31 | JSON.stringify({ myProp: true }), 32 | { options: true } 33 | ]); 34 | }); 35 | }); 36 | describe('getItem', function() { 37 | afterEach(function() { 38 | StorageHandler.prototype.getItem.restore(); 39 | }); 40 | it('should call getItem and return string when value is a string', function() { 41 | sinon.stub(StorageHandler.prototype, 'getItem').callsFake(function(key) { 42 | expect(key).to.be('data'); 43 | return 'the-value'; 44 | }); 45 | var theValue = storage.getItem('data'); 46 | expect(theValue).to.be('the-value'); 47 | }); 48 | it('should call getItem and return an object when value is a JSON string', function() { 49 | sinon.stub(StorageHandler.prototype, 'getItem').callsFake(function(key) { 50 | expect(key).to.be('data'); 51 | return JSON.stringify({ theObject: true }); 52 | }); 53 | var theObject = storage.getItem('data'); 54 | expect(theObject).to.be.eql({ theObject: true }); 55 | }); 56 | it('should call getItem and return undefined when value is undefined', function() { 57 | sinon.stub(StorageHandler.prototype, 'getItem').callsFake(function(key) { 58 | expect(key).to.be('data'); 59 | return undefined; 60 | }); 61 | var nothing = storage.getItem('data'); 62 | expect(nothing).to.be(undefined); 63 | }); 64 | it('should call getItem and return null when value is null', function() { 65 | sinon.stub(StorageHandler.prototype, 'getItem').callsFake(function(key) { 66 | expect(key).to.be('data'); 67 | return null; 68 | }); 69 | var noValue = storage.getItem('data'); 70 | expect(noValue).to.be(null); 71 | }); 72 | }); 73 | describe('removeItem', () => { 74 | beforeEach(function() { 75 | sinon.spy(StorageHandler.prototype, 'removeItem'); 76 | }); 77 | afterEach(function() { 78 | StorageHandler.prototype.removeItem.restore(); 79 | }); 80 | it('should call removeItem', function() { 81 | storage.removeItem('data'); 82 | expect(StorageHandler.prototype.removeItem.firstCall.args).to.be.eql([ 83 | 'data' 84 | ]); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: 4 | # It receives only one parameter with is the version level (major, minor or patch) 5 | # 6 | # Running the script directly: 7 | # scripts/release.sh minor 8 | # 9 | # Running the npm script 10 | # npm run release -- major 11 | # 12 | # or the tag it should use the final version number starting with `v` 13 | # 14 | # Running the script directly: 15 | # scripts/release.sh v8.0.0-beta.1 16 | # 17 | # Running the npm script 18 | # npm run release -- v8.0.0-beta.1 19 | 20 | NEW_VERSION="" 21 | VALID_VERSION_LEVELS=(major minor patch) 22 | CURR_DATE=`date +%Y-%m-%d` 23 | 24 | IS_VALID_VERSION_LEVEL=false 25 | VERSION_LEVEL=$1 26 | 27 | REPO_URL=$( jq .repository.url package.json | sed 's/\"//g' | sed 's/\.git//g') 28 | REPO_NAME=$( basename $REPO_URL ) 29 | TMP_CHANGELOG_FILE="/tmp/$REPO_NAME-TMPCHANGELOG-$RANDOM" 30 | 31 | if [ "$REPO_NAME" = "null" ] || [ "$REPO_NAME" = "" ]; then 32 | echo "Could not parse repository url" 33 | exit 1 34 | fi 35 | 36 | if [ "$VERSION_LEVEL" = "" ]; then 37 | echo "Version level not provided" 38 | exit 1 39 | fi 40 | 41 | for i in "${!VALID_VERSION_LEVELS[@]}"; do 42 | if [[ "${VALID_VERSION_LEVELS[$i]}" = "${VERSION_LEVEL}" ]]; then 43 | IS_VALID_VERSION_LEVEL=true 44 | fi 45 | done 46 | 47 | if [ $IS_VALID_VERSION_LEVEL = false ]; then 48 | FIRST_LETER=${VERSION_LEVEL:0:1} 49 | 50 | if [ "$FIRST_LETER" != "v" ]; then 51 | echo "Version level is not valid (major, minor, patch or the version tag (v#.#.#) supported)" 52 | exit 1 53 | fi 54 | 55 | NEW_V_VERSION=$VERSION_LEVEL 56 | NEW_VERSION=${VERSION_LEVEL:1} 57 | fi 58 | 59 | echo "Release process init" 60 | 61 | ORIG_VERSION=$(jq .version package.json | sed 's/\"//g') 62 | ORIG_V_VERSION="v$ORIG_VERSION" 63 | 64 | echo "Current version" $ORIG_VERSION 65 | 66 | if [ "$NEW_VERSION" == "" ]; then 67 | NEW_VERSION=$( node_modules/.bin/semver $ORIG_VERSION -i $VERSION_LEVEL ) 68 | NEW_V_VERSION="v$NEW_VERSION" 69 | fi 70 | 71 | QUOTED_NEW_VERSION="\"$NEW_VERSION\"" 72 | SINGLE_QUOTED_NEW_VERSION="'$NEW_VERSION'" 73 | 74 | echo "New version" $NEW_VERSION 75 | 76 | read -p "Do you want to continue? (y/n) " choice 77 | case "$choice" in 78 | y|Y ) echo "Releasing" $NEW_VERSION;; 79 | * ) exit 0;; 80 | esac 81 | 82 | echo "Updating package.json" 83 | jq ".version=$QUOTED_NEW_VERSION" package.json > package.json.new 84 | 85 | git checkout master 86 | git pull 87 | git checkout -b prepare-$NEW_V_VERSION 88 | 89 | echo "Generating tmp changelog" 90 | echo "" >> $TMP_CHANGELOG_FILE 91 | echo "## [$NEW_V_VERSION](https://github.com/auth0/$REPO_NAME/tree/$NEW_V_VERSION) ($CURR_DATE)" >> $TMP_CHANGELOG_FILE 92 | echo "[Full Changelog](https://github.com/auth0/$REPO_NAME/compare/$ORIG_V_VERSION...$NEW_V_VERSION)" >> $TMP_CHANGELOG_FILE 93 | 94 | CHANGELOG_WEBTASK="https://webtask.it.auth0.com/api/run/wt-hernan-auth0_com-0/oss-changelog.js?webtask_no_cache=1&repo=$REPO_NAME&milestone=$NEW_V_VERSION" 95 | 96 | curl -f -s -H "Accept: text/markdown" $CHANGELOG_WEBTASK >> $TMP_CHANGELOG_FILE 97 | 98 | echo "Updating README.md" 99 | sed -i .old "s/auth0\/$ORIG_VERSION\/auth0.min.js/auth0\/$NEW_VERSION\/auth0.min.js/g" README.md 100 | 101 | echo "Updating CHANGELOG.md" 102 | 103 | sed "s/\#Change Log//" CHANGELOG.md >> $TMP_CHANGELOG_FILE 104 | 105 | echo "Replacing files" 106 | 107 | echo "module.exports = { raw: $SINGLE_QUOTED_NEW_VERSION };" > src/version.js 108 | 109 | mv package.json.new package.json 110 | mv $TMP_CHANGELOG_FILE CHANGELOG.md 111 | rm README.md.old 112 | 113 | node scripts/jsdocs.js 114 | 115 | git commit -am "Release $NEW_V_VERSION" 116 | git push origin HEAD 117 | -------------------------------------------------------------------------------- /src/web-auth/silent-authentication-handler.js: -------------------------------------------------------------------------------- 1 | import IframeHandler from '../helper/iframe-handler'; 2 | import windowHelper from '../helper/window'; 3 | 4 | function SilentAuthenticationHandler(options) { 5 | this.authenticationUrl = options.authenticationUrl; 6 | this.timeout = options.timeout || 60 * 1000; 7 | this.handler = null; 8 | this.postMessageDataType = options.postMessageDataType || false; 9 | 10 | // prefer origin from options, fallback to origin from browser, and some browsers (for example MS Edge) don't support origin; fallback to construct origin manually 11 | this.postMessageOrigin = 12 | options.postMessageOrigin || 13 | windowHelper.getWindow().location.origin || 14 | windowHelper.getWindow().location.protocol + 15 | '//' + 16 | windowHelper.getWindow().location.hostname + 17 | (windowHelper.getWindow().location.port 18 | ? ':' + windowHelper.getWindow().location.port 19 | : ''); 20 | } 21 | 22 | SilentAuthenticationHandler.create = function(options) { 23 | return new SilentAuthenticationHandler(options); 24 | }; 25 | 26 | SilentAuthenticationHandler.prototype.login = function( 27 | usePostMessage, 28 | callback 29 | ) { 30 | this.handler = new IframeHandler({ 31 | auth0: this.auth0, 32 | url: this.authenticationUrl, 33 | eventListenerType: usePostMessage ? 'message' : 'load', 34 | callback: this.getCallbackHandler(callback, usePostMessage), 35 | timeout: this.timeout, 36 | eventValidator: this.getEventValidator(), 37 | timeoutCallback: function() { 38 | callback( 39 | null, 40 | '#error=timeout&error_description=Timeout+during+authentication+renew.' 41 | ); 42 | }, 43 | usePostMessage: usePostMessage || false 44 | }); 45 | 46 | this.handler.init(); 47 | }; 48 | 49 | SilentAuthenticationHandler.prototype.getEventValidator = function() { 50 | var _this = this; 51 | return { 52 | isValid: function(eventData) { 53 | switch (eventData.event.type) { 54 | case 'message': 55 | // Message must come from the expected origin and iframe window. 56 | if ( 57 | eventData.event.origin !== _this.postMessageOrigin || 58 | eventData.event.source !== _this.handler.iframe.contentWindow 59 | ) { 60 | return false; 61 | } 62 | 63 | // Default behaviour, return all message events from the iframe. 64 | if (_this.postMessageDataType === false) { 65 | return true; 66 | } 67 | 68 | return ( 69 | eventData.event.data.type && 70 | eventData.event.data.type === _this.postMessageDataType 71 | ); 72 | 73 | case 'load': 74 | if ( 75 | eventData.sourceObject.contentWindow.location.protocol === 'about:' 76 | ) { 77 | // Chrome is automatically loading the about:blank page, we ignore this. 78 | return false; 79 | } 80 | // Fall through to default 81 | default: 82 | return true; 83 | } 84 | } 85 | }; 86 | }; 87 | 88 | SilentAuthenticationHandler.prototype.getCallbackHandler = function( 89 | callback, 90 | usePostMessage 91 | ) { 92 | return function(eventData) { 93 | var callbackValue; 94 | if (!usePostMessage) { 95 | callbackValue = eventData.sourceObject.contentWindow.location.hash; 96 | } else if ( 97 | typeof eventData.event.data === 'object' && 98 | eventData.event.data.hash 99 | ) { 100 | callbackValue = eventData.event.data.hash; 101 | } else { 102 | callbackValue = eventData.event.data; 103 | } 104 | callback(null, callbackValue); 105 | }; 106 | }; 107 | 108 | export default SilentAuthenticationHandler; 109 | -------------------------------------------------------------------------------- /plugins/cordova/popup-handler.js: -------------------------------------------------------------------------------- 1 | import windowHandler from '../../src/helper/window'; 2 | import qs from 'qs'; 3 | import urljoin from 'url-join'; 4 | 5 | function PopupHandler(webAuth) { 6 | this.webAuth = webAuth; 7 | this._current_popup = null; 8 | this.options = null; 9 | } 10 | 11 | PopupHandler.prototype.preload = function(options) { 12 | var _this = this; 13 | var _window = windowHandler.getWindow(); 14 | 15 | var url = options.url || 'about:blank'; 16 | var popupOptions = options.popupOptions || {}; 17 | 18 | popupOptions.location = 'yes'; 19 | delete popupOptions.width; 20 | delete popupOptions.height; 21 | 22 | var windowFeatures = qs.stringify(popupOptions, { 23 | encode: false, 24 | delimiter: ',' 25 | }); 26 | 27 | if (this._current_popup && !this._current_popup.closed) { 28 | return this._current_popup; 29 | } 30 | 31 | this._current_popup = _window.open(url, '_blank', windowFeatures); 32 | 33 | this._current_popup.kill = function(success) { 34 | _this._current_popup.success = success; 35 | this.close(); 36 | _this._current_popup = null; 37 | }; 38 | 39 | return this._current_popup; 40 | }; 41 | 42 | PopupHandler.prototype.load = function(url, _, options, cb) { 43 | var _this = this; 44 | this.url = url; 45 | this.options = options; 46 | if (!this._current_popup) { 47 | options.url = url; 48 | this.preload(options); 49 | } else { 50 | this._current_popup.location.href = url; 51 | } 52 | 53 | this.transientErrorHandler = function(event) { 54 | _this.errorHandler(event, cb); 55 | }; 56 | 57 | this.transientStartHandler = function(event) { 58 | _this.startHandler(event, cb); 59 | }; 60 | 61 | this.transientExitHandler = function() { 62 | _this.exitHandler(cb); 63 | }; 64 | 65 | this._current_popup.addEventListener('loaderror', this.transientErrorHandler); 66 | this._current_popup.addEventListener('loadstart', this.transientStartHandler); 67 | this._current_popup.addEventListener('exit', this.transientExitHandler); 68 | }; 69 | 70 | PopupHandler.prototype.errorHandler = function(event, cb) { 71 | if (!this._current_popup) { 72 | return; 73 | } 74 | 75 | this._current_popup.kill(true); 76 | 77 | cb({ error: 'window_error', errorDescription: event.message }); 78 | }; 79 | 80 | PopupHandler.prototype.unhook = function() { 81 | this._current_popup.removeEventListener( 82 | 'loaderror', 83 | this.transientErrorHandler 84 | ); 85 | this._current_popup.removeEventListener( 86 | 'loadstart', 87 | this.transientStartHandler 88 | ); 89 | this._current_popup.removeEventListener('exit', this.transientExitHandler); 90 | }; 91 | 92 | PopupHandler.prototype.exitHandler = function(cb) { 93 | if (!this._current_popup) { 94 | return; 95 | } 96 | 97 | // when the modal is closed, this event is called which ends up removing the 98 | // event listeners. If you move this before closing the modal, it will add ~1 sec 99 | // delay between the user being redirected to the callback and the popup gets closed. 100 | this.unhook(); 101 | 102 | if (!this._current_popup.success) { 103 | cb({ error: 'window_closed', errorDescription: 'Browser window closed' }); 104 | } 105 | }; 106 | 107 | PopupHandler.prototype.startHandler = function(event, cb) { 108 | var _this = this; 109 | 110 | if (!this._current_popup) { 111 | return; 112 | } 113 | 114 | var callbackUrl = urljoin( 115 | 'https:', 116 | this.webAuth.baseOptions.domain, 117 | '/mobile' 118 | ); 119 | 120 | if (event.url && !(event.url.indexOf(callbackUrl + '#') === 0)) { 121 | return; 122 | } 123 | 124 | var parts = event.url.split('#'); 125 | 126 | if (parts.length === 1) { 127 | return; 128 | } 129 | 130 | var opts = { hash: parts.pop() }; 131 | 132 | if (this.options.nonce) { 133 | opts.nonce = this.options.nonce; 134 | } 135 | 136 | this.webAuth.parseHash(opts, function(error, result) { 137 | if (error || result) { 138 | _this._current_popup.kill(true); 139 | cb(error, result); 140 | } 141 | }); 142 | }; 143 | 144 | export default PopupHandler; 145 | -------------------------------------------------------------------------------- /src/helper/request-builder.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | import request from 'superagent'; 3 | import base64Url from './base64_url'; 4 | import version from '../version'; 5 | import objectHelper from './object'; 6 | 7 | // ------------------------------------------------ RequestWrapper 8 | 9 | function RequestWrapper(req) { 10 | this.request = req; 11 | this.method = req.method; 12 | this.url = req.url; 13 | this.body = req._data; 14 | this.headers = req._header; 15 | } 16 | 17 | RequestWrapper.prototype.abort = function() { 18 | this.request.abort(); 19 | }; 20 | 21 | RequestWrapper.prototype.getMethod = function() { 22 | return this.method; 23 | }; 24 | 25 | RequestWrapper.prototype.getBody = function() { 26 | return this.body; 27 | }; 28 | 29 | RequestWrapper.prototype.getUrl = function() { 30 | return this.url; 31 | }; 32 | 33 | RequestWrapper.prototype.getHeaders = function() { 34 | return this.headers; 35 | }; 36 | 37 | // ------------------------------------------------ RequestObj 38 | 39 | function RequestObj(req) { 40 | this.request = req; 41 | } 42 | 43 | RequestObj.prototype.set = function(key, value) { 44 | this.request = this.request.set(key, value); 45 | return this; 46 | }; 47 | 48 | RequestObj.prototype.send = function(body) { 49 | this.request = this.request.send(objectHelper.trimUserDetails(body)); 50 | return this; 51 | }; 52 | 53 | RequestObj.prototype.withCredentials = function() { 54 | this.request = this.request.withCredentials(); 55 | return this; 56 | }; 57 | 58 | RequestObj.prototype.end = function(cb) { 59 | this.request.end(cb); 60 | return new RequestWrapper(this.request); 61 | }; 62 | 63 | // ------------------------------------------------ RequestBuilder 64 | 65 | function RequestBuilder(options) { 66 | this._sendTelemetry = 67 | options._sendTelemetry === false ? options._sendTelemetry : true; 68 | this._telemetryInfo = options._telemetryInfo || null; 69 | this._timesToRetryFailedRequests = options._timesToRetryFailedRequests; 70 | this.headers = options.headers || {}; 71 | this._universalLoginPage = options.universalLoginPage; 72 | } 73 | 74 | RequestBuilder.prototype.setCommonConfiguration = function( 75 | ongoingRequest, 76 | options 77 | ) { 78 | options = options || {}; 79 | 80 | if (this._timesToRetryFailedRequests > 0) { 81 | ongoingRequest = ongoingRequest.retry(this._timesToRetryFailedRequests); 82 | } 83 | 84 | if (options.noHeaders) { 85 | return ongoingRequest; 86 | } 87 | 88 | var headers = this.headers; 89 | ongoingRequest = ongoingRequest.set('Content-Type', 'application/json'); 90 | 91 | var keys = Object.keys(this.headers); 92 | 93 | for (var a = 0; a < keys.length; a++) { 94 | ongoingRequest = ongoingRequest.set(keys[a], headers[keys[a]]); 95 | } 96 | 97 | if (this._sendTelemetry) { 98 | ongoingRequest = ongoingRequest.set( 99 | 'Auth0-Client', 100 | this.getTelemetryData() 101 | ); 102 | } 103 | 104 | return ongoingRequest; 105 | }; 106 | 107 | RequestBuilder.prototype.getTelemetryData = function() { 108 | var telemetryName = this._universalLoginPage ? 'auth0.js-ulp' : 'auth0.js'; 109 | var clientInfo = { name: telemetryName, version: version.raw }; 110 | if (this._telemetryInfo) { 111 | clientInfo = objectHelper.extend({}, this._telemetryInfo); 112 | clientInfo.env = objectHelper.extend({}, this._telemetryInfo.env); 113 | clientInfo.env[telemetryName] = version.raw; 114 | } 115 | var jsonClientInfo = JSON.stringify(clientInfo); 116 | return base64Url.encode(jsonClientInfo); 117 | }; 118 | 119 | RequestBuilder.prototype.get = function(url, options) { 120 | return new RequestObj(this.setCommonConfiguration(request.get(url), options)); 121 | }; 122 | 123 | RequestBuilder.prototype.post = function(url, options) { 124 | return new RequestObj( 125 | this.setCommonConfiguration(request.post(url), options) 126 | ); 127 | }; 128 | 129 | RequestBuilder.prototype.patch = function(url, options) { 130 | return new RequestObj( 131 | this.setCommonConfiguration(request.patch(url), options) 132 | ); 133 | }; 134 | 135 | export default RequestBuilder; 136 | -------------------------------------------------------------------------------- /src/authentication/db-connection.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | 3 | import objectHelper from '../helper/object'; 4 | import assert from '../helper/assert'; 5 | import responseHandler from '../helper/response-handler'; 6 | 7 | function DBConnection(request, options) { 8 | this.baseOptions = options; 9 | this.request = request; 10 | } 11 | 12 | /** 13 | * @callback signUpCallback 14 | * @param {Error} [err] error returned by Auth0 with the reason why the signup failed 15 | * @param {Object} [result] result of the signup request 16 | * @param {Object} result.email user's email 17 | * @param {Object} result.emailVerified if the user's email was verified 18 | */ 19 | 20 | /** 21 | * Creates a new user in a Auth0 Database connection 22 | * 23 | * @method signup 24 | * @param {Object} options 25 | * @param {String} options.email user email address 26 | * @param {String} options.password user password 27 | * @param {String} [options.username] user desired username. Required if you use a database connection and you have enabled `Requires Username` 28 | * @param {String} options.connection name of the connection where the user will be created 29 | * @param {Object} [options.user_metadata] additional signup attributes used for creating the user. Will be stored in `user_metadata` 30 | * @param {signUpCallback} cb 31 | * @see {@link https://auth0.com/docs/api/authentication#signup} 32 | */ 33 | DBConnection.prototype.signup = function(options, cb) { 34 | var url; 35 | var body; 36 | var metadata; 37 | 38 | assert.check( 39 | options, 40 | { type: 'object', message: 'options parameter is not valid' }, 41 | { 42 | connection: { type: 'string', message: 'connection option is required' }, 43 | email: { type: 'string', message: 'email option is required' }, 44 | password: { type: 'string', message: 'password option is required' } 45 | } 46 | ); 47 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 48 | 49 | url = urljoin(this.baseOptions.rootUrl, 'dbconnections', 'signup'); 50 | 51 | body = objectHelper.merge(this.baseOptions, ['clientID', 'state']).with(options); 52 | 53 | metadata = body.user_metadata || body.userMetadata; 54 | 55 | body = objectHelper.blacklist(body, [ 56 | 'scope', 57 | 'userMetadata', 58 | 'user_metadata' 59 | ]); 60 | 61 | body = objectHelper.toSnakeCase(body, ['auth0Client']); 62 | 63 | if (metadata) { 64 | body.user_metadata = metadata; 65 | } 66 | 67 | return this.request 68 | .post(url) 69 | .send(body) 70 | .end(responseHandler(cb)); 71 | }; 72 | 73 | /** 74 | * @callback changePasswordCallback 75 | * @param {Error} [err] error returned by Auth0 with the reason why the request failed 76 | */ 77 | 78 | /** 79 | * Request an email with instruction to change a user's password 80 | * 81 | * @method changePassword 82 | * @param {Object} options 83 | * @param {String} options.email address where the user will receive the change password email. It should match the user's email in Auth0 84 | * @param {String} options.connection name of the connection where the user was created 85 | * @param {changePasswordCallback} cb 86 | * @see {@link https://auth0.com/docs/api/authentication#change-password} 87 | */ 88 | DBConnection.prototype.changePassword = function(options, cb) { 89 | var url; 90 | var body; 91 | 92 | assert.check( 93 | options, 94 | { type: 'object', message: 'options parameter is not valid' }, 95 | { 96 | connection: { type: 'string', message: 'connection option is required' }, 97 | email: { type: 'string', message: 'email option is required' } 98 | } 99 | ); 100 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 101 | 102 | url = urljoin(this.baseOptions.rootUrl, 'dbconnections', 'change_password'); 103 | 104 | body = objectHelper 105 | .merge(this.baseOptions, ['clientID']) 106 | .with(options, ['email', 'connection']); 107 | 108 | body = objectHelper.toSnakeCase(body, ['auth0Client']); 109 | 110 | return this.request 111 | .post(url) 112 | .send(body) 113 | .end(responseHandler(cb)); 114 | }; 115 | 116 | export default DBConnection; 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth0-js", 3 | "version": "9.15.0", 4 | "description": "Auth0 headless browser sdk", 5 | "author": "Auth0", 6 | "license": "MIT", 7 | "module": "dist/auth0.min.esm.js", 8 | "main": "dist/auth0.min.js", 9 | "files": [ 10 | "src", 11 | "plugins", 12 | "build", 13 | "dist" 14 | ], 15 | "keywords": [ 16 | "auth0", 17 | "auth", 18 | "openid", 19 | "authentication", 20 | "jwt", 21 | "browser" 22 | ], 23 | "scripts": { 24 | "clean": "rm -rf dist && rm -rf build", 25 | "start": "npm run clean && rollup -c --watch", 26 | "build": "npm run clean && rollup -c --prod && cp -rf dist build", 27 | "test": "cross-env BABEL_ENV=test mocha --require @babel/register --require jsdom-global/register test/**/*.test.js --exit", 28 | "test:coverage": "nyc --check-coverage -- npm test", 29 | "test:integration": "cross-env BABEL_ENV=test mocha-parallel-tests --compilers @babel/register --compilers jsdom-global/register --max-parallel 2 integration/**/*.test.js", 30 | "test:watch": "npm run test -- --watch --reporter min", 31 | "test:es-check:es5": "es-check es5 'dist/!(*.esm)*.js'", 32 | "test:es-check:es2015:module": "es-check es2015 --module 'dist/auth0.min.esm.js'", 33 | "ci:test": "nyc npm run test -- --forbid-only --reporter mocha-junit-reporter", 34 | "ci:coverage": "codecov", 35 | "lint": "eslint ./src", 36 | "lint:fix": "eslint --fix ./src", 37 | "publish:cdn": "ccu --trace", 38 | "release": "scripts/release.sh", 39 | "jsdocs": "jsdoc --configure .jsdoc.json --verbose", 40 | "precommit": "pretty-quick --staged" 41 | }, 42 | "husky": { 43 | "hooks": { 44 | "pre-commit": "lint-staged" 45 | } 46 | }, 47 | "lint-staged": { 48 | "*.js": "npm run lint" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "git://github.com/auth0/auth0.js" 53 | }, 54 | "dependencies": { 55 | "base64-js": "^1.3.0", 56 | "idtoken-verifier": "^2.0.3", 57 | "js-cookie": "^2.2.0", 58 | "qs": "^6.7.0", 59 | "superagent": "^5.3.1", 60 | "url-join": "^4.0.1", 61 | "winchan": "^0.2.2" 62 | }, 63 | "devDependencies": { 64 | "@auth0/component-cdn-uploader": "^2.2.2", 65 | "@babel/core": "^7.5.5", 66 | "@babel/preset-env": "^7.5.5", 67 | "@babel/register": "^7.5.5", 68 | "babel-plugin-istanbul": "^5.1.4", 69 | "codecov": "^3.7.0", 70 | "cross-env": "^5.2.0", 71 | "es-check": "^5.2.0", 72 | "eslint": "^6.0.1", 73 | "eslint-config-auth0-base": "^13.1.0", 74 | "eslint-config-prettier": "^6.0.0", 75 | "eslint-plugin-compat": "^3.3.0", 76 | "eslint-plugin-import": "^2.18.0", 77 | "expect.js": "^0.3.1", 78 | "husky": "^3.0.0", 79 | "istanbul": "^0.4.5", 80 | "jsdoc": "3.6.3", 81 | "jsdoc-to-markdown": "5.0.0", 82 | "jsdom": "^15.1.1", 83 | "jsdom-global": "^3.0.2", 84 | "jsonwebtoken": "^8.5.1", 85 | "lint-staged": "^9.2.0", 86 | "minami": "^1.2.3", 87 | "mocha": "^6.1.4", 88 | "mocha-junit-reporter": "^1.23.1", 89 | "mocha-parallel-tests": "^2.2.1", 90 | "nyc": "^14.1.1", 91 | "prettier": "^1.18.2", 92 | "pretty-quick": "^1.11.1", 93 | "proxyquire": "^2.1.1", 94 | "rollup": "^1.17.0", 95 | "rollup-plugin-commonjs": "^10.0.1", 96 | "rollup-plugin-json": "^4.0.0", 97 | "rollup-plugin-license": "^2.2.0", 98 | "rollup-plugin-livereload": "^1.0.1", 99 | "rollup-plugin-node-resolve": "^5.2.0", 100 | "rollup-plugin-replace": "^2.2.0", 101 | "rollup-plugin-serve": "^1.0.1", 102 | "rollup-plugin-sourcemaps": "^0.4.2", 103 | "rollup-plugin-terser": "^5.1.1", 104 | "selenium-webdriver": "^3.6.0", 105 | "semver": "^6.2.0", 106 | "sinon": "^7.3.2", 107 | "yargs": "^14.0.0" 108 | }, 109 | "ccu": { 110 | "name": "auth0", 111 | "cdn": "https://cdn.auth0.com", 112 | "mainBundleFile": "auth0.min.js", 113 | "bucket": "assets.us.auth0.com", 114 | "localPath": "dist", 115 | "digest": { 116 | "hashes": [ 117 | "sha384" 118 | ], 119 | "extensions": [ 120 | ".js" 121 | ] 122 | } 123 | }, 124 | "resolutions": { 125 | "minimist": "^1.2.5", 126 | "set-value": "^2.0.1", 127 | "acorn": "^7.1.1", 128 | "serialize-javascript": "^2.1.1", 129 | "kind-of": "^6.0.3" 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/web-auth/extensibility.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import PopupHandler from '../../src/helper/popup-handler'; 5 | import MockAuth0Plugin from '../mock/mock-auth0-plugin'; 6 | import WebAuth from '../../src/web-auth'; 7 | import version from '../../src/version'; 8 | import TransactionManager from '../../src/web-auth/transaction-manager'; 9 | import objectHelper from '../../src/helper/object'; 10 | 11 | describe('auth0.WebAuth extensibility', function() { 12 | context('validations', function() { 13 | it('should validate the plugin version (must throw)', function() { 14 | expect(function() { 15 | var webAuth = new WebAuth({ 16 | domain: 'test.auth0.com', 17 | clientID: '...', 18 | responseType: 'token id_token', 19 | plugins: [new MockAuth0Plugin({ version: 'v1.0.0' })] 20 | }); 21 | }).to.throwException(function(e) { 22 | expect(e.message).to.be( 23 | 'Plugin MockPlugin version (v1.0.0) is not compatible with the SDK version (' + 24 | version.raw + 25 | ')' 26 | ); 27 | }); 28 | }); 29 | 30 | it('should validate the plugin version', function() { 31 | var plugin = new MockAuth0Plugin(); 32 | var webAuth = new WebAuth({ 33 | domain: 'test.auth0.com', 34 | clientID: '...', 35 | responseType: 'token id_token', 36 | plugins: [plugin] 37 | }); 38 | 39 | expect(plugin.webAuth).to.be(webAuth); 40 | }); 41 | }); 42 | 43 | context('buildPopupHandler', function() { 44 | before(function() { 45 | this.webAuth = new WebAuth({ 46 | domain: 'test.auth0.com', 47 | clientID: '...', 48 | responseType: 'token id_token', 49 | plugins: [ 50 | new MockAuth0Plugin({ 51 | extensibilityPoints: 'popup.getPopupHandler', 52 | handler: { 53 | getPopupHandler: function() { 54 | return 'CustomPopupHandler'; 55 | } 56 | } 57 | }) 58 | ] 59 | }); 60 | }); 61 | 62 | it('should get the popup handler from the plugin', function() { 63 | var popupHandler = this.webAuth.popup.buildPopupHandler(); 64 | expect(popupHandler).to.eql('CustomPopupHandler'); 65 | }); 66 | }); 67 | 68 | context('overrdide popup.authorize params', function() { 69 | before(function() { 70 | this.webAuth = new WebAuth({ 71 | domain: 'test.auth0.com', 72 | clientID: '...', 73 | responseType: 'token id_token', 74 | plugins: [ 75 | new MockAuth0Plugin({ 76 | extensibilityPoints: 'popup.authorize', 77 | handler: { 78 | processParams: function(params) { 79 | params.redirectUri = 'http://custom-url.com'; 80 | params.responseType = 'code'; 81 | return params; 82 | } 83 | } 84 | }) 85 | ], 86 | _sendTelemetry: false 87 | }); 88 | sinon 89 | .stub(TransactionManager.prototype, 'generateTransaction') 90 | .callsFake(function(appState, state, nonce) { 91 | return { 92 | state: state || 'randomState', 93 | nonce: nonce || 'randomNonce' 94 | }; 95 | }); 96 | }); 97 | 98 | after(function() { 99 | TransactionManager.prototype.generateTransaction.restore(); 100 | PopupHandler.prototype.load.restore(); 101 | }); 102 | 103 | it('should change the content of the params', function(done) { 104 | sinon 105 | .stub(PopupHandler.prototype, 'load') 106 | .callsFake(function(url, relayUrl, options, cb) { 107 | expect(url).to.be( 108 | 'https://test.auth0.com/authorize?client_id=...&response_type=code&owp=true&scope=openid&redirect_uri=http%3A%2F%2Fcustom-url.com&state=randomState' 109 | ); 110 | expect(relayUrl).to.be('https://test.auth0.com/relay.html'); 111 | expect(options).to.eql({}); 112 | cb(null, { 113 | email_verified: false, 114 | email: 'me@example.com' 115 | }); 116 | }); 117 | 118 | this.webAuth.popup.authorize({ owp: true, scope: 'openid' }, function( 119 | err, 120 | data 121 | ) { 122 | expect(err).to.be(null); 123 | expect(data).to.eql({ 124 | email_verified: false, 125 | emailVerified: false, 126 | email: 'me@example.com' 127 | }); 128 | done(); 129 | }); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /example/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Auth0 Login Callback demo 8 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/web-auth/captcha.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import Authentication from '../authentication'; 3 | import object from '../helper/object'; 4 | 5 | var noop = function () { }; 6 | 7 | var defaults = { 8 | lang: 'en', 9 | templates: { 10 | 'auth0': function (challenge) { 11 | var message = challenge.type === 'code' ? 12 | 'Enter the code shown above' : 13 | 'Solve the formula shown above'; 14 | return '
\n' + 15 | ' \n' + 16 | ' \n' + 17 | '
\n' + 18 | ''; 21 | } 22 | , 23 | 'recaptcha_v2': function () { 24 | return '
'; 25 | } 26 | , 27 | 'error': function () { 28 | return '
Error getting the bot detection challenge. Please contact the system administrator.
' 29 | } 30 | } 31 | }; 32 | 33 | function handleAuth0Provider(element, options, challenge, load) { 34 | element.innerHTML = options.templates[challenge.provider](challenge); 35 | element.querySelector('.captcha-reload').addEventListener('click', function (e) { 36 | e.preventDefault(); 37 | load(); 38 | }); 39 | } 40 | 41 | function injectRecaptchaScript(element, lang, callback) { 42 | var callbackName = 'recaptchaCallback_' + Math.floor(Math.random() * 1000001); 43 | window[callbackName] = function () { 44 | delete window[callbackName]; 45 | callback(); 46 | }; 47 | var script = window.document.createElement('script'); 48 | script.src = 'https://www.google.com/recaptcha/api.js?hl=' + lang + '&onload=' + callbackName; 49 | script.async = true; 50 | window.document.body.appendChild(script); 51 | } 52 | 53 | function handleRecaptchaProvider(element, options, challenge) { 54 | var widgetId = element.hasAttribute('data-wid') && element.getAttribute('data-wid'); 55 | 56 | function setValue(value) { 57 | var input = element.querySelector('input[name="captcha"]'); 58 | input.value = value || ''; 59 | } 60 | 61 | if (widgetId) { 62 | setValue(); 63 | window.grecaptcha.reset(widgetId); 64 | return; 65 | } 66 | 67 | element.innerHTML = options.templates[challenge.provider](challenge); 68 | 69 | var recaptchaDiv = element.querySelector('.recaptcha'); 70 | 71 | injectRecaptchaScript(element, options.lang, function () { 72 | widgetId = window.grecaptcha.render(recaptchaDiv, { 73 | callback: setValue, 74 | 'expired-callback': function () { setValue(); }, 75 | 'error-callback': function () { setValue(); }, 76 | sitekey: challenge.siteKey 77 | }); 78 | element.setAttribute('data-wid', widgetId) 79 | }); 80 | } 81 | 82 | 83 | /** 84 | * 85 | * Renders the captcha challenge in the provided element. 86 | * 87 | * @param {Authentication} auth0Client The challenge response from the authentication server 88 | * @param {HTMLElement} element The element where the captcha needs to be rendered 89 | * @param {Object} options The configuration options for the captcha 90 | * @param {Object} [options.templates] An object containaing templates for each captcha provider 91 | * @param {Function} [options.templates.auth0] template function receiving the challenge and returning an string 92 | * @param {Function} [options.templates.recaptcha_v2] template function receiving the challenge and returning an string 93 | * @param {String} [options.lang=en] the ISO code of the language for recaptcha* 94 | * @param {Function} [callback] an optional callback function 95 | */ 96 | function render(auth0Client, element, options, callback) { 97 | options = object.merge(defaults).with(options || {}); 98 | 99 | function load(done) { 100 | done = done || noop; 101 | auth0Client.getChallenge(function (err, challenge) { 102 | if (err) { 103 | element.innerHTML = options.templates.error(err); 104 | return done(err); 105 | } 106 | if (!challenge.required) { 107 | element.style.display = 'none'; 108 | element.innerHTML = ''; 109 | return; 110 | } 111 | element.style.display = ''; 112 | if (challenge.provider === 'auth0') { 113 | handleAuth0Provider(element, options, challenge, load); 114 | } else if (challenge.provider === 'recaptcha_v2') { 115 | handleRecaptchaProvider(element, options, challenge); 116 | } 117 | done(); 118 | }); 119 | } 120 | 121 | function getValue() { 122 | var captchaInput = element.querySelector('input[name="captcha"]'); 123 | if (!captchaInput) { return; } 124 | return captchaInput.value; 125 | } 126 | 127 | load(callback); 128 | 129 | return { 130 | reload: load, 131 | getValue: getValue 132 | }; 133 | } 134 | 135 | export default { render: render }; 136 | -------------------------------------------------------------------------------- /src/management/index.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | 3 | import RequestBuilder from '../helper/request-builder'; 4 | import assert from '../helper/assert'; 5 | import responseHandler from '../helper/response-handler'; 6 | 7 | /** 8 | * Auth0 Management API Client (methods allowed to be called from the browser only) 9 | * @constructor 10 | * @param {Object} options 11 | * @param {Object} options.domain your Auth0 acount domain 12 | * @param {Object} options.token a valid API token 13 | */ 14 | function Management(options) { 15 | /* eslint-disable */ 16 | assert.check( 17 | options, 18 | { type: 'object', message: 'options parameter is not valid' }, 19 | { 20 | domain: { type: 'string', message: 'domain option is required' }, 21 | token: { type: 'string', message: 'token option is required' }, 22 | _sendTelemetry: { 23 | optional: true, 24 | type: 'boolean', 25 | message: '_sendTelemetry option is not valid' 26 | }, 27 | _telemetryInfo: { 28 | optional: true, 29 | type: 'object', 30 | message: '_telemetryInfo option is not valid' 31 | } 32 | } 33 | ); 34 | /* eslint-enable */ 35 | 36 | this.baseOptions = options; 37 | 38 | this.baseOptions.headers = { 39 | Authorization: 'Bearer ' + this.baseOptions.token 40 | }; 41 | 42 | this.request = new RequestBuilder(this.baseOptions); 43 | this.baseOptions.rootUrl = urljoin( 44 | 'https://' + this.baseOptions.domain, 45 | 'api', 46 | 'v2' 47 | ); 48 | } 49 | 50 | /** 51 | * @callback userCallback 52 | * @param {Error} [err] failure reason for the failed request to Management API 53 | * @param {Object} [result] user profile 54 | */ 55 | 56 | /** 57 | * Returns the user profile 58 | * 59 | * @method getUser 60 | * @param {String} userId identifier of the user to retrieve 61 | * @param {userCallback} cb 62 | * @see https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id 63 | */ 64 | Management.prototype.getUser = function(userId, cb) { 65 | var url; 66 | 67 | assert.check(userId, { 68 | type: 'string', 69 | message: 'userId parameter is not valid' 70 | }); 71 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 72 | 73 | url = urljoin(this.baseOptions.rootUrl, 'users', userId); 74 | 75 | return this.request.get(url).end(responseHandler(cb, { ignoreCasing: true })); 76 | }; 77 | 78 | /** 79 | * Updates the user metdata. It will patch the user metdata with the attributes sent. 80 | * 81 | * 82 | * @method patchUserMetadata 83 | * @param {String} userId 84 | * @param {Object} userMetadata 85 | * @param {userCallback} cb 86 | * @see {@link https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id} 87 | */ 88 | Management.prototype.patchUserMetadata = function(userId, userMetadata, cb) { 89 | var url; 90 | 91 | assert.check(userId, { 92 | type: 'string', 93 | message: 'userId parameter is not valid' 94 | }); 95 | assert.check(userMetadata, { 96 | type: 'object', 97 | message: 'userMetadata parameter is not valid' 98 | }); 99 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 100 | 101 | url = urljoin(this.baseOptions.rootUrl, 'users', userId); 102 | 103 | return this.request 104 | .patch(url) 105 | .send({ user_metadata: userMetadata }) 106 | .end(responseHandler(cb, { ignoreCasing: true })); 107 | }; 108 | 109 | /** 110 | * Updates the user attributes. It will patch the user attributes that the server allows it. 111 | * 112 | * @method patchUserAttributes 113 | * @param {String} userId 114 | * @param {Object} user 115 | * @param {userCallback} cb 116 | * @see {@link https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id} 117 | */ 118 | Management.prototype.patchUserAttributes = function(userId, user, cb) { 119 | var url; 120 | 121 | assert.check(userId, { 122 | type: 'string', 123 | message: 'userId parameter is not valid' 124 | }); 125 | assert.check(user, { 126 | type: 'object', 127 | message: 'user parameter is not valid' 128 | }); 129 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 130 | 131 | url = urljoin(this.baseOptions.rootUrl, 'users', userId); 132 | 133 | return this.request 134 | .patch(url) 135 | .send(user) 136 | .end(responseHandler(cb, { ignoreCasing: true })); 137 | }; 138 | 139 | /** 140 | * Link two users 141 | * 142 | * @method linkUser 143 | * @param {String} userId 144 | * @param {String} secondaryUserToken 145 | * @param {userCallback} cb 146 | * @see {@link https://auth0.com/docs/api/management/v2#!/Users/post_identities} 147 | */ 148 | Management.prototype.linkUser = function(userId, secondaryUserToken, cb) { 149 | var url; 150 | /* eslint-disable */ 151 | assert.check(userId, { 152 | type: 'string', 153 | message: 'userId parameter is not valid' 154 | }); 155 | assert.check(secondaryUserToken, { 156 | type: 'string', 157 | message: 'secondaryUserToken parameter is not valid' 158 | }); 159 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 160 | /* eslint-enable */ 161 | 162 | url = urljoin(this.baseOptions.rootUrl, 'users', userId, 'identities'); 163 | 164 | return this.request 165 | .post(url) 166 | .send({ link_with: secondaryUserToken }) 167 | .end(responseHandler(cb, { ignoreCasing: true })); 168 | }; 169 | 170 | export default Management; 171 | -------------------------------------------------------------------------------- /test/helper/popup-handler.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | import WinChan from 'winchan'; 4 | 5 | import PopupHandler from '../../src/helper/popup-handler'; 6 | 7 | describe('helpers popupHandler', function() { 8 | beforeEach(() => { 9 | global.window = { 10 | screenX: 500, 11 | screenY: 500, 12 | outerWidth: 2000, 13 | outerHeight: 2000, 14 | screenLeft: 500, 15 | screenTop: 500, 16 | document: { 17 | body: { 18 | clientHeight: 2000, 19 | clientWidth: 2000 20 | } 21 | } 22 | }; 23 | }); 24 | 25 | afterEach(() => { 26 | delete global.window; 27 | if (WinChan.open.restore) { 28 | WinChan.open.restore(); 29 | } 30 | }); 31 | describe('calculates the window position', function() { 32 | it('should use default values', function() { 33 | var handler = new PopupHandler(); 34 | var position = handler.calculatePosition({}); 35 | expect(position).to.eql({ 36 | width: 500, 37 | height: 600, 38 | left: 1250, 39 | top: 1200 40 | }); 41 | }); 42 | 43 | it('should use the size from the parameters', function() { 44 | var handler = new PopupHandler(); 45 | var position = handler.calculatePosition({ width: 200, height: 300 }); 46 | expect(position).to.eql({ 47 | width: 200, 48 | height: 300, 49 | left: 1400, 50 | top: 1350 51 | }); 52 | }); 53 | }); 54 | 55 | describe('calculates the window position w screen left/top and body client size', function() { 56 | it('should use default values', function() { 57 | var handler = new PopupHandler(); 58 | var position = handler.calculatePosition({}); 59 | expect(position).to.eql({ 60 | width: 500, 61 | height: 600, 62 | left: 1250, 63 | top: 1200 64 | }); 65 | }); 66 | 67 | it('should use the size from the parameters', function() { 68 | var handler = new PopupHandler(); 69 | var position = handler.calculatePosition({ width: 200, height: 300 }); 70 | expect(position).to.eql({ 71 | width: 200, 72 | height: 300, 73 | left: 1400, 74 | top: 1350 75 | }); 76 | }); 77 | }); 78 | 79 | describe('should open the popup', function() { 80 | it('with the correct parametrs', function(done) { 81 | sinon.stub(WinChan, 'open').callsFake(function(options, cb) { 82 | expect(options).to.eql({ 83 | url: 'url', 84 | relay_url: 'relayUrl', 85 | window_features: 'width=500,height=600,left=1250,top=1200', 86 | popup: null, 87 | params: { opt: 'value' } 88 | }); 89 | 90 | cb(null, { data2: 'value2' }); 91 | 92 | return { 93 | focus: function() { 94 | done(); 95 | } 96 | }; 97 | }); 98 | 99 | var handler = new PopupHandler(); 100 | 101 | handler.load('url', 'relayUrl', { params: { opt: 'value' } }, function( 102 | err, 103 | data 104 | ) { 105 | expect(err).to.be(null); 106 | expect(data).to.eql({ data2: 'value2' }); 107 | }); 108 | }); 109 | }); 110 | 111 | describe('preload should open the popup', function() { 112 | it('should open the window', function(done) { 113 | global.window.open = function(url, name, windowFeatures) { 114 | expect(url).to.eql('about:blank'); 115 | expect(name).to.eql('auth0_signup_popup'); 116 | expect(windowFeatures).to.eql( 117 | 'width=500,height=600,left=1250,top=1200' 118 | ); 119 | 120 | return { 121 | close: function() { 122 | done(); 123 | } 124 | }; 125 | }; 126 | 127 | var handler = new PopupHandler(); 128 | 129 | var popup = handler.preload({}); 130 | 131 | popup.kill(); 132 | }); 133 | 134 | it('should open the window once and return the same instance', function(done) { 135 | var counter = 0; 136 | global.window.open = function(url, name, windowFeatures) { 137 | counter++; 138 | expect(url).to.eql('about:blank'); 139 | expect(counter).to.eql(1); 140 | expect(name).to.eql('auth0_signup_popup'); 141 | expect(windowFeatures).to.eql( 142 | 'width=500,height=600,left=1250,top=1200' 143 | ); 144 | 145 | return { 146 | close: function() { 147 | done(); 148 | } 149 | }; 150 | }; 151 | 152 | var handler = new PopupHandler(); 153 | var popup = handler.preload({}); 154 | var popup2 = handler.preload({}); 155 | 156 | expect(popup).to.be(popup2); 157 | 158 | popup.kill(); 159 | }); 160 | }); 161 | 162 | describe('load', () => { 163 | it('ignores `SyntaxError` errors', done => { 164 | const url = 'https://test.popup.com'; 165 | const relayUrl = 'https://relay.test.popup.com'; 166 | const options = {}; 167 | const callback = (err, data) => { 168 | expect(err).to.be(null); 169 | done(); 170 | }; 171 | 172 | sinon.stub(WinChan, 'open').callsFake(function(_, cb) { 173 | const err = new Error('An error'); 174 | err.name = 'SyntaxError'; 175 | cb(err, null); 176 | setTimeout(() => { 177 | cb(null, { data: 'now it works' }); 178 | }, 100); 179 | return { 180 | focus: function() {} 181 | }; 182 | }); 183 | 184 | new PopupHandler().load(url, relayUrl, options, callback); 185 | }); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /src/web-auth/hosted-pages.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | import qs from 'qs'; 3 | 4 | import UsernamePassword from './username-password'; 5 | import RequestBuilder from '../helper/request-builder'; 6 | import responseHandler from '../helper/response-handler'; 7 | import objectHelper from '../helper/object'; 8 | import windowHelper from '../helper/window'; 9 | import Warn from '../helper/warn'; 10 | import assert from '../helper/assert'; 11 | 12 | function HostedPages(client, options) { 13 | this.baseOptions = options; 14 | this.client = client; 15 | this.baseOptions.universalLoginPage = true; 16 | this.request = new RequestBuilder(this.baseOptions); 17 | 18 | this.warn = new Warn({ 19 | disableWarnings: !!options._disableDeprecationWarnings 20 | }); 21 | } 22 | 23 | /** 24 | * @callback credentialsCallback 25 | * @param {Error} [err] error returned by Auth0 with the reason of the Auth failure 26 | * @param {Object} [result] result of the AuthN request 27 | * @param {String} result.accessToken token that can be used with {@link userinfo} 28 | * @param {String} [result.idToken] token that identifies the user 29 | * @param {String} [result.refreshToken] token that can be used to get new access tokens from Auth0. Note that not all Auth0 Applications can request them or the resource server might not allow them. 30 | */ 31 | 32 | /** 33 | * @callback onRedirectingCallback 34 | * @param {function} done Must be called when finished so that authentication can be resumed 35 | */ 36 | 37 | /** 38 | * Performs authentication with username/email and password with a database connection 39 | * 40 | * This method is not compatible with API Auth so if you need to fetch API tokens with audience 41 | * you should use {@link authorize} or {@link login}. 42 | * 43 | * @method login 44 | * @param {Object} options 45 | * @param {String} [options.redirectUri] url that the Auth0 will redirect after Auth with the Authorization Response 46 | * @param {String} [options.responseType] type of the response used. It can be any of the values `code` and `token` 47 | * @param {String} [options.responseMode] how the AuthN response is encoded and redirected back to the client. Supported values are `query` and `fragment` 48 | * @param {String} [options.scope] scopes to be requested during AuthN. e.g. `openid email` 49 | * @param {onRedirectingCallback} [options.onRedirecting] Hook function that is called before redirecting to /authorize, allowing you to handle custom code. You must call the `done` function to resume authentication. 50 | * @param {credentialsCallback} cb 51 | */ 52 | HostedPages.prototype.login = function(options, cb) { 53 | if (windowHelper.getWindow().location.host !== this.baseOptions.domain) { 54 | throw new Error( 55 | 'This method is meant to be used only inside the Universal Login Page.' 56 | ); 57 | } 58 | 59 | var usernamePassword; 60 | 61 | var params = objectHelper 62 | .merge(this.baseOptions, [ 63 | 'clientID', 64 | 'redirectUri', 65 | 'tenant', 66 | 'responseType', 67 | 'responseMode', 68 | 'scope', 69 | 'audience', 70 | '_csrf', 71 | 'state', 72 | '_intstate', 73 | 'nonce' 74 | ]) 75 | .with(options); 76 | 77 | assert.check( 78 | params, 79 | { type: 'object', message: 'options parameter is not valid' }, 80 | { 81 | responseType: { 82 | type: 'string', 83 | message: 'responseType option is required' 84 | } 85 | } 86 | ); 87 | 88 | usernamePassword = new UsernamePassword(this.baseOptions); 89 | 90 | return usernamePassword.login(params, function(err, data) { 91 | if (err) { 92 | return cb(err); 93 | } 94 | 95 | function doAuth() { 96 | usernamePassword.callback(data); 97 | } 98 | 99 | if (typeof options.onRedirecting === 'function') { 100 | return options.onRedirecting(function() { 101 | doAuth(); 102 | }); 103 | } 104 | 105 | doAuth(); 106 | }); 107 | }; 108 | 109 | /** 110 | * Signs up a new user and automatically logs the user in after the signup. 111 | * 112 | * @method signupAndLogin 113 | * @param {Object} options 114 | * @param {String} options.email user email address 115 | * @param {String} options.password user password 116 | * @param {String} options.connection name of the connection where the user will be created 117 | * @param {credentialsCallback} cb 118 | */ 119 | HostedPages.prototype.signupAndLogin = function(options, cb) { 120 | var _this = this; 121 | return _this.client.client.dbConnection.signup(options, function(err) { 122 | if (err) { 123 | return cb(err); 124 | } 125 | return _this.login(options, cb); 126 | }); 127 | }; 128 | 129 | HostedPages.prototype.getSSOData = function(withActiveDirectories, cb) { 130 | var url; 131 | var params = ''; 132 | 133 | if (typeof withActiveDirectories === 'function') { 134 | cb = withActiveDirectories; 135 | withActiveDirectories = false; 136 | } 137 | 138 | assert.check(withActiveDirectories, { 139 | type: 'boolean', 140 | message: 'withActiveDirectories parameter is not valid' 141 | }); 142 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 143 | 144 | if (withActiveDirectories) { 145 | params = 146 | '?' + 147 | qs.stringify({ 148 | ldaps: 1, 149 | client_id: this.baseOptions.clientID 150 | }); 151 | } 152 | 153 | url = urljoin(this.baseOptions.rootUrl, 'user', 'ssodata', params); 154 | 155 | return this.request 156 | .get(url, { noHeaders: true }) 157 | .withCredentials() 158 | .end(responseHandler(cb)); 159 | }; 160 | 161 | export default HostedPages; 162 | -------------------------------------------------------------------------------- /src/helper/object.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | /* eslint-disable no-restricted-syntax */ 3 | /* eslint-disable guard-for-in */ 4 | 5 | import assert from './assert'; 6 | import objectAssign from './object-assign'; 7 | 8 | function pick(object, keys) { 9 | return keys.reduce(function(prev, key) { 10 | if (object[key]) { 11 | prev[key] = object[key]; 12 | } 13 | return prev; 14 | }, {}); 15 | } 16 | 17 | function getKeysNotIn(obj, allowedKeys) { 18 | var notAllowed = []; 19 | for (var key in obj) { 20 | if (allowedKeys.indexOf(key) === -1) { 21 | notAllowed.push(key); 22 | } 23 | } 24 | return notAllowed; 25 | } 26 | 27 | function objectValues(obj) { 28 | var values = []; 29 | for (var key in obj) { 30 | values.push(obj[key]); 31 | } 32 | return values; 33 | } 34 | 35 | function extend() { 36 | var params = objectValues(arguments); 37 | params.unshift({}); 38 | return objectAssign.get().apply(undefined, params); 39 | } 40 | 41 | function merge(object, keys) { 42 | return { 43 | base: keys ? pick(object, keys) : object, 44 | with: function(object2, keys2) { 45 | object2 = keys2 ? pick(object2, keys2) : object2; 46 | return extend(this.base, object2); 47 | } 48 | }; 49 | } 50 | 51 | function blacklist(object, blacklistedKeys) { 52 | return Object.keys(object).reduce(function(p, key) { 53 | if (blacklistedKeys.indexOf(key) === -1) { 54 | p[key] = object[key]; 55 | } 56 | return p; 57 | }, {}); 58 | } 59 | 60 | function camelToSnake(str) { 61 | var newKey = ''; 62 | var index = 0; 63 | var code; 64 | var wasPrevNumber = true; 65 | var wasPrevUppercase = true; 66 | 67 | while (index < str.length) { 68 | code = str.charCodeAt(index); 69 | if ( 70 | (!wasPrevUppercase && code >= 65 && code <= 90) || 71 | (!wasPrevNumber && code >= 48 && code <= 57) 72 | ) { 73 | newKey += '_'; 74 | newKey += str[index].toLowerCase(); 75 | } else { 76 | newKey += str[index].toLowerCase(); 77 | } 78 | wasPrevNumber = code >= 48 && code <= 57; 79 | wasPrevUppercase = code >= 65 && code <= 90; 80 | index++; 81 | } 82 | 83 | return newKey; 84 | } 85 | 86 | function snakeToCamel(str) { 87 | var parts = str.split('_'); 88 | return parts.reduce(function(p, c) { 89 | return p + c.charAt(0).toUpperCase() + c.slice(1); 90 | }, parts.shift()); 91 | } 92 | 93 | function toSnakeCase(object, exceptions) { 94 | if (typeof object !== 'object' || assert.isArray(object) || object === null) { 95 | return object; 96 | } 97 | exceptions = exceptions || []; 98 | 99 | return Object.keys(object).reduce(function(p, key) { 100 | var newKey = exceptions.indexOf(key) === -1 ? camelToSnake(key) : key; 101 | p[newKey] = toSnakeCase(object[key]); 102 | return p; 103 | }, {}); 104 | } 105 | 106 | function toCamelCase(object, exceptions, options) { 107 | if (typeof object !== 'object' || assert.isArray(object) || object === null) { 108 | return object; 109 | } 110 | 111 | exceptions = exceptions || []; 112 | options = options || {}; 113 | return Object.keys(object).reduce(function(p, key) { 114 | var newKey = exceptions.indexOf(key) === -1 ? snakeToCamel(key) : key; 115 | 116 | p[newKey] = toCamelCase(object[newKey] || object[key], [], options); 117 | 118 | if (options.keepOriginal) { 119 | p[key] = toCamelCase(object[key], [], options); 120 | } 121 | return p; 122 | }, {}); 123 | } 124 | 125 | function getLocationFromUrl(href) { 126 | var match = href.match( 127 | /^(https?:|file:|chrome-extension:)\/\/(([^:/?#]*)(?::([0-9]+))?)([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/ 128 | ); 129 | return ( 130 | match && { 131 | href: href, 132 | protocol: match[1], 133 | host: match[2], 134 | hostname: match[3], 135 | port: match[4], 136 | pathname: match[5], 137 | search: match[6], 138 | hash: match[7] 139 | } 140 | ); 141 | } 142 | 143 | function getOriginFromUrl(url) { 144 | if (!url) { 145 | return undefined; 146 | } 147 | var parsed = getLocationFromUrl(url); 148 | if (!parsed) { 149 | return null; 150 | } 151 | var origin = parsed.protocol + '//' + parsed.hostname; 152 | if (parsed.port) { 153 | origin += ':' + parsed.port; 154 | } 155 | return origin; 156 | } 157 | 158 | function trim(options, key) { 159 | var trimmed = extend(options); 160 | if (options[key]) { 161 | trimmed[key] = options[key].trim(); 162 | } 163 | return trimmed; 164 | } 165 | 166 | function trimMultiple(options, keys) { 167 | return keys.reduce(trim, options); 168 | } 169 | 170 | function trimUserDetails(options) { 171 | return trimMultiple(options, ['username', 'email', 'phoneNumber']); 172 | } 173 | 174 | /** 175 | * Updates the value of a property on the given object, using a deep path selector. 176 | * @param {object} obj The object to set the property value on 177 | * @param {string|array} path The path to the property that should have its value updated. e.g. 'prop1.prop2.prop3' or ['prop1', 'prop2', 'prop3'] 178 | * @param {any} value The value to set 179 | */ 180 | function updatePropertyOn(obj, path, value) { 181 | if (typeof path === 'string') { 182 | path = path.split('.'); 183 | } 184 | 185 | var next = path[0]; 186 | 187 | if (obj.hasOwnProperty(next)) { 188 | if (path.length === 1) { 189 | obj[next] = value; 190 | } else { 191 | updatePropertyOn(obj[next], path.slice(1), value); 192 | } 193 | } 194 | } 195 | 196 | export default { 197 | toSnakeCase: toSnakeCase, 198 | toCamelCase: toCamelCase, 199 | blacklist: blacklist, 200 | merge: merge, 201 | pick: pick, 202 | getKeysNotIn: getKeysNotIn, 203 | extend: extend, 204 | getOriginFromUrl: getOriginFromUrl, 205 | getLocationFromUrl: getLocationFromUrl, 206 | trimUserDetails: trimUserDetails, 207 | updatePropertyOn: updatePropertyOn 208 | }; 209 | -------------------------------------------------------------------------------- /src/authentication/passwordless-authentication.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | 3 | import objectHelper from '../helper/object'; 4 | import assert from '../helper/assert'; 5 | import qs from 'qs'; 6 | import responseHandler from '../helper/response-handler'; 7 | 8 | function PasswordlessAuthentication(request, options) { 9 | this.baseOptions = options; 10 | this.request = request; 11 | } 12 | 13 | PasswordlessAuthentication.prototype.buildVerifyUrl = function(options) { 14 | var params; 15 | var qString; 16 | 17 | /* eslint-disable */ 18 | assert.check( 19 | options, 20 | { type: 'object', message: 'options parameter is not valid' }, 21 | { 22 | connection: { type: 'string', message: 'connection option is required' }, 23 | verificationCode: { 24 | type: 'string', 25 | message: 'verificationCode option is required' 26 | }, 27 | phoneNumber: { 28 | optional: false, 29 | type: 'string', 30 | message: 'phoneNumber option is required', 31 | condition: function(o) { 32 | return !o.email; 33 | } 34 | }, 35 | email: { 36 | optional: false, 37 | type: 'string', 38 | message: 'email option is required', 39 | condition: function(o) { 40 | return !o.phoneNumber; 41 | } 42 | } 43 | } 44 | ); 45 | /* eslint-enable */ 46 | 47 | params = objectHelper 48 | .merge(this.baseOptions, [ 49 | 'clientID', 50 | 'responseType', 51 | 'responseMode', 52 | 'redirectUri', 53 | 'scope', 54 | 'audience', 55 | '_csrf', 56 | 'state', 57 | '_intstate', 58 | 'protocol', 59 | 'nonce' 60 | ]) 61 | .with(options); 62 | 63 | // eslint-disable-next-line 64 | if (this.baseOptions._sendTelemetry) { 65 | params.auth0Client = this.request.getTelemetryData(); 66 | } 67 | 68 | params = objectHelper.toSnakeCase(params, ['auth0Client']); 69 | 70 | qString = qs.stringify(params); 71 | 72 | return urljoin( 73 | this.baseOptions.rootUrl, 74 | 'passwordless', 75 | 'verify_redirect', 76 | '?' + qString 77 | ); 78 | }; 79 | 80 | PasswordlessAuthentication.prototype.start = function(options, cb) { 81 | var url; 82 | var body; 83 | 84 | /* eslint-disable */ 85 | assert.check( 86 | options, 87 | { type: 'object', message: 'options parameter is not valid' }, 88 | { 89 | connection: { type: 'string', message: 'connection option is required' }, 90 | send: { 91 | type: 'string', 92 | message: 'send option is required', 93 | values: ['link', 'code'], 94 | value_message: 'send is not valid ([link, code])' 95 | }, 96 | phoneNumber: { 97 | optional: true, 98 | type: 'string', 99 | message: 'phoneNumber option is required', 100 | condition: function(o) { 101 | return o.send === 'code' || !o.email; 102 | } 103 | }, 104 | email: { 105 | optional: true, 106 | type: 'string', 107 | message: 'email option is required', 108 | condition: function(o) { 109 | return o.send === 'link' || !o.phoneNumber; 110 | } 111 | }, 112 | authParams: { 113 | optional: true, 114 | type: 'object', 115 | message: 'authParams option is required' 116 | } 117 | } 118 | ); 119 | /* eslint-enable */ 120 | 121 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 122 | 123 | url = urljoin(this.baseOptions.rootUrl, 'passwordless', 'start'); 124 | 125 | body = objectHelper 126 | .merge(this.baseOptions, [ 127 | 'clientID', 128 | 'responseType', 129 | 'redirectUri', 130 | 'scope' 131 | ]) 132 | .with(options); 133 | 134 | if (body.scope) { 135 | body.authParams = body.authParams || {}; 136 | body.authParams.scope = body.authParams.scope || body.scope; 137 | } 138 | 139 | if (body.redirectUri) { 140 | body.authParams = body.authParams || {}; 141 | body.authParams.redirect_uri = 142 | body.authParams.redirectUri || body.redirectUri; 143 | } 144 | 145 | if (body.responseType) { 146 | body.authParams = body.authParams || {}; 147 | body.authParams.response_type = 148 | body.authParams.responseType || body.responseType; 149 | } 150 | 151 | delete body.redirectUri; 152 | delete body.responseType; 153 | delete body.scope; 154 | 155 | body = objectHelper.toSnakeCase(body, ['auth0Client', 'authParams']); 156 | 157 | return this.request 158 | .post(url) 159 | .send(body) 160 | .end(responseHandler(cb)); 161 | }; 162 | 163 | PasswordlessAuthentication.prototype.verify = function(options, cb) { 164 | var url; 165 | var cleanOption; 166 | 167 | /* eslint-disable */ 168 | assert.check( 169 | options, 170 | { type: 'object', message: 'options parameter is not valid' }, 171 | { 172 | connection: { type: 'string', message: 'connection option is required' }, 173 | verificationCode: { 174 | type: 'string', 175 | message: 'verificationCode option is required' 176 | }, 177 | phoneNumber: { 178 | optional: false, 179 | type: 'string', 180 | message: 'phoneNumber option is required', 181 | condition: function(o) { 182 | return !o.email; 183 | } 184 | }, 185 | email: { 186 | optional: false, 187 | type: 'string', 188 | message: 'email option is required', 189 | condition: function(o) { 190 | return !o.phoneNumber; 191 | } 192 | } 193 | } 194 | ); 195 | /* eslint-enable */ 196 | 197 | assert.check(cb, { type: 'function', message: 'cb parameter is not valid' }); 198 | 199 | cleanOption = objectHelper.pick(options, [ 200 | 'connection', 201 | 'verificationCode', 202 | 'phoneNumber', 203 | 'email', 204 | 'auth0Client' 205 | ]); 206 | cleanOption = objectHelper.toSnakeCase(cleanOption, ['auth0Client']); 207 | 208 | url = urljoin(this.baseOptions.rootUrl, 'passwordless', 'verify'); 209 | 210 | return this.request 211 | .post(url) 212 | .send(cleanOption) 213 | .end(responseHandler(cb)); 214 | }; 215 | 216 | export default PasswordlessAuthentication; 217 | -------------------------------------------------------------------------------- /test/helper/storage-handler.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import windowHandler from '../../src/helper/window'; 5 | import StorageHandler from '../../src/helper/storage/handler'; 6 | import CookieStorage from '../../src/helper/storage/cookie'; 7 | import DummyStorage from '../../src/helper/storage/dummy'; 8 | 9 | function MockLocalStorage() {} 10 | MockLocalStorage.prototype.getItem = function() { 11 | throw new Error('fail'); 12 | }; 13 | MockLocalStorage.prototype.removeItem = function() { 14 | throw new Error('fail'); 15 | }; 16 | MockLocalStorage.prototype.setItem = function() { 17 | throw new Error('fail'); 18 | }; 19 | 20 | describe('helpers storage handler', function() { 21 | beforeEach(function() { 22 | sinon.stub(windowHandler, 'getWindow').callsFake(function(message) { 23 | return { 24 | localStorage: new MockLocalStorage(), 25 | location: { 26 | protocol: 'http:' 27 | } 28 | }; 29 | }); 30 | }); 31 | afterEach(function() { 32 | windowHandler.getWindow.restore(); 33 | }); 34 | it('should use CookieStorage by default', function() { 35 | var handler = new StorageHandler({}); 36 | expect(handler.storage).to.be.a(CookieStorage); 37 | }); 38 | it('should use localStorage when __tryLocalStorageFirst is true', function() { 39 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 40 | expect(handler.storage).to.be.a(MockLocalStorage); 41 | }); 42 | describe('when using localStorage', function() { 43 | let setItemSpy; 44 | let getItemStub; 45 | let removeItemSpy; 46 | 47 | beforeEach(function() { 48 | windowHandler.getWindow.restore(); 49 | 50 | setItemSpy = sinon.spy(); 51 | getItemStub = sinon.stub().returns('test'); 52 | removeItemSpy = sinon.spy(); 53 | 54 | sinon.stub(windowHandler, 'getWindow').callsFake(function() { 55 | return { 56 | localStorage: { 57 | setItem: setItemSpy, 58 | getItem: getItemStub, 59 | removeItem: removeItemSpy 60 | } 61 | }; 62 | }); 63 | }); 64 | it('calls getItem correctly', function() { 65 | var key = 'foo'; 66 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 67 | expect(handler.getItem(key)).to.be('test'); 68 | expect(getItemStub.firstCall.args).to.be.eql([key]); 69 | }); 70 | it('calls setItem correctly', function() { 71 | var key = 'foo'; 72 | var value = 'bar'; 73 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 74 | handler.setItem(key, value, {}); 75 | expect(setItemSpy.firstCall.args).to.be.eql([key, value, {}]); 76 | }); 77 | it('calls removeItem correctly', function() { 78 | var key = 'foo'; 79 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 80 | handler.removeItem(key); 81 | expect(removeItemSpy.firstCall.args).to.be.eql([key]); 82 | }); 83 | }); 84 | describe('should use cookie storage', function() { 85 | it('when __tryLocalStorageFirst is true but localStorage is not available', function() { 86 | windowHandler.getWindow.restore(); 87 | sinon.stub(windowHandler, 'getWindow').callsFake(function(message) {}); 88 | 89 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 90 | expect(handler.storage).to.be.a(CookieStorage); 91 | }); 92 | it('when localstorage throws an error', function() { 93 | windowHandler.getWindow.restore(); 94 | sinon.stub(windowHandler, 'getWindow').callsFake(function(message) { 95 | return { 96 | get localStorage() { 97 | throw new Error('asdasd'); 98 | } 99 | }; 100 | }); 101 | 102 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 103 | expect(handler.storage).to.be.a(CookieStorage); 104 | }); 105 | it('when localstorage fails with getItem', function() { 106 | sinon.spy(CookieStorage.prototype, 'getItem'); 107 | 108 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 109 | 110 | expect(handler.storage).to.be.a(MockLocalStorage); 111 | handler.getItem('pepe'); 112 | 113 | expect(handler.storage).to.be.a(CookieStorage); 114 | expect(CookieStorage.prototype.getItem.firstCall.args).to.be.eql([ 115 | 'pepe' 116 | ]); 117 | 118 | CookieStorage.prototype.getItem.restore(); 119 | }); 120 | it('when localstorage fails with setItem', function() { 121 | sinon.spy(CookieStorage.prototype, 'setItem'); 122 | 123 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 124 | 125 | expect(handler.storage).to.be.a(MockLocalStorage); 126 | 127 | handler.setItem('some', 'value', { options: true }); 128 | 129 | expect(handler.storage).to.be.a(CookieStorage); 130 | expect(CookieStorage.prototype.setItem.firstCall.args).to.be.eql([ 131 | 'some', 132 | 'value', 133 | { options: true } 134 | ]); 135 | 136 | CookieStorage.prototype.setItem.restore(); 137 | }); 138 | it('when localstorage fails with removeItem', function() { 139 | sinon.spy(CookieStorage.prototype, 'removeItem'); 140 | 141 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 142 | 143 | expect(handler.storage).to.be.a(MockLocalStorage); 144 | handler.removeItem('some'); 145 | 146 | expect(handler.storage).to.be.a(CookieStorage); 147 | expect(CookieStorage.prototype.removeItem.firstCall.args).to.be.eql([ 148 | 'some' 149 | ]); 150 | 151 | CookieStorage.prototype.removeItem.restore(); 152 | }); 153 | }); 154 | 155 | it('should failover to from localStorage to CookieStorage to DummyStorage', function() { 156 | var handler = new StorageHandler({ __tryLocalStorageFirst: true }); 157 | 158 | expect(handler.storage).to.be.a(MockLocalStorage); 159 | handler.failover(); 160 | 161 | expect(handler.storage).to.be.a(CookieStorage); 162 | handler.failover(); 163 | 164 | expect(handler.storage).to.be.a(DummyStorage); 165 | handler.failover(); 166 | 167 | expect(handler.storage).to.be.a(DummyStorage); 168 | expect(handler.storage.getItem()).to.be(null); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /src/web-auth/cross-origin-authentication.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | 3 | import windowHelper from '../helper/window'; 4 | import objectHelper from '../helper/object'; 5 | import RequestBuilder from '../helper/request-builder'; 6 | import WebMessageHandler from './web-message-handler'; 7 | import responseHandler from '../helper/response-handler'; 8 | import Storage from '../helper/storage'; 9 | import * as times from '../helper/times'; 10 | 11 | function CrossOriginAuthentication(webAuth, options) { 12 | this.webAuth = webAuth; 13 | this.baseOptions = options; 14 | this.request = new RequestBuilder(options); 15 | this.webMessageHandler = new WebMessageHandler(webAuth); 16 | this.storage = new Storage(options); 17 | } 18 | 19 | function getFragment(name) { 20 | var theWindow = windowHelper.getWindow(); 21 | var value = '&' + theWindow.location.hash.substring(1); 22 | var parts = value.split('&' + name + '='); 23 | if (parts.length === 2) { 24 | return parts 25 | .pop() 26 | .split('&') 27 | .shift(); 28 | } 29 | } 30 | 31 | function createKey(origin, coId) { 32 | return [ 33 | 'co/verifier', 34 | encodeURIComponent(origin), 35 | encodeURIComponent(coId) 36 | ].join('/'); 37 | } 38 | 39 | /** 40 | * @callback onRedirectingCallback 41 | * @param {function} done Must be called when finished so that authentication can be resumed 42 | */ 43 | 44 | /** 45 | * Logs in the user with username and password using the cross origin authentication (/co/authenticate) flow. You can use either `username` or `email` to identify the user, but `username` will take precedence over `email`. 46 | * Some browsers might not be able to successfully authenticate if 3rd party cookies are disabled in your browser. [See here for more information.]{@link https://auth0.com/docs/cross-origin-authentication}. 47 | * After the /co/authenticate call, you'll have to use the {@link parseHash} function at the `redirectUri` specified in the constructor. 48 | * 49 | * @method login 50 | * @param {Object} options options used in the {@link authorize} call after the login_ticket is acquired 51 | * @param {String} [options.username] Username (mutually exclusive with email) 52 | * @param {String} [options.email] Email (mutually exclusive with username) 53 | * @param {String} [options.password] Password 54 | * @param {String} [options.realm] Realm used to authenticate the user, it can be a realm name or a database connection name 55 | * @param {onRedirectingCallback} [options.onRedirecting] Hook function that is called before redirecting to /authorize, allowing you to handle custom code. You must call the `done` function to resume authentication. 56 | * @param {crossOriginLoginCallback} cb Callback function called only when an authentication error, like invalid username or password, occurs. For other types of errors, there will be a redirect to the `redirectUri`. 57 | */ 58 | CrossOriginAuthentication.prototype.login = function(options, cb) { 59 | var _this = this; 60 | var url = urljoin(this.baseOptions.rootUrl, '/co/authenticate'); 61 | 62 | options.username = options.username || options.email; 63 | delete options.email; 64 | 65 | var authenticateBody = { 66 | client_id: options.clientID || this.baseOptions.clientID, 67 | username: options.username 68 | }; 69 | 70 | if (options.password) { 71 | authenticateBody.password = options.password; 72 | } 73 | 74 | if (options.otp) { 75 | authenticateBody.otp = options.otp; 76 | } 77 | 78 | var realm = options.realm || this.baseOptions.realm; 79 | 80 | if (realm) { 81 | var credentialType = 82 | options.credentialType || 83 | this.baseOptions.credentialType || 84 | 'http://auth0.com/oauth/grant-type/password-realm'; 85 | 86 | authenticateBody.realm = realm; 87 | authenticateBody.credential_type = credentialType; 88 | } else { 89 | authenticateBody.credential_type = 'password'; 90 | } 91 | 92 | this.request 93 | .post(url) 94 | .withCredentials() 95 | .send(authenticateBody) 96 | .end(function(err, data) { 97 | if (err) { 98 | var errorObject = (err.response && err.response.body) || { 99 | error: 'request_error', 100 | error_description: JSON.stringify(err) 101 | }; 102 | 103 | return responseHandler(cb, { forceLegacyError: true })(errorObject); 104 | } 105 | 106 | function doAuth() { 107 | var popupMode = options.popup === true; 108 | 109 | options = objectHelper.blacklist(options, [ 110 | 'password', 111 | 'credentialType', 112 | 'otp', 113 | 'popup', 114 | 'onRedirecting' 115 | ]); 116 | 117 | var authorizeOptions = objectHelper 118 | .merge(options) 119 | .with({ loginTicket: data.body.login_ticket }); 120 | 121 | var key = createKey(_this.baseOptions.rootUrl, data.body.co_id); 122 | 123 | _this.storage.setItem(key, data.body.co_verifier, { 124 | expires: times.MINUTES_15 125 | }); 126 | 127 | if (popupMode) { 128 | _this.webMessageHandler.run( 129 | authorizeOptions, 130 | responseHandler(cb, { forceLegacyError: true }) 131 | ); 132 | } else { 133 | _this.webAuth.authorize(authorizeOptions); 134 | } 135 | } 136 | 137 | // Handle pre-redirecting to login, then proceed with '/authorize' once that is complete 138 | if (typeof options.onRedirecting === 'function') { 139 | options.onRedirecting(doAuth); 140 | } else { 141 | // If not handling pre-redirect, just do the login as before 142 | doAuth(); 143 | } 144 | }); 145 | }; 146 | 147 | function tryGetVerifier(storage, key) { 148 | try { 149 | var verifier = storage.getItem(key); 150 | storage.removeItem(key); 151 | return verifier || ''; 152 | } catch (e) { 153 | return ''; 154 | } 155 | } 156 | 157 | /** 158 | * Runs the callback code for the cross origin authentication call. This method is meant to be called by the cross origin authentication callback url. 159 | * 160 | * @method callback 161 | */ 162 | CrossOriginAuthentication.prototype.callback = function() { 163 | var targetOrigin = decodeURIComponent(getFragment('origin')); 164 | var theWindow = windowHelper.getWindow(); 165 | var _this = this; 166 | 167 | theWindow.addEventListener('message', function(evt) { 168 | if (evt.data.type !== 'co_verifier_request') { 169 | return; 170 | } 171 | var key = createKey(evt.origin, evt.data.request.id); 172 | var verifier = tryGetVerifier(_this.storage, key); 173 | 174 | evt.source.postMessage( 175 | { 176 | type: 'co_verifier_response', 177 | response: { 178 | verifier: verifier 179 | } 180 | }, 181 | evt.origin 182 | ); 183 | }); 184 | 185 | theWindow.parent.postMessage({ type: 'ready' }, targetOrigin); 186 | }; 187 | 188 | export default CrossOriginAuthentication; 189 | -------------------------------------------------------------------------------- /test/authentication/ro.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import RequestMock from '../mock/request-mock'; 5 | 6 | import request from 'superagent'; 7 | 8 | import RequestBuilder from '../../src/helper/request-builder'; 9 | import Authentication from '../../src/authentication'; 10 | 11 | var telemetryInfo = new RequestBuilder({}).getTelemetryData(); 12 | 13 | describe('auth0.authentication', function() { 14 | context('/oauth/ro', function() { 15 | before(function() { 16 | this.auth0 = new Authentication({ 17 | domain: 'me.auth0.com', 18 | clientID: '...', 19 | redirectUri: 'http://page.com/callback', 20 | responseType: 'code' 21 | }); 22 | }); 23 | 24 | afterEach(function() { 25 | request.post.restore(); 26 | }); 27 | 28 | it('should call the ro endpoint with all the parameters', function(done) { 29 | sinon.stub(request, 'post').callsFake(function(url) { 30 | expect(url).to.be('https://me.auth0.com/oauth/ro'); 31 | return new RequestMock({ 32 | body: { 33 | client_id: '...', 34 | grant_type: 'password', 35 | username: 'the username', 36 | password: 'the password', 37 | connection: 'the_connection', 38 | scope: 'openid' 39 | }, 40 | headers: { 41 | 'Content-Type': 'application/json', 42 | 'Auth0-Client': telemetryInfo 43 | }, 44 | cb: function(cb) { 45 | cb(null, { 46 | body: { 47 | id_token: 'id_token.id_token.id_token', 48 | access_token: 'access_token', 49 | token_type: 'bearer' 50 | } 51 | }); 52 | } 53 | }); 54 | }); 55 | 56 | this.auth0.loginWithResourceOwner( 57 | { 58 | username: 'the username', 59 | password: 'the password', 60 | connection: 'the_connection', 61 | scope: 'openid' 62 | }, 63 | function(err, data) { 64 | expect(err).to.be(null); 65 | expect(data).to.eql({ 66 | idToken: 'id_token.id_token.id_token', 67 | accessToken: 'access_token', 68 | tokenType: 'bearer' 69 | }); 70 | done(); 71 | } 72 | ); 73 | }); 74 | 75 | it('should handle ro errors', function(done) { 76 | sinon.stub(request, 'post').callsFake(function(url) { 77 | expect(url).to.be('https://me.auth0.com/oauth/ro'); 78 | return new RequestMock({ 79 | body: { 80 | client_id: '...', 81 | grant_type: 'password', 82 | username: 'the username', 83 | password: 'the password', 84 | connection: 'the_connection', 85 | scope: 'openid' 86 | }, 87 | headers: { 88 | 'Content-Type': 'application/json', 89 | 'Auth0-Client': telemetryInfo 90 | }, 91 | cb: function(cb) { 92 | cb({ 93 | error: 'unauthorized', 94 | error_description: 'invalid username' 95 | }); 96 | } 97 | }); 98 | }); 99 | 100 | this.auth0.loginWithResourceOwner( 101 | { 102 | username: 'the username', 103 | password: 'the password', 104 | connection: 'the_connection', 105 | scope: 'openid' 106 | }, 107 | function(err, data) { 108 | expect(data).to.be(undefined); 109 | expect(err).to.eql({ 110 | original: { 111 | error: 'unauthorized', 112 | error_description: 'invalid username' 113 | }, 114 | code: 'unauthorized', 115 | description: 'invalid username' 116 | }); 117 | done(); 118 | } 119 | ); 120 | }); 121 | 122 | it('should call the ro endpoint overriding the parameters', function(done) { 123 | sinon.stub(request, 'post').callsFake(function(url) { 124 | expect(url).to.be('https://me.auth0.com/oauth/ro'); 125 | return new RequestMock({ 126 | body: { 127 | client_id: '...', 128 | grant_type: 'password', 129 | username: 'the username', 130 | password: 'the password', 131 | connection: 'the_connection', 132 | scope: 'openid' 133 | }, 134 | headers: { 135 | 'Content-Type': 'application/json', 136 | 'Auth0-Client': telemetryInfo 137 | }, 138 | cb: function(cb) { 139 | cb(null, { 140 | body: { 141 | id_token: 'id_token.id_token.id_token', 142 | access_token: 'access_token', 143 | token_type: 'bearer' 144 | } 145 | }); 146 | } 147 | }); 148 | }); 149 | 150 | this.auth0.loginWithResourceOwner( 151 | { 152 | username: 'the username', 153 | password: 'the password', 154 | connection: 'the_connection', 155 | scope: 'openid' 156 | }, 157 | function(err, data) { 158 | expect(err).to.be(null); 159 | expect(data).to.eql({ 160 | idToken: 'id_token.id_token.id_token', 161 | accessToken: 'access_token', 162 | tokenType: 'bearer' 163 | }); 164 | done(); 165 | } 166 | ); 167 | }); 168 | 169 | it('should exclude parameters that are not allowed', function(done) { 170 | sinon.stub(request, 'post').callsFake(function(url) { 171 | expect(url).to.be('https://me.auth0.com/oauth/ro'); 172 | return new RequestMock({ 173 | body: { 174 | client_id: '...', 175 | grant_type: 'password', 176 | username: 'the username', 177 | password: 'the password', 178 | connection: 'the_connection' 179 | }, 180 | headers: { 181 | 'Content-Type': 'application/json', 182 | 'Auth0-Client': telemetryInfo 183 | }, 184 | cb: function(cb) { 185 | cb(null, { 186 | body: { 187 | id_token: 'id_token.id_token.id_token', 188 | access_token: 'access_token', 189 | token_type: 'bearer' 190 | } 191 | }); 192 | } 193 | }); 194 | }); 195 | 196 | this.auth0.loginWithResourceOwner( 197 | { 198 | username: 'the username', 199 | password: 'the password', 200 | connection: 'the_connection', 201 | should_ignore: { invalid: 'invalid value' } 202 | }, 203 | function(err, data) { 204 | expect(err).to.be(null); 205 | expect(data).to.eql({ 206 | idToken: 'id_token.id_token.id_token', 207 | accessToken: 'access_token', 208 | tokenType: 'bearer' 209 | }); 210 | done(); 211 | } 212 | ); 213 | }); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /integration/redirect_authorize.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var webdriver = require('selenium-webdriver'); 3 | var selenium = require('./selenium'); 4 | 5 | var By = webdriver.By; 6 | var until = webdriver.until; 7 | 8 | describe('redirect authorize', function() { 9 | this.timeout(9999999); 10 | 11 | selenium.runTests((newSession, browser) => { 12 | context(browser, function() { 13 | it('[token] should result in a successful transaction', function() { 14 | var session = newSession(this.test.title); 15 | var driver = session.start(); 16 | 17 | driver.findElement(By.id('login-response-type')).sendKeys('token'); 18 | driver.findElement(By.className('login-redirect-authorize')).click(); 19 | driver.wait(until.elementLocated(By.id('hlploaded')), 30000); 20 | driver.findElement(By.id('email')).sendKeys('johnfoo@gmail.com'); 21 | driver.findElement(By.id('password')).sendKeys('1234'); 22 | driver.findElement(By.id('upLogin')).click(); 23 | driver.wait(until.elementLocated(By.id('parsed')), 10000); 24 | 25 | driver 26 | .findElement(By.id('err')) 27 | .getText() 28 | .then(function(value) { 29 | console.log('ERR:', value ? value : '-empty-'); 30 | expect(value).to.equal(''); 31 | }); 32 | driver 33 | .findElement(By.id('result')) 34 | .getText() 35 | .then(function(value) { 36 | console.log('RESULT:', value); 37 | expect(value).to.not.equal(''); 38 | 39 | var response = JSON.parse(value); 40 | 41 | expect(response.accessToken).to.be.ok(); 42 | expect(response.idToken).to.not.be.ok(); 43 | expect(response.tokenType).to.be.ok(); 44 | expect(response.expiresIn).to.be.ok(); 45 | }); 46 | 47 | return session.finish(); 48 | }); 49 | 50 | it('[code] should result in a successful transaction', function() { 51 | var session = newSession(this.test.title); 52 | var driver = session.start(); 53 | 54 | driver.findElement(By.id('login-response-type')).sendKeys('code'); 55 | driver.findElement(By.className('login-redirect-authorize')).click(); 56 | driver.wait(until.elementLocated(By.id('hlploaded')), 30000); 57 | driver.findElement(By.id('email')).sendKeys('johnfoo@gmail.com'); 58 | driver.findElement(By.id('password')).sendKeys('1234'); 59 | driver.findElement(By.id('upLogin')).click(); 60 | driver.wait(until.elementLocated(By.id('loaded')), 10000); 61 | 62 | driver.getCurrentUrl().then(function(url) { 63 | console.log('RESULT URL:', url); 64 | expect(url).to.contain('code='); 65 | }); 66 | 67 | return session.finish(); 68 | }); 69 | 70 | it('[token openid] should result in a successful transaction', function() { 71 | var session = newSession(this.test.title); 72 | var driver = session.start(); 73 | 74 | driver.findElement(By.id('login-scope')).sendKeys('openid'); 75 | driver.findElement(By.id('login-response-type')).sendKeys('token'); 76 | driver.findElement(By.className('login-redirect-authorize')).click(); 77 | driver.wait(until.elementLocated(By.id('hlploaded')), 30000); 78 | driver.findElement(By.id('email')).sendKeys('johnfoo@gmail.com'); 79 | driver.findElement(By.id('password')).sendKeys('1234'); 80 | driver.findElement(By.id('upLogin')).click(); 81 | driver.wait(until.elementLocated(By.id('parsed')), 10000); 82 | 83 | driver 84 | .findElement(By.id('err')) 85 | .getText() 86 | .then(function(value) { 87 | console.log('ERR:', value ? value : '-empty-'); 88 | expect(value).to.equal(''); 89 | }); 90 | 91 | driver 92 | .findElement(By.id('result')) 93 | .getText() 94 | .then(function(value) { 95 | console.log('RESULT:', value); 96 | expect(value).to.not.equal(''); 97 | 98 | var response = JSON.parse(value); 99 | 100 | expect(response.accessToken).to.be.ok(); 101 | expect(response.idToken).to.not.be.ok(); 102 | expect(response.tokenType).to.be.ok(); 103 | expect(response.expiresIn).to.be.ok(); 104 | }); 105 | 106 | return session.finish(); 107 | }); 108 | 109 | it('[id_token] should result in a successful transaction', function() { 110 | var session = newSession(this.test.title); 111 | var driver = session.start(); 112 | 113 | driver.findElement(By.id('login-response-type')).sendKeys('id_token'); 114 | driver.findElement(By.className('login-redirect-authorize')).click(); 115 | driver.wait(until.elementLocated(By.id('hlploaded')), 30000); 116 | driver.findElement(By.id('email')).sendKeys('johnfoo@gmail.com'); 117 | driver.findElement(By.id('password')).sendKeys('1234'); 118 | driver.findElement(By.id('upLogin')).click(); 119 | driver.wait(until.elementLocated(By.id('parsed')), 10000); 120 | 121 | driver 122 | .findElement(By.id('err')) 123 | .getText() 124 | .then(function(value) { 125 | console.log('ERR:', value ? value : '-empty-'); 126 | expect(value).to.equal(''); 127 | }); 128 | 129 | driver 130 | .findElement(By.id('result')) 131 | .getText() 132 | .then(function(value) { 133 | console.log('RESULT:', value); 134 | expect(value).to.not.equal(''); 135 | 136 | var response = JSON.parse(value); 137 | 138 | expect(response.accessToken).to.not.be.ok(); 139 | expect(response.idToken).to.be.ok(); 140 | expect(response.tokenType).to.not.be.ok(); 141 | expect(response.expiresIn).to.not.be.ok(); 142 | }); 143 | 144 | return session.finish(); 145 | }); 146 | 147 | it('[token id_token] should result in a successful transaction', function() { 148 | var session = newSession(this.test.title); 149 | var driver = session.start(); 150 | 151 | driver 152 | .findElement(By.id('login-response-type')) 153 | .sendKeys('token id_token'); 154 | driver.findElement(By.className('login-redirect-authorize')).click(); 155 | driver.wait(until.elementLocated(By.id('hlploaded')), 30000); 156 | driver.findElement(By.id('email')).sendKeys('johnfoo@gmail.com'); 157 | driver.findElement(By.id('password')).sendKeys('1234'); 158 | driver.findElement(By.id('upLogin')).click(); 159 | driver.wait(until.elementLocated(By.id('parsed')), 10000); 160 | 161 | driver 162 | .findElement(By.id('err')) 163 | .getText() 164 | .then(function(value) { 165 | console.log('ERR:', value ? value : '-empty-'); 166 | expect(value).to.equal(''); 167 | }); 168 | 169 | driver 170 | .findElement(By.id('result')) 171 | .getText() 172 | .then(function(value) { 173 | console.log('RESULT:', value); 174 | expect(value).to.not.equal(''); 175 | 176 | var response = JSON.parse(value); 177 | 178 | expect(response.accessToken).to.be.ok(); 179 | expect(response.idToken).to.be.ok(); 180 | expect(response.tokenType).to.be.ok(); 181 | expect(response.expiresIn).to.be.ok(); 182 | }); 183 | 184 | return session.finish(); 185 | }); 186 | }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /test/authentication/db-connection.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | import sinon from 'sinon'; 3 | 4 | import RequestMock from '../mock/request-mock'; 5 | import request from 'superagent'; 6 | 7 | import RequestBuilder from '../../src/helper/request-builder'; 8 | import Authentication from '../../src/authentication'; 9 | var telemetryInfo = new RequestBuilder({}).getTelemetryData(); 10 | 11 | describe('auth0.authentication', function() { 12 | context('dbConnection signup options', function() { 13 | before(function() { 14 | this.auth0 = new Authentication({ 15 | domain: 'me.auth0.com', 16 | clientID: '...', 17 | redirectUri: 'http://page.com/callback', 18 | responseType: 'code', 19 | state: 'state-xyz' 20 | }); 21 | }); 22 | 23 | it('should check that options is passed', function() { 24 | var _this = this; 25 | expect(function() { 26 | _this.auth0.dbConnection.signup(); 27 | }).to.throwException(function(e) { 28 | expect(e.message).to.be('options parameter is not valid'); 29 | }); 30 | }); 31 | 32 | it('should check that options.connection is passed', function() { 33 | var _this = this; 34 | expect(function() { 35 | _this.auth0.dbConnection.signup({}); 36 | }).to.throwException(function(e) { 37 | expect(e.message).to.be('connection option is required'); 38 | }); 39 | }); 40 | 41 | it('should check that options.email is passed', function() { 42 | var _this = this; 43 | expect(function() { 44 | _this.auth0.dbConnection.signup({ connection: 'bla' }); 45 | }).to.throwException(function(e) { 46 | expect(e.message).to.be('email option is required'); 47 | }); 48 | }); 49 | 50 | it('should check that options.password is passed', function() { 51 | var _this = this; 52 | expect(function() { 53 | _this.auth0.dbConnection.signup({ connection: 'bla', email: 'blabla' }); 54 | }).to.throwException(function(e) { 55 | expect(e.message).to.be('password option is required'); 56 | }); 57 | }); 58 | 59 | it('should check that cb is valid', function() { 60 | var _this = this; 61 | expect(function() { 62 | _this.auth0.dbConnection.signup({ 63 | connection: 'bla', 64 | email: 'blabla', 65 | password: '123456' 66 | }); 67 | }).to.throwException(function(e) { 68 | expect(e.message).to.be('cb parameter is not valid'); 69 | }); 70 | }); 71 | 72 | context('additional fields', function() { 73 | afterEach(function() { 74 | request.post.restore(); 75 | }); 76 | 77 | it('should send metadata on signup', function(done) { 78 | sinon.stub(request, 'post').callsFake(function(url) { 79 | expect(url).to.be('https://me.auth0.com/dbconnections/signup'); 80 | expect; 81 | return new RequestMock({ 82 | body: { 83 | client_id: '...', 84 | state: 'state-xyz', 85 | email: 'the email', 86 | password: 'the password', 87 | connection: 'the_connection', 88 | user_metadata: { 89 | firstName: 'Toon', 90 | lastName: 'De Coninck' 91 | } 92 | }, 93 | headers: { 94 | 'Content-Type': 'application/json', 95 | 'Auth0-Client': telemetryInfo 96 | }, 97 | cb: function(cb) { 98 | cb(null, { 99 | body: { 100 | email: 'the email' 101 | } 102 | }); 103 | } 104 | }); 105 | }); 106 | 107 | this.auth0.dbConnection.signup( 108 | { 109 | email: 'the email', 110 | password: 'the password', 111 | connection: 'the_connection', 112 | user_metadata: { 113 | firstName: 'Toon', 114 | lastName: 'De Coninck' 115 | } 116 | }, 117 | function(err, data) { 118 | expect(err).to.be(null); 119 | expect(data).to.eql({ 120 | email: 'the email' 121 | }); 122 | done(); 123 | } 124 | ); 125 | }); 126 | 127 | it('should send metadata on signup when using camel case', function(done) { 128 | sinon.stub(request, 'post').callsFake(function(url) { 129 | expect(url).to.be('https://me.auth0.com/dbconnections/signup'); 130 | expect; 131 | return new RequestMock({ 132 | body: { 133 | client_id: '...', 134 | state: 'state-xyz', 135 | email: 'the email', 136 | password: 'the password', 137 | connection: 'the_connection', 138 | user_metadata: { 139 | firstName: 'Toon', 140 | lastName: 'De Coninck', 141 | last_location: 'Mexico' 142 | } 143 | }, 144 | headers: { 145 | 'Content-Type': 'application/json', 146 | 'Auth0-Client': telemetryInfo 147 | }, 148 | cb: function(cb) { 149 | cb(null, { 150 | body: { 151 | email: 'the email' 152 | } 153 | }); 154 | } 155 | }); 156 | }); 157 | 158 | this.auth0.dbConnection.signup( 159 | { 160 | email: 'the email', 161 | password: 'the password', 162 | connection: 'the_connection', 163 | userMetadata: { 164 | firstName: 'Toon', 165 | lastName: 'De Coninck', 166 | last_location: 'Mexico' 167 | } 168 | }, 169 | function(err, data) { 170 | expect(err).to.be(null); 171 | expect(data).to.eql({ 172 | email: 'the email' 173 | }); 174 | done(); 175 | } 176 | ); 177 | }); 178 | }); 179 | }); 180 | 181 | context('change password options', function() { 182 | before(function() { 183 | this.auth0 = new Authentication({ 184 | domain: 'me.auth0.com', 185 | clientID: '...', 186 | redirectUri: 'http://page.com/callback', 187 | responseType: 'code', 188 | _sendTelemetry: false 189 | }); 190 | }); 191 | 192 | it('should check that options is passed', function() { 193 | var _this = this; 194 | expect(function() { 195 | _this.auth0.dbConnection.changePassword(); 196 | }).to.throwException(function(e) { 197 | expect(e.message).to.be('options parameter is not valid'); 198 | }); 199 | }); 200 | 201 | it('should check that options.connection is passed', function() { 202 | var _this = this; 203 | expect(function() { 204 | _this.auth0.dbConnection.changePassword({}); 205 | }).to.throwException(function(e) { 206 | expect(e.message).to.be('connection option is required'); 207 | }); 208 | }); 209 | 210 | it('should check that options.email is passed', function() { 211 | var _this = this; 212 | expect(function() { 213 | _this.auth0.dbConnection.changePassword({ connection: 'bla' }); 214 | }).to.throwException(function(e) { 215 | expect(e.message).to.be('email option is required'); 216 | }); 217 | }); 218 | 219 | it('should check that cb is valid', function() { 220 | var _this = this; 221 | expect(function() { 222 | _this.auth0.dbConnection.changePassword({ 223 | connection: 'bla', 224 | email: 'blabla', 225 | password: '123456' 226 | }); 227 | }).to.throwException(function(e) { 228 | expect(e.message).to.be('cb parameter is not valid'); 229 | }); 230 | }); 231 | }); 232 | }); 233 | -------------------------------------------------------------------------------- /test/helper/response-handler.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js'; 2 | 3 | import responseHandler from '../../src/helper/response-handler'; 4 | 5 | describe('helpers responseHandler', function() { 6 | it('should return default error', function(done) { 7 | responseHandler(function(err, data) { 8 | expect(data).to.be(undefined); 9 | expect(err).to.eql({ 10 | error: 'generic_error', 11 | errorDescription: 'Something went wrong' 12 | }); 13 | done(); 14 | })(null, null); 15 | }); 16 | 17 | it('should return normalized format 1', function(done) { 18 | var assert_err = {}; 19 | assert_err.response = {}; 20 | assert_err.response.statusCode = 400; 21 | assert_err.response.statusText = 'Bad request'; 22 | assert_err.response.body = { 23 | error: 'the_error_code', 24 | policy: 'the policy', 25 | error_description: 'The error description.', 26 | name: 'SomeName' 27 | }; 28 | 29 | responseHandler(function(err, data) { 30 | expect(data).to.be(undefined); 31 | expect(err).to.eql({ 32 | original: assert_err, 33 | statusCode: 400, 34 | statusText: 'Bad request', 35 | code: 'the_error_code', 36 | policy: 'the policy', 37 | description: 'The error description.', 38 | name: 'SomeName' 39 | }); 40 | done(); 41 | })(assert_err, null); 42 | }); 43 | 44 | it('should return normalized format 2', function(done) { 45 | var assert_err = {}; 46 | assert_err.response = {}; 47 | assert_err.response.body = { 48 | code: 'the_error_code', 49 | description: 'The error description.' 50 | }; 51 | 52 | responseHandler(function(err, data) { 53 | expect(data).to.be(undefined); 54 | expect(err).to.eql({ 55 | original: assert_err, 56 | code: 'the_error_code', 57 | description: 'The error description.' 58 | }); 59 | done(); 60 | })(assert_err, null); 61 | }); 62 | 63 | it('should return normalized format 3', function(done) { 64 | var assert_err = {}; 65 | assert_err.response = {}; 66 | assert_err.response.body = {}; 67 | 68 | responseHandler(function(err, data) { 69 | expect(data).to.be(undefined); 70 | expect(err).to.eql({ 71 | original: assert_err, 72 | code: null, 73 | description: null 74 | }); 75 | done(); 76 | })(assert_err, null); 77 | }); 78 | 79 | it('should return normalized format 4', function(done) { 80 | var assert_err = {}; 81 | assert_err.response = {}; 82 | assert_err.response.body = { 83 | error_code: 'the_error_code', 84 | error_description: 'The error description.' 85 | }; 86 | 87 | responseHandler(function(err, data) { 88 | expect(data).to.be(undefined); 89 | expect(err).to.eql({ 90 | original: assert_err, 91 | code: 'the_error_code', 92 | description: 'The error description.' 93 | }); 94 | done(); 95 | })(assert_err, null); 96 | }); 97 | 98 | it('should return normalized format 4', function(done) { 99 | var assert_err = {}; 100 | assert_err.err = {}; 101 | assert_err.err = { 102 | status: 'the_error_code', 103 | err: 'The error description.' 104 | }; 105 | 106 | responseHandler(function(err, data) { 107 | expect(data).to.be(undefined); 108 | expect(err).to.eql({ 109 | original: assert_err, 110 | code: 'the_error_code', 111 | description: 'The error description.' 112 | }); 113 | done(); 114 | })(assert_err, null); 115 | }); 116 | 117 | it('should return normalized format 5 (error comes from data)', function(done) { 118 | var assert_err = { 119 | error: 'the_error_code', 120 | errorDescription: 'The error description.' 121 | }; 122 | 123 | responseHandler(function(err, data) { 124 | expect(data).to.be(undefined); 125 | expect(err).to.eql({ 126 | original: assert_err, 127 | code: 'the_error_code', 128 | description: 'The error description.' 129 | }); 130 | done(); 131 | })(null, assert_err); 132 | }); 133 | 134 | it('should return normalized format 6', function(done) { 135 | var assert_err = {}; 136 | assert_err.response = {}; 137 | assert_err.response.body = { 138 | code: 'the_error_code', 139 | error: 'The error description.' 140 | }; 141 | 142 | responseHandler(function(err, data) { 143 | expect(data).to.be(undefined); 144 | expect(err).to.eql({ 145 | original: assert_err, 146 | code: 'the_error_code', 147 | description: 'The error description.' 148 | }); 149 | done(); 150 | })(assert_err, null); 151 | }); 152 | 153 | it('should return normalized error codes and details', function(done) { 154 | var assert_err = {}; 155 | assert_err.response = {}; 156 | assert_err.response.body = { 157 | code: 'blocked_user', 158 | error: 'Blocked user.', 159 | error_codes: ['reason-1', 'reason-2'], 160 | error_details: { 161 | 'reason-1': { 162 | timestamp: 123 163 | }, 164 | 'reason-2': { 165 | timestamp: 456 166 | } 167 | } 168 | }; 169 | 170 | responseHandler(function(err, data) { 171 | expect(data).to.be(undefined); 172 | 173 | expect(err).to.eql({ 174 | original: assert_err, 175 | code: 'blocked_user', 176 | description: 'Blocked user.', 177 | errorDetails: { 178 | codes: ['reason-1', 'reason-2'], 179 | details: { 180 | 'reason-1': { 181 | timestamp: 123 182 | }, 183 | 'reason-2': { 184 | timestamp: 456 185 | } 186 | } 187 | } 188 | }); 189 | 190 | done(); 191 | })(assert_err, null); 192 | }); 193 | 194 | it('should return the data', function(done) { 195 | var assert_data = { 196 | body: { 197 | attr1: 'attribute 1', 198 | attr2: 'attribute 2' 199 | } 200 | }; 201 | 202 | responseHandler(function(err, data) { 203 | expect(err).to.be(null); 204 | expect(data).to.eql({ 205 | attr1: 'attribute 1', 206 | attr2: 'attribute 2' 207 | }); 208 | done(); 209 | })(null, assert_data); 210 | }); 211 | 212 | it('should return the data 2', function(done) { 213 | var assert_data = { 214 | text: 'The response message', 215 | type: 'text/html' 216 | }; 217 | 218 | responseHandler(function(err, data) { 219 | expect(err).to.be(null); 220 | expect(data).to.eql('The response message'); 221 | done(); 222 | })(null, assert_data); 223 | }); 224 | 225 | it('should return the data respecting the `keepOriginalCasing` option', function(done) { 226 | var assert_data = { 227 | body: { 228 | the_attr: 'attr' 229 | } 230 | }; 231 | 232 | responseHandler( 233 | function(err, data) { 234 | expect(err).to.be(null); 235 | expect(data).to.eql({ 236 | the_attr: 'attr', 237 | theAttr: 'attr' 238 | }); 239 | done(); 240 | }, 241 | { keepOriginalCasing: true } 242 | )(null, assert_data); 243 | }); 244 | 245 | it('should mask the password object in the original response object', function(done) { 246 | var assert_err = { 247 | code: 'the_error_code', 248 | error: 'The error description.', 249 | response: { 250 | req: { 251 | _data: { 252 | realm: 'realm', 253 | client_id: 'client_id', 254 | username: 'username', 255 | password: 'this is a password' 256 | } 257 | } 258 | } 259 | }; 260 | 261 | responseHandler(function(err, data) { 262 | expect(data).to.be(undefined); 263 | 264 | expect(err).to.eql({ 265 | original: { 266 | code: 'the_error_code', 267 | error: 'The error description.', 268 | response: { 269 | req: { 270 | _data: { 271 | realm: 'realm', 272 | client_id: 'client_id', 273 | username: 'username', 274 | password: '*****' 275 | } 276 | } 277 | } 278 | }, 279 | code: 'the_error_code', 280 | description: 'The error description.' 281 | }); 282 | 283 | done(); 284 | })(assert_err, null); 285 | }); 286 | 287 | it('should mask the password object in the data object', function(done) { 288 | var assert_err = { 289 | code: 'the_error_code', 290 | error: 'The error description.', 291 | response: { 292 | req: { 293 | _data: { 294 | realm: 'realm', 295 | client_id: 'client_id', 296 | username: 'username', 297 | password: 'this is a password' 298 | } 299 | } 300 | } 301 | }; 302 | 303 | responseHandler(function(err, data) { 304 | expect(data).to.be(undefined); 305 | 306 | expect(err).to.eql({ 307 | original: { 308 | code: 'the_error_code', 309 | error: 'The error description.', 310 | response: { 311 | req: { 312 | _data: { 313 | realm: 'realm', 314 | client_id: 'client_id', 315 | username: 'username', 316 | password: '*****' 317 | } 318 | } 319 | } 320 | }, 321 | code: 'the_error_code', 322 | description: 'The error description.' 323 | }); 324 | 325 | done(); 326 | })(assert_err, null); 327 | }); 328 | }); 329 | -------------------------------------------------------------------------------- /test/web-auth/captcha.test.js: -------------------------------------------------------------------------------- 1 | import { JSDOM } from 'jsdom'; 2 | import url from 'url'; 3 | import sinon from 'sinon'; 4 | import captcha from '../../src/web-auth/captcha'; 5 | import expect from 'expect.js'; 6 | 7 | describe('captcha rendering', function () { 8 | describe('when challenge is not required', function () { 9 | const { window } = new JSDOM('
'); 10 | const element = window.document.querySelector('.captcha'); 11 | let c; 12 | 13 | beforeEach(function () { 14 | const mockClient = { 15 | getChallenge: (cb) => cb(null, { required: false }) 16 | } 17 | c = captcha.render(mockClient, element); 18 | }); 19 | 20 | it('should hide the element', function () { 21 | expect(element.style.display).to.equal('none'); 22 | }); 23 | 24 | it('should clean the innerHTML', function () { 25 | expect(element.innerHTML).to.equal(''); 26 | }); 27 | 28 | it('should return undefined when calling getValue', function () { 29 | expect(c.getValue()).to.be.equal(undefined); 30 | }); 31 | }); 32 | 33 | describe('when challenge request fail', function () { 34 | const { window } = new JSDOM('
'); 35 | const element = window.document.querySelector('.captcha'); 36 | const callbackStub = sinon.stub(); 37 | 38 | beforeEach(function () { 39 | const mockClient = { 40 | getChallenge: (cb) => cb(new Error('network error')) 41 | }; 42 | captcha.render(mockClient, element, {}, callbackStub); 43 | }); 44 | 45 | it('should show the element', function () { 46 | expect(element.style.display).to.equal(''); 47 | }); 48 | 49 | it('should show an error', function () { 50 | expect(element.querySelector('div.error').innerHTML) 51 | .to.equal('Error getting the bot detection challenge. Please contact the system administrator.') 52 | }); 53 | 54 | it('should call the optional callback with the error', function () { 55 | expect(callbackStub.called).to.equal(true) 56 | expect(callbackStub.args[0][0].message).to.equal('network error') 57 | }); 58 | }); 59 | 60 | describe('when challenge is required and the provider is auth0', function () { 61 | const { window } = new JSDOM('