├── .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('');
62 | const element = window.document.querySelector('.captcha');
63 | const callbackStub = sinon.stub();
64 |
65 | const challenges = [{
66 | required: true,
67 | provider: 'auth0',
68 | image: 'img+svg///image1'
69 | }, {
70 | required: true,
71 | provider: 'auth0',
72 | image: 'img+svg///image2'
73 | }];
74 |
75 | let c;
76 |
77 | beforeEach(function () {
78 | const mockClient = {
79 | challengeIndex: 0,
80 | getChallenge(cb) {
81 | cb(null, challenges[this.challengeIndex++]);
82 | }
83 | }
84 | c = captcha.render(mockClient, element, {}, callbackStub);
85 | });
86 |
87 | it('should call the optional callback', function () {
88 | expect(callbackStub.called).to.be.ok();
89 | expect(callbackStub.args[0]).to.be.empty();
90 | });
91 |
92 | it('should show the element', function () {
93 | expect(element.style.display).to.equal('');
94 | });
95 |
96 | it('should set the image tag', function () {
97 | const imgEl = element.querySelector('img');
98 | expect(imgEl.src).to.equal(challenges[0].image);
99 | });
100 |
101 | it('should contain an input tag with name captcha', function () {
102 | const inputEl = element.querySelector('input[name="captcha"]');
103 | expect(inputEl).to.be.ok();
104 | expect(inputEl.type).to.contain('text');
105 | });
106 |
107 | it('should return the user input when calling getValue()', function () {
108 | const inputEl = element.querySelector('input[name="captcha"]');
109 | inputEl.value = 'foobar';
110 | expect(c.getValue()).to.equal('foobar');
111 | });
112 |
113 | it('should load a new image when clicking the reload button', function () {
114 | const btn = element.querySelector('button.captcha-reload');
115 | btn.click();
116 | const imgEl = element.querySelector('img');
117 | expect(imgEl.src).to.equal(challenges[1].image);
118 | });
119 |
120 | it('should load a new image when calling the reload method', function () {
121 | c.reload();
122 | const imgEl = element.querySelector('img');
123 | expect(imgEl.src).to.equal(challenges[1].image);
124 | });
125 | });
126 |
127 | describe('when challenge is required, the provider is auth0 and we use a custom template', function () {
128 | const button = {
129 | listeners: {},
130 | addEventListener(event, handler) {
131 | this.listeners[event] = handler;
132 | }
133 | };
134 |
135 | const element = {
136 | style: {},
137 | display: 'none',
138 | querySelector(selector) {
139 | switch (selector) {
140 | case '.captcha-reload':
141 | return button;
142 | }
143 | }
144 | };
145 |
146 | const challenges = [{
147 | required: true,
148 | provider: 'auth0',
149 | image: 'img+svg///image1'
150 | }, {
151 | required: true,
152 | provider: 'auth0',
153 | image: 'img+svg///image2'
154 | }];
155 |
156 | beforeEach(function () {
157 | const mockClient = {
158 | challengeIndex: 0,
159 | getChallenge(cb) {
160 | cb(null, challenges[this.challengeIndex++]);
161 | }
162 | }
163 | captcha.render(mockClient, element, {
164 | templates: {
165 | auth0: challenge => `custom template: ${challenge.image}`
166 | }
167 | });
168 | });
169 |
170 | it('should show the element', function () {
171 | expect(element.style.display).to.equal('');
172 | });
173 |
174 | it('should set the image tag', function () {
175 | expect(element.innerHTML).to.equal(`custom template: ${challenges[0].image}`);
176 | });
177 | });
178 |
179 | describe('when challenge is required and provider is recaptcha', function () {
180 |
181 | const challenge = {
182 | required: true,
183 | provider: 'recaptcha_v2',
184 | siteKey: 'blabla sitekey'
185 | };
186 |
187 | let c, recaptchaScript, scriptOnLoadCallback, element;
188 |
189 | beforeEach(() => {
190 | const { window } = new JSDOM('');
191 | element = window.document.querySelector('.captcha');
192 | global.window = window;
193 | const mockClient = {
194 | getChallenge(cb) {
195 | cb(null, challenge);
196 | }
197 | };
198 | c = captcha.render(mockClient, element);
199 | recaptchaScript = [...window.document.querySelectorAll('script')].find(s => s.src.match('google\.com'));
200 | scriptOnLoadCallback = window[url.parse(recaptchaScript.src, true).query.onload];
201 | });
202 |
203 | afterEach(function () {
204 | delete global.window;
205 | });
206 |
207 | it('should inject the recaptcha script', function () {
208 | expect(recaptchaScript.async).to.be.ok();
209 | const scriptUrl = url.parse(recaptchaScript.src, true);
210 | expect(scriptUrl.hostname).to.equal('www.google.com');
211 | expect(scriptUrl.pathname).to.equal('/recaptcha/api.js');
212 | expect(scriptUrl.query.hl).to.equal('en');
213 | expect(scriptUrl.query).to.have.property('onload');
214 | });
215 |
216 | describe('after captcha is loaded', function () {
217 | let renderOptions;
218 | let renderElement;
219 | let reseted = false;
220 |
221 | beforeEach(function () {
222 | reseted = false;
223 | window.grecaptcha = {
224 | render(element, options) {
225 | renderElement = element;
226 | renderOptions = options;
227 | return 0;
228 | },
229 | reset() { reseted = true; }
230 | };
231 | scriptOnLoadCallback();
232 | });
233 |
234 | it('should render with the site key', function () {
235 | expect(renderOptions.sitekey).to.equal(challenge.siteKey);
236 | });
237 |
238 | it('should set the value on the input when the user completes the captcha', function () {
239 | const mockToken = 'token xxxxxx';
240 | const input = element.querySelector('input[name="captcha"]');
241 | renderOptions.callback(mockToken)
242 | expect(input.value).to.equal(mockToken);
243 | });
244 |
245 |
246 | it('should return the value when calling getValue()', function () {
247 | const mockToken = 'token xxxxxx';
248 | renderOptions.callback(mockToken)
249 | expect(c.getValue()).to.equal(mockToken);
250 | });
251 |
252 | it('should clean the value when the token expires', function () {
253 | const input = element.querySelector('input[name="captcha"]');
254 | input.value = 'expired token';
255 | renderOptions['expired-callback']()
256 | expect(input.value).to.equal('');
257 | });
258 |
259 | it('should clean the value when there is an error', function () {
260 | const input = element.querySelector('input[name="captcha"]');
261 | input.value = 'expired token';
262 | renderOptions['error-callback']()
263 | expect(input.value).to.equal('');
264 | });
265 |
266 | it('should clean the value and reset when reloading', function () {
267 | const input = element.querySelector('input[name="captcha"]');
268 | input.value = 'old token';
269 | c.reload();
270 | expect(input.value).to.equal('');
271 | expect(reseted).to.be.ok();
272 | });
273 |
274 | });
275 | });
276 | });
277 |
--------------------------------------------------------------------------------