486 | // MIT License
487 |
488 | var keys = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'hostname', 'port', 'resource', 'relative', 'pathname', 'directory', 'file', 'query', 'fragment'];
489 |
490 | var uriRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?(((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?)(?:#(.*))?)/;
491 | var uriByNumber = uriRegex.exec(input);
492 | var uri = {};
493 |
494 | var i = 15;
495 | while (i--) {
496 | uri[keys[i]] = uriByNumber[i] || '';
497 | }
498 |
499 | if (uri.port === null ||
500 | uri.port === '') {
501 |
502 | uri.port = (uri.protocol.toLowerCase() === 'http' ? '80' : (uri.protocol.toLowerCase() === 'https' ? '443' : ''));
503 | }
504 |
505 | return uri;
506 | }
507 | };
508 |
509 |
510 | return hawk;
511 | });
512 |
--------------------------------------------------------------------------------
/client/lib/hawkCredentials.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 | define(['sjcl', './hkdf'], function (sjcl, hkdf) {
5 | 'use strict';
6 |
7 | var PREFIX_NAME = 'identity.mozilla.com/picl/v1/';
8 | var bitSlice = sjcl.bitArray.bitSlice;
9 | var salt = sjcl.codec.hex.toBits('');
10 |
11 | /**
12 | * @class hawkCredentials
13 | * @method deriveHawkCredentials
14 | * @param {String} tokenHex
15 | * @param {String} context
16 | * @param {int} size
17 | * @returns {Promise}
18 | */
19 | function deriveHawkCredentials(tokenHex, context, size) {
20 | var token = sjcl.codec.hex.toBits(tokenHex);
21 | var info = sjcl.codec.utf8String.toBits(PREFIX_NAME + context);
22 |
23 | return hkdf(token, info, salt, size || 3 * 32)
24 | .then(function(out) {
25 | var authKey = bitSlice(out, 8 * 32, 8 * 64);
26 | var bundleKey = bitSlice(out, 8 * 64);
27 |
28 | return {
29 | algorithm: 'sha256',
30 | id: sjcl.codec.hex.fromBits(bitSlice(out, 0, 8 * 32)),
31 | key: authKey,
32 | bundleKey: bundleKey
33 | };
34 | });
35 | }
36 |
37 | return deriveHawkCredentials;
38 | });
39 |
--------------------------------------------------------------------------------
/client/lib/hkdf.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 | define(['sjcl'], function (sjcl) {
5 | 'use strict';
6 |
7 | /**
8 | * hkdf - The HMAC-based Key Derivation Function
9 | * based on https://github.com/mozilla/node-hkdf
10 | *
11 | * @class hkdf
12 | * @param {bitArray} ikm Initial keying material
13 | * @param {bitArray} info Key derivation data
14 | * @param {bitArray} salt Salt
15 | * @param {integer} length Length of the derived key in bytes
16 | * @return promise object- It will resolve with `output` data
17 | */
18 | function hkdf(ikm, info, salt, length) {
19 |
20 | var mac = new sjcl.misc.hmac(salt, sjcl.hash.sha256);
21 | mac.update(ikm);
22 |
23 | // compute the PRK
24 | var prk = mac.digest();
25 |
26 | // hash length is 32 because only sjcl.hash.sha256 is used at this moment
27 | var hashLength = 32;
28 | var num_blocks = Math.ceil(length / hashLength);
29 | var prev = sjcl.codec.hex.toBits('');
30 | var output = '';
31 |
32 | for (var i = 0; i < num_blocks; i++) {
33 | var hmac = new sjcl.misc.hmac(prk, sjcl.hash.sha256);
34 |
35 | var input = sjcl.bitArray.concat(
36 | sjcl.bitArray.concat(prev, info),
37 | sjcl.codec.utf8String.toBits((String.fromCharCode(i + 1)))
38 | );
39 |
40 | hmac.update(input);
41 |
42 | prev = hmac.digest();
43 | output += sjcl.codec.hex.fromBits(prev);
44 | }
45 |
46 | var truncated = sjcl.bitArray.clamp(sjcl.codec.hex.toBits(output), length * 8);
47 |
48 | return Promise.resolve(truncated);
49 | }
50 |
51 | return hkdf;
52 |
53 | });
54 |
--------------------------------------------------------------------------------
/client/lib/metricsContext.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | // This module does the handling for the metrics context
6 | // activity event metadata.
7 |
8 | define([], function () {
9 | 'use strict';
10 |
11 | return {
12 | marshall: function (data) {
13 | return {
14 | deviceId: data.deviceId,
15 | entrypoint: data.entrypoint,
16 | flowId: data.flowId,
17 | flowBeginTime: data.flowBeginTime,
18 | utmCampaign: data.utmCampaign,
19 | utmContent: data.utmContent,
20 | utmMedium: data.utmMedium,
21 | utmSource: data.utmSource,
22 | utmTerm: data.utmTerm
23 | };
24 | }
25 | };
26 | });
27 |
--------------------------------------------------------------------------------
/client/lib/pbkdf2.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 | define(['sjcl'], function (sjcl, P) {
5 | 'use strict';
6 |
7 | /**
8 | * @class pbkdf2
9 | * @constructor
10 | */
11 | var pbkdf2 = {
12 | /**
13 | * @method derive
14 | * @param {bitArray} input The password hex buffer.
15 | * @param {bitArray} salt The salt string buffer.
16 | * @return {int} iterations the derived key bit array.
17 | */
18 | derive: function(input, salt, iterations, len) {
19 | var result = sjcl.misc.pbkdf2(input, salt, iterations, len, sjcl.misc.hmac);
20 | return Promise.resolve(result);
21 | }
22 | };
23 |
24 | return pbkdf2;
25 |
26 | });
27 |
--------------------------------------------------------------------------------
/client/lib/request.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 | define(['./hawk', './errors'], function (hawk, ERRORS) {
5 | 'use strict';
6 | /* global XMLHttpRequest */
7 |
8 | /**
9 | * @class Request
10 | * @constructor
11 | * @param {String} baseUri Base URI
12 | * @param {Object} xhr XMLHttpRequest constructor
13 | * @param {Object} [options={}] Options
14 | * @param {Number} [options.localtimeOffsetMsec]
15 | * Local time offset with the remote auth server's clock
16 | */
17 | function Request (baseUri, xhr, options) {
18 | if (!options) {
19 | options = {};
20 | }
21 | this.baseUri = baseUri;
22 | this._localtimeOffsetMsec = options.localtimeOffsetMsec;
23 | this.xhr = xhr || XMLHttpRequest;
24 | this.timeout = options.timeout || 30 * 1000;
25 | }
26 |
27 | /**
28 | * @method send
29 | * @param {String} path Request path
30 | * @param {String} method HTTP Method
31 | * @param {Object} credentials HAWK Headers
32 | * @param {Object} jsonPayload JSON Payload
33 | * @param {Object} [options={}] Options
34 | * @param {String} [options.retrying]
35 | * Flag indicating if the request is a retry
36 | * @param {Array} [options.headers]
37 | * A set of extra headers to add to the request
38 | * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
39 | */
40 | Request.prototype.send = function request(path, method, credentials, jsonPayload, options) {
41 | /*eslint complexity: [2, 8] */
42 | var xhr = new this.xhr();
43 | var uri = this.baseUri + path;
44 | var payload = null;
45 | var self = this;
46 | options = options || {};
47 |
48 | if (jsonPayload) {
49 | payload = JSON.stringify(jsonPayload);
50 | }
51 |
52 | try {
53 | xhr.open(method, uri);
54 | } catch (e) {
55 | return Promise.reject({ error: 'Unknown error', message: e.toString(), errno: 999 });
56 | }
57 |
58 | return new Promise(function (resolve, reject) {
59 | xhr.timeout = self.timeout;
60 |
61 | xhr.onreadystatechange = function() {
62 | if (xhr.readyState === 4) {
63 | var result = xhr.responseText;
64 | try {
65 | result = JSON.parse(xhr.responseText);
66 | } catch (e) { }
67 |
68 | if (result.errno) {
69 | // Try to recover from a timeskew error and not already tried
70 | if (result.errno === ERRORS.INVALID_TIMESTAMP && !options.retrying) {
71 | var serverTime = result.serverTime;
72 | self._localtimeOffsetMsec = (serverTime * 1000) - new Date().getTime();
73 |
74 | // add to options that the request is retrying
75 | options.retrying = true;
76 |
77 | return self.send(path, method, credentials, jsonPayload, options)
78 | .then(resolve, reject);
79 |
80 | } else {
81 | return reject(result);
82 | }
83 | }
84 |
85 | if (typeof xhr.status === 'undefined' || xhr.status !== 200) {
86 | if (result.length === 0) {
87 | return reject({ error: 'Timeout error', errno: 999 });
88 | } else {
89 | return reject({ error: 'Unknown error', message: result, errno: 999, code: xhr.status });
90 | }
91 | }
92 |
93 | resolve(result);
94 | }
95 | };
96 |
97 | // calculate Hawk header if credentials are supplied
98 | if (credentials) {
99 | var hawkHeader = hawk.client.header(uri, method, {
100 | credentials: credentials,
101 | payload: payload,
102 | contentType: 'application/json',
103 | localtimeOffsetMsec: self._localtimeOffsetMsec || 0
104 | });
105 | xhr.setRequestHeader('authorization', hawkHeader.field);
106 | }
107 |
108 | xhr.setRequestHeader('Content-Type', 'application/json');
109 |
110 | if (options && options.headers) {
111 | // set extra headers for this request
112 | for (var header in options.headers) {
113 | xhr.setRequestHeader(header, options.headers[header]);
114 | }
115 | }
116 |
117 | xhr.send(payload);
118 | });
119 | };
120 |
121 | return Request;
122 |
123 | });
124 |
--------------------------------------------------------------------------------
/node/amd-loader.js:
--------------------------------------------------------------------------------
1 | // This is intended to be the simplest possible AMD shim that works
2 | // It is not intended a general AMD loader just enough to load this package
3 | // This relies on the fact that Node.js require() is synchronous.
4 | // It attempts to let the node.js module loader do as much work as possible
5 | // Also provides a way to replace modules with api compatible counterparts
6 |
7 | var path = require('path');
8 |
9 | module.exports = function amdload(absoluteFilename, map) {
10 | // Store this so we can put it back later.
11 | var oldDefine = global.define;
12 |
13 | map = map || {};
14 | var loaded = {}, dirs = [], exported;
15 |
16 | /**
17 | * These two functions operate as a pair
18 | */
19 | var amdrequire = function amdrequire(filepath) {
20 | // Return real node modules if we have them mapped
21 | if (filepath in map) {
22 | return require(map[filepath]);
23 | }
24 |
25 | // Resolve target against 'current working directory'
26 | var fullpath = path.resolve(dirs[0], filepath);
27 |
28 | if (!loaded[fullpath]) {
29 | // Put current operation on stack
30 | dirs.unshift(path.dirname(fullpath));
31 |
32 | // setup fake define and delegate to real require()
33 | global.define = define;
34 |
35 | require(fullpath);
36 |
37 | // Capture and store exported module
38 | loaded[fullpath] = exported;
39 | exported = null;
40 |
41 | // Restore previous define() state
42 | if (oldDefine) {
43 | global.define = define;
44 | } else {
45 | delete global.define;
46 | }
47 |
48 | // return to cwd from before define
49 | dirs.shift();
50 | }
51 |
52 | // return value captured by define()
53 | return loaded[fullpath];
54 | };
55 | var define = function define(deps, factory) {
56 | // Load all dependencies
57 | var modules = deps.map(amdrequire);
58 | // Capture the exported value
59 | exported = factory.apply(null, modules);
60 | };
61 | define.amd = true;
62 |
63 | return amdrequire(absoluteFilename);
64 | };
65 |
--------------------------------------------------------------------------------
/node/index.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 |
3 | var amd = require('./amd-loader');
4 |
5 | var map = {
6 | 'es6-promise': 'es6-promise',
7 | sjcl: 'sjcl'
8 | };
9 |
10 | var FxAccountClient = amd(__dirname + '/../client/FxAccountClient.js', map);
11 |
12 | function NodeFxAccountClient(uri, config) {
13 | if (!(this instanceof FxAccountClient)) {
14 | return new NodeFxAccountClient(uri, config);
15 | }
16 |
17 | if (typeof uri !== 'string') {
18 | config = uri || {};
19 | uri = config.uri;
20 | }
21 | if (typeof config === 'undefined') {
22 | config = {};
23 | }
24 |
25 | if (!config.xhr) {
26 | config.xhr = require('xhr2');
27 | }
28 |
29 | FxAccountClient.call(this, uri, config);
30 | }
31 |
32 | NodeFxAccountClient.VERSION = FxAccountClient.VERSION;
33 |
34 | module.exports = NodeFxAccountClient;
35 | util.inherits(NodeFxAccountClient, FxAccountClient);
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fxa-js-client",
3 | "version": "1.0.8",
4 | "description": "Web client that talks to the Firefox Accounts API server",
5 | "author": "Mozilla",
6 | "license": "MPL-2.0",
7 | "scripts": {
8 | "start": "grunt",
9 | "test": "grunt test",
10 | "test-local": "intern-client config=tests/intern auth_server=LOCAL",
11 | "setup": "npm install && grunt sjcl",
12 | "contributors": "git shortlog -s | cut -c8- | sort -f > CONTRIBUTORS.md"
13 | },
14 | "main": "node/index.js",
15 | "files": [
16 | "node/",
17 | "client/",
18 | "LICENSE"
19 | ],
20 | "readmeFilename": "README.md",
21 | "homepage": "https://github.com/mozilla/fxa-js-client",
22 | "engines": {
23 | "node": ">=8"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/mozilla/fxa-js-client.git"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/mozilla/fxa-js-client/issues"
31 | },
32 | "dependencies": {
33 | "es6-promise": "4.1.1",
34 | "sjcl": "git://github.com/bitwiseshiftleft/sjcl.git#a03ea8e",
35 | "xhr2": "0.0.7"
36 | },
37 | "devDependencies": {
38 | "eslint-config-fxa": "1.8.1",
39 | "grunt": "0.4.2",
40 | "grunt-build-control": "git://github.com/robwierzbowski/grunt-build-control#274952",
41 | "grunt-bump": "0.3.1",
42 | "grunt-bytesize": "0.1.1",
43 | "grunt-cli": "0.1.13",
44 | "grunt-contrib-clean": "0.6.0",
45 | "grunt-contrib-watch": "0.6.1",
46 | "grunt-contrib-yuidoc": "0.9.0",
47 | "grunt-conventional-changelog": "3.0.0",
48 | "grunt-copyright": "0.2.0",
49 | "grunt-eslint": "16.0.0",
50 | "grunt-open": "0.2.2",
51 | "grunt-webpack": "3.0.2",
52 | "http-proxy": "1.11.1",
53 | "intern-geezer": "2.2.3",
54 | "jscs-jsdoc": "1.1.0",
55 | "load-grunt-tasks": "3.2.0",
56 | "otplib": "7.1.0",
57 | "webpack": "3.10.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tasks/buildcontrol.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('buildcontrol', {
9 | options: {
10 | commit: true,
11 | push: true,
12 | remote: 'git@github.com:mozilla/fxa-js-client.git'
13 | },
14 | release: {
15 | options: {
16 | branch: 'release',
17 | dir: 'build',
18 | tag: '<%= pkg.version %>'
19 | }
20 | },
21 | docs: {
22 | options: {
23 | branch: 'gh-pages',
24 | dir: 'docs',
25 | tag: 'docs-<%= pkg.version %>'
26 | }
27 | }
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/tasks/bump.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('bump', {
9 | options: {
10 | files: ['package.json'],
11 | updateConfigs: ['pkg'],
12 | push: true,
13 | pushTo: 'git@github.com:mozilla/fxa-js-client.git update-master',
14 | commitMessage: 'source-%VERSION%',
15 | tagName: 'source-%VERSION%',
16 | // commit all modified files
17 | commitFiles: ['-a'],
18 | commit: true
19 | }
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/tasks/bytesize.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('bytesize', {
9 | all: {
10 | src: ['build/fxa-client.js', 'build/fxa-client.min.js']
11 | }
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/tasks/changelog.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('conventionalChangelog', {
9 | options: {
10 | from: 'source-<%= pkgReadOnly.version %>'
11 | }
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/tasks/clean.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('clean', {
9 | build: ['build']
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/tasks/copyright.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('copyright', {
9 | app: {
10 | options: {
11 | pattern: /This Source Code Form is subject to the terms of the Mozilla Public/
12 | },
13 | src: [
14 | '**/*.js',
15 | '!tests/addons/sinon.js',
16 | '!build/**',
17 | '!node_modules/**',
18 | '!docs/**'
19 | ]
20 | }
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/tasks/eslint.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | grunt.config('eslint', {
7 | app: {
8 | eslintrc: '.eslintrc',
9 | src: ['Gruntfile.js', 'tasks/*.js', 'config/**/*.js', 'node/**/*.js']
10 | },
11 | client: {
12 | options: {eslintrc: 'client/.eslintrc'},
13 | src: ['client/*.js', 'client/lib/**/*']
14 | },
15 | test: {
16 | options: {
17 | eslintrc: 'tests/.eslintrc'
18 | },
19 | src: [
20 | 'tests/**/*.js',
21 | '!tests/addons/sinon.js'
22 | ]
23 | }
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/tasks/intern.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('intern', {
9 | node: {
10 | options: {
11 | config: 'tests/intern',
12 | reporters: ['console'],
13 | suites: ['tests/all']
14 | }
15 | },
16 | native_node: {
17 | options: {
18 | config: 'tests/intern_native_node',
19 | reporters: ['console'],
20 | suites: ['tests/all']
21 | }
22 | },
23 | // local browser
24 | browser: {
25 | options: {
26 | runType: 'runner',
27 | config: 'tests/intern_browser',
28 | suites: ['tests/all']
29 | }
30 | },
31 | sauce: {
32 | options: {
33 | runType: 'runner',
34 | config: 'tests/intern_sauce',
35 | suites: ['tests/all'],
36 | sauceUsername: 'fxa-client',
37 | sauceAccessKey: '863203af-38fd-4f1d-9332-adc8f60f157b'
38 | }
39 | }
40 | });
41 | };
42 |
43 |
44 |
--------------------------------------------------------------------------------
/tasks/open.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('open', {
9 | dev: {
10 | path: 'docs/index.html'
11 | }
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/tasks/sjcl.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | var fs = require('fs');
7 | var exec = require('child_process').exec;
8 |
9 | grunt.registerTask('sjcl', 'Build the SJCL library', function () {
10 | var done = this.async();
11 | var configSJCL = './configure --without-random --without-ocb2 --without-gcm --without-ccm && make';
12 | var src = 'core_closure.js';
13 | var dist = 'sjcl.js';
14 |
15 | process.chdir('node_modules/sjcl/');
16 |
17 | exec(configSJCL,
18 | function (error, stdout, stderr) {
19 | grunt.log.write(stdout);
20 | if (stderr) {
21 | grunt.log.warn(stderr);
22 | }
23 |
24 | var sjclBower = fs.readFileSync(src);
25 | var sjclAmd = 'define([], function () {' + sjclBower + ' return sjcl; });';
26 | fs.writeFileSync(dist, sjclAmd);
27 |
28 | process.chdir('../..');
29 | done();
30 | });
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/tasks/watch.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('watch', {
9 | dev: {
10 | options: {
11 | atBegin: true
12 | },
13 | files: ['Gruntfile.js', 'client/**/*.js', 'tests/**/*.js'],
14 | tasks: ['build', 'intern:node']
15 | },
16 | debug: {
17 | options: {
18 | atBegin: true
19 | },
20 | files: ['Gruntfile.js', 'client/**/*.js', 'tests/**/*.js'],
21 | tasks: ['webpack:app', 'intern:node']
22 | }
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/tasks/webpack.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | const webpackConfig = require('../webpack.config');
7 |
8 | grunt.config('webpack', {
9 | options: {
10 | stats: true
11 | },
12 | app: webpackConfig,
13 | watch: Object.assign({ watch: true }, webpackConfig)
14 | });
15 | };
16 |
17 |
--------------------------------------------------------------------------------
/tasks/yuidoc.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | module.exports = function (grunt) {
6 | 'use strict';
7 |
8 | grunt.config('yuidoc', {
9 | compile: {
10 | name: '<%= pkg.name %>',
11 | description: '<%= pkg.description %>',
12 | version: '<%= pkg.version %>',
13 | options: {
14 | paths: 'client/',
15 | outdir: 'docs/'
16 | }
17 | }
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "rules": {
6 | "no-with": 0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/addons/accountHelper.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'tests/mocks/request'
7 | ], function (RequestMocks) {
8 | 'use strict';
9 |
10 | function AccountHelper(client, mail, respond) {
11 | this.client = client;
12 | this.mail = mail;
13 | this.respond = respond;
14 | }
15 | AccountHelper.prototype.newVerifiedAccount = function (options) {
16 | var username = 'testHelp1';
17 | var domain = '@restmail.net';
18 |
19 | if (options && options.domain) {
20 | domain = options.domain;
21 | }
22 |
23 | if (options && options.username) {
24 | username = options.username;
25 | }
26 |
27 | var user = username + new Date().getTime();
28 | var email = user + domain;
29 | var password = 'iliketurtles';
30 | var respond = this.respond;
31 | var mail = this.mail;
32 | var client = this.client;
33 | var uid;
34 | var result = {
35 | input: {
36 | user: user,
37 | email: email,
38 | password: password
39 | }
40 | };
41 |
42 | return respond(client.signUp(email, password), RequestMocks.signUp)
43 | .then(function (res) {
44 | uid = res.uid;
45 | result.signUp = res;
46 |
47 | return respond(mail.wait(user), RequestMocks.mail);
48 | })
49 | .then(function (emails) {
50 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
51 |
52 | return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
53 | })
54 |
55 | .then(function (res) {
56 | result.verifyCode = res;
57 |
58 | return respond(client.signIn(email, password, {keys: true}), RequestMocks.signInWithKeys);
59 | })
60 | .then(function(res) {
61 | result.signIn = res;
62 |
63 | return result;
64 | });
65 | };
66 |
67 | AccountHelper.prototype.newUnverifiedAccount = function (options) {
68 | var username = 'testHelp2';
69 | var domain = '@restmail.net';
70 |
71 | if (options && options.domain) {
72 | domain = options.domain;
73 | }
74 |
75 | if (options && options.username) {
76 | username = options.username;
77 | }
78 |
79 | var user = username + new Date().getTime();
80 | var email = user + domain;
81 | var password = 'iliketurtles';
82 | var respond = this.respond;
83 | var client = this.client;
84 | var result = {
85 | input: {
86 | user: user,
87 | email: email,
88 | password: password
89 | }
90 | };
91 |
92 | return respond(client.signUp(email, password), RequestMocks.signUp)
93 | .then(function (res) {
94 | result.signUp = res;
95 |
96 | return respond(client.signIn(email, password, {keys: true}), RequestMocks.signInWithKeys);
97 | }).then(function(res) {
98 | result.signIn = res;
99 |
100 | return result;
101 | });
102 |
103 | };
104 |
105 | return AccountHelper;
106 | });
107 |
--------------------------------------------------------------------------------
/tests/addons/environment.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'tests/intern',
7 | 'intern/node_modules/dojo/has!host-node?intern/node_modules/dojo/node!xhr2',
8 | 'tests/addons/sinon',
9 | 'client/FxAccountClient',
10 | 'tests/addons/restmail',
11 | 'tests/addons/accountHelper',
12 | 'tests/mocks/request',
13 | 'tests/mocks/errors'
14 | ], function (config, XHR, Sinon, FxAccountClient, Restmail, AccountHelper, RequestMocks, ErrorMocks) {
15 |
16 | function Environment() {
17 | var self = this;
18 | this.authServerUrl = config.AUTH_SERVER_URL || 'http://127.0.0.1:9000';
19 | // if 'auth_server' is part of the Intern arguments then using a remote server
20 | this.useRemoteServer = !!config.AUTH_SERVER_URL;
21 | this.mailServerUrl = this.authServerUrl.match(/^http:\/\/127/) ?
22 | 'http://127.0.0.1:9001' :
23 | 'http://restmail.net';
24 |
25 | if (this.useRemoteServer) {
26 | this.xhr = XHR.XMLHttpRequest;
27 | // respond is a noop because we are using real XHR in this case
28 | this.respond = noop;
29 |
30 | } else {
31 | this.requests = [];
32 | this.responses = [];
33 | // switch to the fake XHR
34 | this.xhr = Sinon.useFakeXMLHttpRequest();
35 | this.xhr.onCreate = function (xhr) {
36 | if (self.requests.length < self.responses.length) {
37 | var mock = self.responses[self.requests.length];
38 | setTimeout(function() {
39 | xhr.respond(mock.status, mock.headers, mock.body);
40 | }, 0);
41 | }
42 | self.requests.push(xhr);
43 | };
44 | // respond calls a fake XHR response using SinonJS
45 | this.respond = function (returnValue, mock) {
46 | if (arguments.length < 2) {
47 | mock = returnValue;
48 | returnValue = null;
49 | }
50 | if (typeof mock === 'undefined') {
51 | console.log('Mock does not exist!');
52 | }
53 | // this has to be here to work in IE
54 | setTimeout(function () {
55 | if (self.responses.length < self.requests.length) {
56 | self.requests[self.responses.length].respond(mock.status, mock.headers, mock.body);
57 | }
58 | self.responses.push(mock);
59 | }, 0);
60 | return returnValue;
61 | };
62 | }
63 | // initialize a new FxA Client
64 | this.client = new FxAccountClient(this.authServerUrl, { xhr: this.xhr });
65 | // setup Restmail,
66 | this.mail = new Restmail(this.mailServerUrl, this.xhr);
67 | // account helper takes care of new verified and unverified accounts
68 | this.accountHelper = new AccountHelper(this.client, this.mail, this.respond);
69 | this.ErrorMocks = ErrorMocks;
70 | this.RequestMocks = RequestMocks;
71 | }
72 |
73 | function noop(val) { return val; }
74 |
75 | return Environment;
76 | });
77 |
--------------------------------------------------------------------------------
/tests/addons/node-client.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern/node_modules/dojo/node!../../node/index'
7 | ], function (FxAccountClient) {
8 |
9 | return FxAccountClient;
10 | });
11 |
--------------------------------------------------------------------------------
/tests/addons/restmail.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'client/lib/request'
7 | ], function (Request) {
8 | 'use strict';
9 |
10 | function Restmail(server, xhr) {
11 | this.request = new Request(server, xhr);
12 | }
13 |
14 | // utility function that waits for a restmail email to arrive
15 | Restmail.prototype.wait = function (user, number) {
16 | var self = this;
17 |
18 | if (!number) number = 1; //eslint-disable-line curly
19 | console.log('Waiting for email...');
20 |
21 | return this.request.send('/mail/' + user, 'GET')
22 | .then(function (result) {
23 | if (result.length === number) {
24 | return result;
25 | } else {
26 | return new Promise(function (resolve, reject) {
27 | setTimeout(function () {
28 | self.wait(user, number)
29 | .then(resolve, reject);
30 | }, 1000);
31 | });
32 | }
33 | });
34 | };
35 |
36 | return Restmail;
37 | });
38 |
--------------------------------------------------------------------------------
/tests/all.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'tests/lib/account',
7 | 'tests/lib/certificateSign',
8 | 'tests/lib/credentials',
9 | 'tests/lib/device',
10 | 'tests/lib/emails',
11 | 'tests/lib/errors',
12 | 'tests/lib/hawkCredentials',
13 | 'tests/lib/headerLang',
14 | 'tests/lib/hkdf',
15 | 'tests/lib/init',
16 | 'tests/lib/metricsContext',
17 | 'tests/lib/misc',
18 | 'tests/lib/passwordChange',
19 | 'tests/lib/recoveryCodes',
20 | 'tests/lib/recoveryKeys',
21 | 'tests/lib/recoveryEmail',
22 | 'tests/lib/request',
23 | 'tests/lib/session',
24 | 'tests/lib/signIn',
25 | 'tests/lib/signinCodes',
26 | 'tests/lib/signUp',
27 | 'tests/lib/totp',
28 | 'tests/lib/tokenCodes',
29 | 'tests/lib/sms',
30 | 'tests/lib/unbundle',
31 | 'tests/lib/uriVersion',
32 | 'tests/lib/verifyCode'
33 | ], function () {});
34 |
--------------------------------------------------------------------------------
/tests/ci/install-tunnel.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | wget https://saucelabs.com/downloads/sc-4.4.5-linux.tar.gz -O /tmp/sc-4.4.5-linux.tar.gz
4 | cd /tmp
5 | tar xvf /tmp/sc-4.4.5-linux.tar.gz
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/ci/travis-auth-server-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | # Install and start the auth server
4 | git clone https://github.com/mozilla/fxa-auth-server.git
5 | cd fxa-auth-server && npm i
6 | SECONDARY_EMAIL_ENABLED=true SIGNIN_CONFIRMATION_ENABLED=true SIGNIN_CONFIRMATION_FORCE_EMAIL_REGEX="^confirm.*@restmail\\.net$" SIGNIN_UNBLOCK_ALLOWED_EMAILS="^block.*@restmail\\.net$" SIGNIN_UNBLOCK_FORCED_EMAILS="^block.*@restmail\\.net$" npm start &
7 | cd ..
8 | sleep 10
9 |
10 | # Run the tests against the local auth server
11 | npm run test-local
12 |
--------------------------------------------------------------------------------
/tests/examples/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 | fxa-js-client tester
20 |
21 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
85 |
89 |
90 |
91 |
92 |
93 | See Console....
94 |
95 |
96 |
97 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/tests/examples/proxy.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | /**
6 | * Auth Proxy tester for better IE debugging.
7 | *
8 | * Run using: node tests/examples/proxy.js
9 | */
10 | var http = require('http');
11 | var fs = require('fs');
12 | var httpProxy = require('http-proxy');
13 |
14 | var proxy = httpProxy.createProxyServer();
15 | var port = 9133;
16 | var targetAuthServer = 'http://127.0.0.1:9000';
17 |
18 | http.createServer(function (req, res) {
19 |
20 | if (req.url === '/example.html') {
21 |
22 | res.end(fs.readFileSync('tests/examples/example.html'));
23 | } else if (req.url === '/build/fxa-client.js') {
24 |
25 | res.end(fs.readFileSync('build/fxa-client.js'));
26 | } else {
27 |
28 | proxy.web(req, res, {
29 | target: targetAuthServer
30 | });
31 | }
32 |
33 | }).listen(port);
34 |
35 | console.log('Starting proxy on', port, 'targeting', targetAuthServer, 'fxa-auth-server');
36 |
--------------------------------------------------------------------------------
/tests/intern.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | // Learn more about configuring this file at .
6 | // These default settings work OK for most people. The options that *must* be changed below are the
7 | // packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites.
8 | define(['intern/lib/args'], function (args) {
9 |
10 | // define a server to run against
11 | var server;
12 |
13 | // if 'auth_server' in the Intern args
14 | if (args.auth_server) {
15 | server = args.auth_server;
16 | if (server === 'LOCAL') {
17 | server = 'http://127.0.0.1:9000';
18 | }
19 |
20 | if (server === 'LATEST') {
21 | server = 'https://latest.dev.lcip.org/auth';
22 | }
23 |
24 | if (server === 'STABLE') {
25 | server = 'https://stable.dev.lcip.org/auth';
26 | }
27 |
28 | console.log('Running against ' + server);
29 | } else {
30 | console.log('Running with mocks...');
31 | }
32 |
33 | return {
34 | loader: {
35 | // Packages that should be registered with the loader in each testing environment
36 | packages: [ { name: 'fxa-js-client', location: 'client' } ],
37 | map: {
38 | '*': {
39 | 'es6-promise': 'node_modules/es6-promise/dist/es6-promise',
40 | sjcl: 'node_modules/sjcl/sjcl'
41 | }
42 | }
43 | },
44 |
45 | suites: [ 'tests/all' ],
46 | functionalSuites: [ ],
47 | AUTH_SERVER_URL: server,
48 |
49 | excludeInstrumentation: /./
50 |
51 | };
52 |
53 | });
54 |
--------------------------------------------------------------------------------
/tests/intern_browser.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | './intern'
7 | ], function (intern) {
8 | intern.proxyPort = 9090;
9 | intern.proxyUrl = 'http://localhost:9090/';
10 |
11 | intern.useSauceConnect = false;
12 |
13 | intern.webdriver = {
14 | host: 'localhost',
15 | port: 4444
16 | };
17 |
18 | intern.capabilities = {
19 | 'selenium-version': '2.39.0'
20 | };
21 |
22 | intern.environments = [
23 | { browserName: 'firefox', version: '25' }
24 | ];
25 |
26 | return intern;
27 | });
28 |
--------------------------------------------------------------------------------
/tests/intern_native_node.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | './intern'
7 | ], function (intern, FxAccountClient) {
8 |
9 | var map = intern.loader.map['*'];
10 | map['client/FxAccountClient'] = 'tests/addons/node-client';
11 |
12 | return intern;
13 | });
14 |
--------------------------------------------------------------------------------
/tests/intern_sauce.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | './intern'
7 | ], function (intern) {
8 | intern.proxyPort = 9090;
9 | intern.proxyUrl = 'http://localhost:9090/';
10 |
11 | intern.useSauceConnect = true;
12 | intern.maxConcurrency = 3;
13 |
14 | intern.tunnel = 'SauceLabsTunnel';
15 | intern.tunnelOptions = {
16 | directory: '/tmp/sc-4.4.5-linux/bin',
17 | executable: './sc'
18 | };
19 |
20 | intern.webdriver = {
21 | host: 'localhost',
22 | port: 4445
23 | };
24 |
25 | intern.capabilities = {
26 | 'build': '1',
27 | };
28 |
29 | intern.environments = [
30 | { browserName: 'firefox', version: [ '45' ], platform: [ 'Windows 7', 'Linux' ] },
31 | { browserName: 'firefox', version: [ '56' ], platform: [ 'Windows 7' ] }, // Sauce only supports Fx 56 on Windows/Mac
32 | { browserName: 'internet explorer', version: [ '10', '11' ], platform: [ 'Windows 7' ] },
33 | { browserName: 'chrome' }
34 | ];
35 |
36 | console.log('SAUCE', intern.proxyUrl);
37 |
38 | return intern;
39 | });
40 |
--------------------------------------------------------------------------------
/tests/lib/certificateSign.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | with (tdd) {
12 | suite('certificateSign', function () {
13 | var accountHelper;
14 | var respond;
15 | var client;
16 | var RequestMocks;
17 |
18 | beforeEach(function () {
19 | var env = new Environment();
20 | accountHelper = env.accountHelper;
21 | respond = env.respond;
22 | client = env.client;
23 | RequestMocks = env.RequestMocks;
24 | });
25 |
26 | test('#basic', function () {
27 |
28 | return accountHelper.newVerifiedAccount()
29 | .then(function (account) {
30 | var publicKey = {
31 | algorithm: 'RS',
32 | n: '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123',
33 | e: '65537'
34 | };
35 | var duration = 86400000;
36 |
37 | return respond(client.certificateSign(account.signIn.sessionToken, publicKey, duration), RequestMocks.certificateSign);
38 | })
39 | .then(
40 | function(res) {
41 | assert.property(res, 'cert', 'got cert');
42 | },
43 | assert.notOk
44 | );
45 | });
46 |
47 | test('#with service option', function () {
48 | return accountHelper.newVerifiedAccount()
49 | .then(function (account) {
50 | var publicKey = {
51 | algorithm: 'RS',
52 | n: '4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123',
53 | e: '65537'
54 | };
55 | var duration = 86400000;
56 |
57 | return respond(
58 | client.certificateSign(account.signIn.sessionToken, publicKey, duration, {
59 | service: 'wibble'
60 | }),
61 | RequestMocks.certificateSign
62 | );
63 | })
64 | .then(
65 | function(res) {
66 | assert.ok(res);
67 | },
68 | assert.notOk
69 | );
70 | });
71 | });
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/tests/lib/credentials.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'node_modules/sjcl/sjcl',
9 | 'client/lib/credentials'
10 | ], function (tdd, assert, sjcl, credentials) {
11 | with (tdd) {
12 | suite('credentials', function () {
13 | test('#client stretch-KDF vectors', function () {
14 | var email = sjcl.codec.utf8String.fromBits(sjcl.codec.hex.toBits('616e6472c3a9406578616d706c652e6f7267'));
15 | var password = sjcl.codec.utf8String.fromBits(sjcl.codec.hex.toBits('70c3a4737377c3b67264'));
16 |
17 | return credentials.setup(email, password)
18 | .then(
19 | function(result) {
20 | var quickStretchedPW = sjcl.codec.hex.fromBits(result.quickStretchedPW);
21 | var authPW = sjcl.codec.hex.fromBits(result.authPW);
22 | var unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey);
23 |
24 | assert.equal(quickStretchedPW, 'e4e8889bd8bd61ad6de6b95c059d56e7b50dacdaf62bd84644af7e2add84345d', '== quickStretchedPW is equal');
25 | assert.equal(authPW, '247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375', '== authPW is equal');
26 | assert.equal(unwrapBKey, 'de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28', '== unwrapBkey is equal');
27 | },
28 | assert.notOk
29 | );
30 | });
31 |
32 | test('#wrap', function () {
33 | var bit1 = sjcl.codec.hex.toBits('c347de41c8a409c17b5b88e4985e1cd10585bb79b4a80d5e576eaf97cd1277fc');
34 | var bit2 = sjcl.codec.hex.toBits('3afd383d9bc1857318f24c5f293af62254f0476f0aaacfb929c61b534d0b5075');
35 | var result = credentials.xor(bit1, bit2);
36 |
37 | assert.equal(sjcl.codec.hex.fromBits(result), 'f9bae67c53658cb263a9c4bbb164eaf35175fc16be02c2e77ea8b4c480192789', '== wrap worked correctly');
38 | });
39 | });
40 | }
41 | });
42 |
--------------------------------------------------------------------------------
/tests/lib/device.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/lib/push-constants'
10 | ], function (tdd, assert, Environment, PushTestConstants) {
11 |
12 | var DEVICE_CALLBACK = PushTestConstants.DEVICE_CALLBACK;
13 | var DEVICE_NAME = PushTestConstants.DEVICE_NAME;
14 | var DEVICE_NAME_2 = PushTestConstants.DEVICE_NAME_2;
15 | var DEVICE_TYPE = PushTestConstants.DEVICE_TYPE;
16 |
17 | with (tdd) {
18 | suite('device', function () {
19 | var accountHelper;
20 | var respond;
21 | var client;
22 | var RequestMocks;
23 |
24 | beforeEach(function () {
25 | var env = new Environment();
26 | accountHelper = env.accountHelper;
27 | respond = env.respond;
28 | client = env.client;
29 | RequestMocks = env.RequestMocks;
30 | });
31 |
32 | test('#register', function () {
33 |
34 | return accountHelper.newVerifiedAccount()
35 | .then(function (account) {
36 |
37 | return respond(client.deviceRegister(
38 | account.signIn.sessionToken,
39 | DEVICE_NAME,
40 | DEVICE_TYPE,
41 | {
42 | deviceCallback: DEVICE_CALLBACK
43 | }
44 | ), RequestMocks.deviceRegister);
45 | })
46 | .then(
47 | function(res) {
48 | assert.ok(res.id);
49 | assert.equal(res.name, DEVICE_NAME);
50 | assert.equal(res.pushCallback, DEVICE_CALLBACK);
51 | assert.equal(res.type, DEVICE_TYPE);
52 | },
53 | function (err) {
54 | console.log(err);
55 | assert.notOk();
56 | }
57 | );
58 | });
59 |
60 | test('#update', function () {
61 |
62 | return accountHelper.newVerifiedAccount()
63 | .then(function (account) {
64 |
65 | return respond(client.deviceRegister(
66 | account.signIn.sessionToken,
67 | DEVICE_NAME,
68 | DEVICE_TYPE,
69 | {
70 | deviceCallback: DEVICE_CALLBACK
71 | }
72 | ), RequestMocks.deviceRegister)
73 |
74 | .then(function (device) {
75 |
76 | return respond(client.deviceUpdate(
77 | account.signIn.sessionToken,
78 | device.id,
79 | DEVICE_NAME_2,
80 | {
81 | deviceCallback: DEVICE_CALLBACK
82 | }
83 | ), RequestMocks.deviceUpdate);
84 | });
85 | })
86 | .then(
87 | function(res) {
88 | assert.ok(res.id);
89 | assert.equal(res.name, DEVICE_NAME_2);
90 | assert.equal(res.pushCallback, DEVICE_CALLBACK);
91 | },
92 | assert.notOk
93 | );
94 | });
95 |
96 | test('#destroy', function () {
97 |
98 | return accountHelper.newVerifiedAccount()
99 | .then(function (account) {
100 |
101 | return respond(client.deviceRegister(
102 | account.signIn.sessionToken,
103 | DEVICE_NAME,
104 | DEVICE_TYPE,
105 | {
106 | deviceCallback: DEVICE_CALLBACK
107 | }
108 | ), RequestMocks.deviceRegister)
109 |
110 | .then(function (device) {
111 |
112 | return respond(client.deviceDestroy(
113 | account.signIn.sessionToken,
114 | device.id
115 | ), RequestMocks.deviceDestroy);
116 | });
117 | })
118 | .then(
119 | function(res) {
120 | assert.equal(Object.keys(res), 0);
121 | },
122 | assert.notOk
123 | );
124 | });
125 |
126 | test('#list', function () {
127 |
128 | return accountHelper.newVerifiedAccount()
129 | .then(function (account) {
130 |
131 | return respond(client.deviceRegister(
132 | account.signIn.sessionToken,
133 | DEVICE_NAME,
134 | DEVICE_TYPE,
135 | {
136 | deviceCallback: DEVICE_CALLBACK
137 | }
138 | ), RequestMocks.deviceRegister)
139 |
140 | .then(function (device) {
141 | return respond(client.deviceList(account.signIn.sessionToken),
142 | RequestMocks.deviceList);
143 | })
144 |
145 | .then(function (devices) {
146 | assert.equal(devices.length, 1);
147 |
148 | var device = devices[0];
149 | assert.ok(device.id);
150 | assert.equal(device.name, DEVICE_NAME);
151 | assert.equal(device.pushCallback, DEVICE_CALLBACK);
152 | assert.equal(device.type, DEVICE_TYPE);
153 | });
154 | });
155 | });
156 |
157 | });
158 | }
159 | });
160 |
161 |
--------------------------------------------------------------------------------
/tests/lib/emails.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | var user2;
12 | var user2Email;
13 |
14 | with (tdd) {
15 | suite('emails', function () {
16 | var accountHelper;
17 | var respond;
18 | var mail;
19 | var client;
20 | var RequestMocks;
21 | var account;
22 |
23 | beforeEach(function () {
24 | var env = new Environment();
25 | accountHelper = env.accountHelper;
26 | respond = env.respond;
27 | mail = env.mail;
28 | client = env.client;
29 | RequestMocks = env.RequestMocks;
30 |
31 | user2 = 'anotherEmail' + new Date().getTime();
32 | user2Email = user2 + '@restmail.net';
33 | });
34 |
35 | function recoveryEmailCreate() {
36 | return accountHelper.newVerifiedAccount()
37 | .then(
38 | function (res) {
39 | account = res;
40 | return respond(client.recoveryEmailCreate(
41 | account.signIn.sessionToken,
42 | user2Email
43 | ), RequestMocks.recoveryEmailCreate);
44 | },
45 | handleError
46 | );
47 | }
48 |
49 | function handleError(err) {
50 | console.log(err);
51 | assert.notOk();
52 | }
53 |
54 | test('#recoveryEmailCreate', function () {
55 | return recoveryEmailCreate()
56 | .then(
57 | function (res) {
58 | assert.ok(res);
59 | },
60 | handleError
61 | );
62 | });
63 |
64 | test('#recoveryEmails', function () {
65 | return recoveryEmailCreate()
66 | .then(
67 | function (res) {
68 | assert.ok(res);
69 | return respond(client.recoveryEmails(
70 | account.signIn.sessionToken
71 | ), RequestMocks.recoveryEmailsUnverified);
72 | },
73 | handleError
74 | )
75 | .then(
76 | function (res) {
77 | assert.ok(res);
78 | assert.equal(res.length, 2, 'returned two emails');
79 | assert.equal(res[1].verified, false, 'returned not verified');
80 | },
81 | handleError
82 | );
83 | });
84 |
85 | test('#verifyCode', function () {
86 | return recoveryEmailCreate()
87 | .then(
88 | function (res) {
89 | assert.ok(res);
90 |
91 | return respond(mail.wait(user2, 1), RequestMocks.mailUnverifiedEmail);
92 | },
93 | handleError
94 | )
95 | .then(function (emails) {
96 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
97 |
98 | return respond(client.verifyCode(account.signIn.uid, code, {type: 'secondary'}), RequestMocks.verifyCode);
99 | })
100 | .then(
101 | function (res) {
102 | assert.ok(res);
103 |
104 | return respond(client.recoveryEmails(
105 | account.signIn.sessionToken
106 | ), RequestMocks.recoveryEmailsVerified);
107 | },
108 | handleError
109 | )
110 | .then(
111 | function (res) {
112 | assert.ok(res);
113 | assert.equal(res.length, 2, 'returned one email');
114 | assert.equal(res[1].verified, true, 'returned not verified');
115 | },
116 | handleError
117 | );
118 | });
119 |
120 | test('#recoveryEmailDestroy', function () {
121 | return recoveryEmailCreate()
122 | .then(
123 | function (res) {
124 | assert.ok(res);
125 |
126 | return respond(client.recoveryEmails(
127 | account.signIn.sessionToken
128 | ), RequestMocks.recoveryEmailsUnverified);
129 | },
130 | handleError
131 | )
132 | .then(
133 | function (res) {
134 | assert.ok(res);
135 | assert.equal(res.length, 2, 'returned two email');
136 | assert.equal(res[1].verified, false, 'returned not verified');
137 |
138 | return respond(client.recoveryEmailDestroy(
139 | account.signIn.sessionToken,
140 | user2Email
141 | ), RequestMocks.recoveryEmailDestroy);
142 | },
143 | handleError
144 | )
145 | .then(
146 | function (res) {
147 | assert.ok(res);
148 |
149 | return respond(client.recoveryEmails(
150 | account.signIn.sessionToken
151 | ), RequestMocks.recoveryEmails);
152 | },
153 | handleError
154 | )
155 | .then(
156 | function (res) {
157 | assert.ok(res);
158 | assert.equal(res.length, 1, 'returned one email');
159 | },
160 | handleError
161 | );
162 | });
163 |
164 | test('#recoveryEmailSetPrimaryEmail', function () {
165 | return recoveryEmailCreate()
166 | .then(
167 | function (res) {
168 | assert.ok(res);
169 |
170 | return respond(mail.wait(user2, 1), RequestMocks.mailUnverifiedEmail);
171 | },
172 | handleError
173 | )
174 | .then(function (emails) {
175 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
176 |
177 | return respond(client.verifyCode(account.signIn.uid, code, {type: 'secondary'}), RequestMocks.verifyCode);
178 | })
179 | .then(
180 | function (res) {
181 | assert.ok(res);
182 |
183 | return respond(client.recoveryEmailSetPrimaryEmail(
184 | account.signIn.sessionToken,
185 | user2Email
186 | ), RequestMocks.recoveryEmailSetPrimaryEmail);
187 | },
188 | handleError
189 | )
190 | .then(
191 | function (res) {
192 | assert.ok(res);
193 |
194 | return respond(client.recoveryEmails(
195 | account.signIn.sessionToken
196 | ), RequestMocks.recoveryEmailsSetPrimaryVerified);
197 | },
198 | handleError
199 | )
200 | .then(
201 | function (res) {
202 | assert.ok(res);
203 | assert.equal(res.length, 2, 'returned two emails');
204 |
205 | assert.equal(true, res[0].email.indexOf('anotherEmail') > -1, 'returned correct primary email');
206 | assert.equal(res[0].verified, true, 'returned verified');
207 | assert.equal(res[0].isPrimary, true, 'returned isPrimary true');
208 |
209 | assert.equal(res[1].verified, true, 'returned verified');
210 | assert.equal(res[1].isPrimary, false, 'returned isPrimary false');
211 | },
212 | handleError
213 | );
214 | });
215 | });
216 | }
217 | });
218 |
219 |
--------------------------------------------------------------------------------
/tests/lib/errors.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | with (tdd) {
12 | suite('errors', function () {
13 | var accountHelper;
14 | var respond;
15 | var client;
16 | var ErrorMocks;
17 |
18 | beforeEach(function () {
19 | var env = new Environment();
20 | accountHelper = env.accountHelper;
21 | respond = env.respond;
22 | client = env.client;
23 | ErrorMocks = env.ErrorMocks;
24 | });
25 |
26 | test('#accountUnverified', function () {
27 |
28 | return accountHelper.newUnverifiedAccount()
29 | .then(function (account) {
30 | var pk = {algorithm: 'RS', n: 'x', e: 'y'};
31 | var duration = 1000;
32 |
33 | return respond(client.certificateSign(account.signIn.sessionToken, pk, duration), ErrorMocks.accountUnverified);
34 | })
35 | .then(
36 | function () {
37 | assert.fail();
38 | },
39 | function(error) {
40 | assert.equal(error.code, 400);
41 | assert.equal(error.errno, 104);
42 | }
43 | );
44 | });
45 |
46 | test('#invalidVerificationCode', function () {
47 |
48 | return accountHelper.newUnverifiedAccount()
49 | .then(function (account) {
50 | return respond(client.verifyCode(account.signUp.uid, 'eb531a64deb628b2baeaceaa8762abf0'), ErrorMocks.invalidVerification);
51 | })
52 | .then(
53 | function () {
54 | assert.fail();
55 | },
56 | function(error) {
57 | assert.equal(error.code, 400);
58 | assert.equal(error.errno, 105);
59 | }
60 | );
61 | });
62 | });
63 | }
64 | });
65 |
--------------------------------------------------------------------------------
/tests/lib/hawkCredentials.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'node_modules/sjcl/sjcl',
9 | 'client/lib/hawkCredentials'
10 | ], function (tdd, assert, sjcl, hawkCredentials) {
11 | with (tdd) {
12 | suite('hawkCredentials', function () {
13 | test('#client derive hawk credentials', function () {
14 | var context = 'sessionToken';
15 | var sessionToken = 'a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf';
16 |
17 | return hawkCredentials(sessionToken, context, 3 * 32)
18 | .then(
19 | function (result) {
20 | var hmacKey = sjcl.codec.hex.fromBits(result.key);
21 |
22 | assert.equal(hmacKey, '9d8f22998ee7f5798b887042466b72d53e56ab0c094388bf65831f702d2febc0', '== hmacKey is equal');
23 | assert.equal(result.id, 'c0a29dcf46174973da1378696e4c82ae10f723cf4f4d9f75e39f4ae3851595ab', '== id is equal');
24 | },
25 | assert.notOk
26 | );
27 | });
28 | });
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/tests/lib/headerLang.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | with (tdd) {
12 | suite('headerLanguage', function () {
13 | var accountHelper;
14 | var respond;
15 | var client;
16 | var mail;
17 | var RequestMocks;
18 |
19 | beforeEach(function () {
20 | var env = new Environment();
21 | accountHelper = env.accountHelper;
22 | respond = env.respond;
23 | RequestMocks = env.RequestMocks;
24 | client = env.client;
25 | mail = env.mail;
26 | });
27 |
28 | test('#signUp', function () {
29 | var user = 'test' + new Date().getTime();
30 | var email = user + '@restmail.net';
31 | var password = 'iliketurtles';
32 | var opts = {
33 | lang: 'zh-cn;'
34 | };
35 |
36 | return respond(client.signUp(email, password, opts), RequestMocks.signUp)
37 | .then(function (res) {
38 | assert.ok(res.uid);
39 | return respond(mail.wait(user), RequestMocks.mailSignUpLang);
40 | })
41 | .then(
42 | function (emails) {
43 | assert.property(emails[0], 'headers');
44 | assert.equal(emails[0].headers['content-language'], 'zh-CN');
45 | },
46 | assert.notOk
47 | );
48 | });
49 |
50 | test('#passwordForgotSendCode', function () {
51 | var account;
52 | var passwordForgotToken;
53 | var opts = {
54 | lang: 'zh-CN',
55 | service: 'sync'
56 | };
57 |
58 | return accountHelper.newUnverifiedAccount()
59 | .then(function (acc) {
60 | account = acc;
61 |
62 | return respond(client.passwordForgotSendCode(account.input.email, opts), RequestMocks.passwordForgotSendCode);
63 | })
64 | .then(function (result) {
65 | passwordForgotToken = result.passwordForgotToken;
66 | assert.ok(passwordForgotToken, 'passwordForgotToken is returned');
67 |
68 | return respond(mail.wait(account.input.user, 3), RequestMocks.resetMailLang);
69 | })
70 | .then(
71 | function (emails) {
72 | assert.property(emails[2], 'headers');
73 | assert.equal(emails[2].headers['content-language'], 'zh-CN');
74 | },
75 | assert.notOk
76 | );
77 | });
78 |
79 | test('#recoveryEmailResendCode', function () {
80 | var user;
81 | var opts = {
82 | lang: 'zh-CN'
83 | };
84 |
85 | return accountHelper.newUnverifiedAccount()
86 | .then(function (account) {
87 | user = account.input.user;
88 |
89 | return respond(client.recoveryEmailResendCode(account.signIn.sessionToken, opts), RequestMocks.recoveryEmailResendCode);
90 | })
91 | .then(
92 | function(res) {
93 | assert.ok(res);
94 |
95 | return respond(mail.wait(user, 3), RequestMocks.resetMailLang);
96 | })
97 | .then(
98 | function (emails) {
99 | assert.property(emails[2], 'headers');
100 | assert.equal(emails[2].headers['content-language'], 'zh-CN');
101 | },
102 | assert.notOk
103 | );
104 | });
105 |
106 | });
107 | }
108 | });
109 |
--------------------------------------------------------------------------------
/tests/lib/hkdf.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'node_modules/sjcl/sjcl',
9 | 'client/lib/hkdf'
10 | ], function (tdd, assert, sjcl, hkdf) {
11 | with (tdd) {
12 |
13 | // test vectors from RFC5869
14 | suite('hkdf', function () {
15 |
16 | test('#vector 1', function () {
17 |
18 | var ikm = sjcl.codec.hex.toBits('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
19 | var salt = sjcl.codec.hex.toBits('000102030405060708090a0b0c');
20 | var info = sjcl.codec.hex.toBits('f0f1f2f3f4f5f6f7f8f9');
21 |
22 | return hkdf(ikm, info, salt, 42)
23 | .then(
24 | function (result) {
25 | assert.equal(sjcl.codec.hex.fromBits(result).length, 84);
26 | assert.equal(sjcl.codec.hex.fromBits(result), '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865');
27 | },
28 | assert.notOk
29 | );
30 | });
31 |
32 | test('#vector 2', function () {
33 |
34 | var ikm = sjcl.codec.hex.toBits('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
35 | var salt = sjcl.codec.hex.toBits('');
36 | var info = sjcl.codec.hex.toBits('');
37 |
38 | return hkdf(ikm, info, salt, 42)
39 | .then(
40 | function (result) {
41 | assert.equal(sjcl.codec.hex.fromBits(result).length, 84);
42 | assert.equal(sjcl.codec.hex.fromBits(result), '8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8');
43 | },
44 | assert.notOk
45 | );
46 | });
47 |
48 | test('#vector 3', function () {
49 |
50 | var ikm = sjcl.codec.hex.toBits('4a9cbe5ae7190a7bb7cc54d5d84f5e4ba743904f8a764933b72f10260067375a');
51 | var salt = sjcl.codec.hex.toBits('');
52 | var info = sjcl.codec.utf8String.toBits('identity.mozilla.com/picl/v1/keyFetchToken');
53 |
54 | return hkdf(ikm, info, salt, 3 * 32)
55 | .then(
56 | function (result) {
57 | assert.equal(sjcl.codec.hex.fromBits(result), 'f4df04ffb79db35e94e4881719a6f145f9206e8efea17fc9f02a5ce09cbfac1e829a935f34111d75e0d16b7aa178e2766759eedb6f623c0babd2abcfea82bc12af75f6aa543a8ba7e0a029f87c785c4af0ad03889f7437f735b5256a88fc73fd');
58 | },
59 | assert.notOk
60 | );
61 | });
62 |
63 | test('#vector 4', function () {
64 |
65 | var ikm = sjcl.codec.hex.toBits('ba0a107dab60f3b065ff7a642d14fe824fbd71bc5c99087e9e172a1abd1634f1');
66 | var salt = sjcl.codec.hex.toBits('');
67 | var info = sjcl.codec.utf8String.toBits('identity.mozilla.com/picl/v1/account/keys');
68 |
69 | return hkdf(ikm, info, salt, 3 * 32)
70 | .then(
71 | function (result) {
72 | assert.equal(sjcl.codec.hex.fromBits(result), '17ab463653a94c9a6419b48781930edefe500395e3b4e7879a2be1599975702285de16c3218a126404668bf9b7acfb6ce2b7e03c8889047ba48b8b854c6d8beb3ae100e145ca6d69cb519a872a83af788771954455716143bc08225ea8644d85');
73 | },
74 | assert.notOk
75 | );
76 | });
77 |
78 | });
79 | }
80 | });
81 |
--------------------------------------------------------------------------------
/tests/lib/init.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'client/FxAccountClient'
9 | ], function (tdd, assert, FxAccountClient) {
10 |
11 | with (tdd) {
12 | suite('init', function () {
13 | test('#should error if no options set', function () {
14 | try {
15 | void new FxAccountClient();
16 | } catch (e) {
17 | assert.isDefined(e.message);
18 | }
19 | });
20 |
21 | test('#should catch undefined parameters for the url', function () {
22 | try {
23 | void new FxAccountClient(undefined, {});
24 | } catch (e) {
25 | assert.isDefined(e.message);
26 | }
27 | });
28 | });
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/tests/lib/metricsContext.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'client/lib/metricsContext'
9 | ], function (t, assert, metricsContext) {
10 | 'use strict';
11 |
12 | t.suite('metricsContext', function () {
13 | t.test('interface is correct', function () {
14 | assert.isObject(metricsContext);
15 | assert.lengthOf(Object.keys(metricsContext), 1);
16 | assert.isFunction(metricsContext.marshall);
17 | });
18 |
19 | t.test('marshall returns correct data', function () {
20 | var input = {
21 | context: 'fx_desktop_v3',
22 | deviceId: '0123456789abcdef0123456789abcdef',
23 | entrypoint: 'menupanel',
24 | flowBeginTime: 1479815991573,
25 | flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
26 | migration: 'sync11',
27 | service: 'sync',
28 | utmCampaign: 'foo',
29 | utmContent: 'bar',
30 | utmMedium: 'baz',
31 | utmSource: 'qux',
32 | utmTerm: 'wibble'
33 | };
34 |
35 | assert.deepEqual(metricsContext.marshall(input), {
36 | deviceId: input.deviceId,
37 | entrypoint: 'menupanel',
38 | flowBeginTime: input.flowBeginTime,
39 | flowId: input.flowId,
40 | utmCampaign: 'foo',
41 | utmContent: 'bar',
42 | utmMedium: 'baz',
43 | utmSource: 'qux',
44 | utmTerm: 'wibble'
45 | });
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tests/lib/misc.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | with (tdd) {
12 | suite('misc', function () {
13 | var respond;
14 | var client;
15 | var RequestMocks;
16 |
17 | beforeEach(function () {
18 | var env = new Environment();
19 | respond = env.respond;
20 | client = env.client;
21 | RequestMocks = env.RequestMocks;
22 | });
23 |
24 | test('#getRandomBytes', function () {
25 |
26 | return respond(client.getRandomBytes(), RequestMocks.getRandomBytes)
27 | .then(
28 | function(res) {
29 | assert.property(res, 'data');
30 | },
31 | assert.notOk
32 | );
33 | });
34 |
35 | test('_required', function () {
36 | assert.doesNotThrow(function () {
37 | client._required(true, 'true_boolean');
38 | client._required(false, 'false_boolean');
39 | client._required('string', 'string');
40 | client._required({ hasValue: true }, 'object_with_value');
41 | client._required(1, 'number');
42 | client._required(0, 'zero');
43 | });
44 |
45 | assert.throws(function () {
46 | client._required('', 'empty_string');
47 | });
48 |
49 | assert.throws(function () {
50 | client._required({}, 'empty_object');
51 | });
52 |
53 | assert.throws(function () {
54 | client._required(null, 'null');
55 | });
56 | });
57 | });
58 | }
59 | });
60 |
--------------------------------------------------------------------------------
/tests/lib/passwordChange.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'sjcl',
9 | 'client/lib/credentials',
10 | 'tests/addons/environment'
11 | ], function (tdd, assert, sjcl, credentials, Environment) {
12 |
13 | with (tdd) {
14 | suite('passwordChange', function () {
15 | var accountHelper;
16 | var respond;
17 | var mail;
18 | var client;
19 | var RequestMocks;
20 | var ErrorMocks;
21 | var requests;
22 |
23 | beforeEach(function () {
24 | var env = new Environment();
25 | accountHelper = env.accountHelper;
26 | respond = env.respond;
27 | mail = env.mail;
28 | client = env.client;
29 | RequestMocks = env.RequestMocks;
30 | ErrorMocks = env.ErrorMocks;
31 | requests = env.requests;
32 | });
33 |
34 | test('#basic', function () {
35 | var user = 'test7' + new Date().getTime();
36 | var email = user + '@restmail.net';
37 | var password = 'iliketurtles';
38 | var newPassword = 'ilikefoxes';
39 | var kB;
40 | var newUnwrapBKey;
41 | var oldCreds;
42 | var uid;
43 | var account;
44 |
45 | // newUnwrapBKey from email+newpassword. The submitted newWrapKB
46 | // should equal (kB XOR newUnwrapBKey). This way we don't need to
47 | // know what the server will return for wrapKB: handy, since
48 | // sometimes we're using a mock (with a fixed response), but
49 | // sometimes we're using a real server (which randomly creates
50 | // wrapKB)
51 |
52 | return credentials.setup(email, newPassword)
53 | .then(function (newCreds) {
54 | newUnwrapBKey = sjcl.codec.hex.fromBits(newCreds.unwrapBKey);
55 | return respond(client.signUp(email, password), RequestMocks.signUp);
56 | })
57 | .then(function (result) {
58 | uid = result.uid;
59 |
60 | return respond(mail.wait(user), RequestMocks.mail);
61 | })
62 | .then(function (emails) {
63 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
64 |
65 | return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
66 | })
67 | .then(function() {
68 | return respond(client.signIn(email, password, {keys: true}), RequestMocks.signInWithKeys);
69 | })
70 | .then(function(result) {
71 | account = result;
72 | })
73 | .then(function () {
74 | return respond(client.accountKeys(account.keyFetchToken, account.unwrapBKey), RequestMocks.accountKeys);
75 | })
76 | .then(function(keys) {
77 | kB = keys.kB;
78 | })
79 | .then(function () {
80 | return respond(client._passwordChangeStart(email, password), RequestMocks.passwordChangeStart);
81 | })
82 | .then(function (credentials) {
83 | oldCreds = credentials;
84 | assert.equal(credentials.emailToHashWith, email);
85 |
86 | return respond(client._passwordChangeKeys(oldCreds), RequestMocks.accountKeys);
87 | })
88 | .then(function (keys) {
89 |
90 | return respond(client._passwordChangeFinish(email, newPassword, oldCreds, keys, { keys: false }), RequestMocks.passwordChangeFinish);
91 | })
92 | .then(function (result) {
93 | // currently only available for mocked requests (issue #103)
94 | if (requests) {
95 | var req = requests[requests.length - 1];
96 | var args = JSON.parse(req.requestBody);
97 | var expectedNewWrapKB = sjcl.codec.hex.fromBits(
98 | credentials.xor(sjcl.codec.hex.toBits(kB),
99 | sjcl.codec.hex.toBits(newUnwrapBKey)));
100 | assert.equal(args.wrapKb, expectedNewWrapKB);
101 | }
102 | assert.notProperty(result, 'keyFetchToken');
103 |
104 | return respond(client.signIn(email, newPassword), RequestMocks.signIn);
105 | })
106 | .then(
107 | function (res) {
108 | assert.property(res, 'sessionToken');
109 | },
110 | function (err) {
111 | throw err;
112 | }
113 | );
114 | });
115 |
116 | test('#keys', function () {
117 | var user = 'test7' + new Date().getTime();
118 | var email = user + '@restmail.net';
119 | var password = 'iliketurtles';
120 | var newPassword = 'ilikefoxes';
121 | var kB;
122 | var newUnwrapBKey;
123 | var oldCreds;
124 | var sessionToken;
125 | var uid;
126 | var account;
127 |
128 | // newUnwrapBKey from email+newpassword. The submitted newWrapKB
129 | // should equal (kB XOR newUnwrapBKey). This way we don't need to
130 | // know what the server will return for wrapKB: handy, since
131 | // sometimes we're using a mock (with a fixed response), but
132 | // sometimes we're using a real server (which randomly creates
133 | // wrapKB)
134 |
135 | return credentials.setup(email, newPassword)
136 | .then(function (newCreds) {
137 | newUnwrapBKey = sjcl.codec.hex.fromBits(newCreds.unwrapBKey);
138 | return respond(client.signUp(email, password), RequestMocks.signUp);
139 | })
140 | .then(function (result) {
141 | uid = result.uid;
142 |
143 | return respond(mail.wait(user), RequestMocks.mail);
144 | })
145 | .then(function (emails) {
146 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
147 |
148 | return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
149 | })
150 | .then(function() {
151 | return respond(client.signIn(email, password, {keys: true}), RequestMocks.signInWithKeys);
152 | })
153 | .then(function(result) {
154 | sessionToken = result.sessionToken;
155 | account = result;
156 | })
157 | .then(function () {
158 |
159 | return respond(client.accountKeys(account.keyFetchToken, account.unwrapBKey), RequestMocks.accountKeys);
160 | })
161 | .then(function(keys) {
162 | kB = keys.kB;
163 | })
164 | .then(function () {
165 | return respond(client._passwordChangeStart(email, password), RequestMocks.passwordChangeStart);
166 | })
167 | .then(function (credentials) {
168 | oldCreds = credentials;
169 | assert.equal(credentials.emailToHashWith, email);
170 |
171 | return respond(client._passwordChangeKeys(oldCreds), RequestMocks.accountKeys);
172 | })
173 | .then(function (keys) {
174 |
175 | return respond(client._passwordChangeFinish(email, newPassword, oldCreds, keys, { keys: true, sessionToken: sessionToken }), RequestMocks.passwordChangeFinishKeys);
176 | })
177 | .then(function (result) {
178 | // currently only available for mocked requests (issue #103)
179 | if (requests) {
180 | var req = requests[requests.length - 1];
181 | var args = JSON.parse(req.requestBody);
182 | var expectedNewWrapKB = sjcl.codec.hex.fromBits(
183 | credentials.xor(sjcl.codec.hex.toBits(kB),
184 | sjcl.codec.hex.toBits(newUnwrapBKey)));
185 | assert.equal(args.wrapKb, expectedNewWrapKB);
186 | }
187 | assert.property(result, 'sessionToken');
188 | assert.property(result, 'keyFetchToken');
189 | assert.property(result, 'unwrapBKey');
190 | assert.isTrue(result.verified);
191 |
192 | return respond(client.signIn(email, newPassword), RequestMocks.signIn);
193 | })
194 | .then(
195 | function (res) {
196 | assert.property(res, 'sessionToken');
197 | },
198 | function (err) {
199 | throw err;
200 | }
201 | );
202 | });
203 |
204 | test('#with incorrect case', function () {
205 | var newPassword = 'ilikefoxes';
206 | var account;
207 | var oldCreds;
208 |
209 | return accountHelper.newVerifiedAccount()
210 | .then(function (acc) {
211 | account = acc;
212 | var incorrectCaseEmail = account.input.email.charAt(0).toUpperCase() + account.input.email.slice(1);
213 |
214 | return respond(client._passwordChangeStart(incorrectCaseEmail, account.input.password), RequestMocks.passwordChangeStart);
215 | })
216 | .then(function (credentials) {
217 |
218 | oldCreds = credentials;
219 |
220 | return respond(client._passwordChangeKeys(oldCreds), RequestMocks.accountKeys);
221 | })
222 | .then(function (keys) {
223 |
224 | return respond(client._passwordChangeFinish(account.input.email, newPassword, oldCreds, keys), RequestMocks.passwordChangeFinish);
225 | })
226 | .then(function (result) {
227 | assert.ok(result, '{}');
228 |
229 | return respond(client.signIn(account.input.email, newPassword), RequestMocks.signIn);
230 | })
231 | .then(
232 | function (res) {
233 | assert.property(res, 'sessionToken');
234 | },
235 | function (err) {
236 | throw err;
237 | }
238 | );
239 | });
240 |
241 | test('#with incorrect case with skipCaseError', function () {
242 | var account;
243 |
244 | return accountHelper.newVerifiedAccount()
245 | .then(function (acc) {
246 | account = acc;
247 | var incorrectCaseEmail = account.input.email.charAt(0).toUpperCase() + account.input.email.slice(1);
248 |
249 | return respond(client._passwordChangeStart(incorrectCaseEmail, account.input.password, {skipCaseError: true}),
250 | ErrorMocks.incorrectEmailCase);
251 | })
252 | .then(
253 | function () {
254 | assert.fail();
255 | },
256 | function (res) {
257 | assert.equal(res.code, 400);
258 | assert.equal(res.errno, 120);
259 | }
260 | );
261 | });
262 |
263 | /**
264 | * Changing the Password failure
265 | */
266 | test('#changeFailure', function () {
267 | var user = 'test8' + new Date().getTime();
268 | var email = user + '@restmail.net';
269 | var password = 'iliketurtles';
270 | var newPassword = 'ilikefoxes';
271 | var wrongPassword = '12345678';
272 | var uid;
273 | var oldCreds;
274 |
275 | return respond(client.signUp(email, password), RequestMocks.signUp)
276 | .then(function (result) {
277 | uid = result.uid;
278 |
279 | return respond(mail.wait(user), RequestMocks.mail);
280 | })
281 | .then(function (emails) {
282 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
283 |
284 | return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
285 | })
286 | .then(function () {
287 | return respond(client._passwordChangeStart(email, password), RequestMocks.passwordChangeStart);
288 | })
289 | .then(function (credentials) {
290 | oldCreds = credentials;
291 | assert.equal(credentials.emailToHashWith, email);
292 | return respond(client._passwordChangeKeys(oldCreds), RequestMocks.accountKeys);
293 | })
294 | .then(function (keys) {
295 |
296 | return respond(client._passwordChangeFinish(email, newPassword, oldCreds, keys), RequestMocks.passwordChangeFinish);
297 | })
298 | .then(function (result) {
299 | assert.ok(result);
300 |
301 | return respond(client.signIn(email, wrongPassword), ErrorMocks.accountIncorrectPassword);
302 | })
303 | .then(
304 | function () {
305 | assert.fail();
306 | },
307 | function (error) {
308 | assert.ok(error);
309 | assert.equal(error.message, 'Incorrect password', '== Password is incorrect');
310 | assert.equal(error.code, 400, '== Correct status code');
311 | }
312 | );
313 | });
314 | });
315 | }
316 | });
317 |
--------------------------------------------------------------------------------
/tests/lib/push-constants.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([], function () {
6 | return {
7 | DEVICE_CALLBACK: 'https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef',
8 | DEVICE_ID: '0f7aa00356e5416e82b3bef7bc409eef',
9 | DEVICE_NAME: 'My Phone',
10 | DEVICE_NAME_2: 'My Android Phone',
11 | DEVICE_PUBLIC_KEY: 'BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc',
12 | DEVICE_AUTH_KEY: 'GSsIiaD2Mr83iPqwFNK4rw',
13 | DEVICE_TYPE: 'mobile'
14 | };
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/tests/lib/recoveryCodes.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/addons/sinon',
10 | 'node_modules/otplib/otplib-browser'
11 | ], function (tdd, assert, Environment, sinon, otplib) {
12 |
13 | with (tdd) {
14 | suite('recovery codes', function () {
15 | var account;
16 | var accountHelper;
17 | var respond;
18 | var client;
19 | var RequestMocks;
20 | var env;
21 | var xhr;
22 | var xhrOpen;
23 | var xhrSend;
24 | var recoveryCodes;
25 | var metricsContext;
26 |
27 | beforeEach(function () {
28 | env = new Environment();
29 | accountHelper = env.accountHelper;
30 | respond = env.respond;
31 | client = env.client;
32 | RequestMocks = env.RequestMocks;
33 | metricsContext = {
34 | flowBeginTime: Date.now(),
35 | flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
36 | };
37 |
38 | return accountHelper.newVerifiedAccount()
39 | .then(function (newAccount) {
40 | account = newAccount;
41 | return respond(client.createTotpToken(account.signIn.sessionToken), RequestMocks.createTotpToken);
42 | })
43 | .then(function (res) {
44 | assert.ok(res.qrCodeUrl, 'should return QR code data encoded url');
45 | assert.ok(res.secret, 'should return secret that is encoded in url');
46 |
47 | var authenticator = new otplib.authenticator.Authenticator();
48 | authenticator.options = otplib.authenticator.options;
49 |
50 | var code = authenticator.generate(res.secret);
51 | return respond(client.verifyTotpCode(account.signIn.sessionToken, code), RequestMocks.verifyTotpCodeTrueEnableToken);
52 | })
53 | .then(function (res) {
54 | assert.equal(res.recoveryCodes.length, 8, 'should return recovery codes');
55 | recoveryCodes = res.recoveryCodes;
56 |
57 | xhr = env.xhr;
58 | xhrOpen = sinon.spy(xhr.prototype, 'open');
59 | xhrSend = sinon.spy(xhr.prototype, 'send');
60 | });
61 | });
62 |
63 | afterEach(function () {
64 | xhrOpen.restore();
65 | xhrSend.restore();
66 | });
67 |
68 | test('#consumeRecoveryCode - fails for invalid code', function () {
69 | return respond(client.consumeRecoveryCode(account.signIn.sessionToken, '00000000'), RequestMocks.consumeRecoveryCodeInvalidCode)
70 | .then(assert.fail, function (err) {
71 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
72 | assert.include(xhrOpen.args[0][1], '/session/verify/recoveryCode', 'path is correct');
73 | assert.equal(err.errno, 156, 'invalid recovery code errno');
74 | });
75 | });
76 |
77 | test('#consumeRecoveryCode - consumes valid code', function () {
78 | var code = recoveryCodes[0];
79 | return respond(client.consumeRecoveryCode(account.signIn.sessionToken, code, {metricsContext: metricsContext}), RequestMocks.consumeRecoveryCodeSuccess)
80 | .then(function (res) {
81 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
82 | assert.include(xhrOpen.args[0][1], '/session/verify/recoveryCode', 'path is correct');
83 | var sentData = JSON.parse(xhrSend.args[0][0]);
84 | assert.lengthOf(Object.keys(sentData), 1);
85 | assert.equal(sentData.code, code, 'code is correct');
86 |
87 | assert.equal(res.remaining, 7, 'correct remaining recovery codes');
88 | });
89 | });
90 |
91 | test('#replaceRecoveryCodes - replaces current recovery codes', function () {
92 | return respond(client.replaceRecoveryCodes(account.signIn.sessionToken), RequestMocks.replaceRecoveryCodesSuccessNew)
93 | .then(function (res) {
94 | assert.equal(xhrOpen.args[0][0], 'GET', 'method is correct');
95 | assert.include(xhrOpen.args[0][1], '/recoveryCodes', 'path is correct');
96 |
97 | assert.equal(res.recoveryCodes.length, 8, 'should return recovery codes');
98 | assert.notDeepEqual(res.recoveryCodes, recoveryCodes, 'should not be the same codes');
99 | });
100 | });
101 | });
102 | }
103 | });
104 |
--------------------------------------------------------------------------------
/tests/lib/recoveryEmail.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | with (tdd) {
12 | suite('recoveryEmail', function () {
13 | var accountHelper;
14 | var respond;
15 | var mail;
16 | var client;
17 | var RequestMocks;
18 |
19 | beforeEach(function () {
20 | var env = new Environment();
21 | accountHelper = env.accountHelper;
22 | respond = env.respond;
23 | mail = env.mail;
24 | client = env.client;
25 | RequestMocks = env.RequestMocks;
26 | });
27 |
28 | test('#recoveryEmail - recoveryEmailResendCode', function () {
29 | var user;
30 |
31 | return accountHelper.newUnverifiedAccount()
32 | .then(function (account) {
33 | user = account.input.user;
34 |
35 | return respond(client.recoveryEmailResendCode(account.signIn.sessionToken), RequestMocks.recoveryEmailResendCode);
36 | })
37 | .then(
38 | function(res) {
39 | assert.ok(res);
40 |
41 | return respond(mail.wait(user, 3), RequestMocks.resetMailrecoveryEmailResendCode);
42 | })
43 | .then(
44 | function (emails) {
45 | // second email, the code is resent.
46 | var code = emails[2].html.match(/code=([A-Za-z0-9]+)/)[1];
47 | assert.ok(code, 'code is returned');
48 | },
49 | assert.notOk
50 | );
51 | });
52 |
53 | test('#recoveryEmailResendCode with service, redirectTo, type, and resume', function () {
54 | var user;
55 | var opts = {
56 | service: 'sync',
57 | redirectTo: 'https://sync.127.0.0.1/after_reset',
58 | resume: 'resumejwt',
59 | type: 'upgradeSession'
60 | };
61 |
62 | return accountHelper.newUnverifiedAccount()
63 | .then(function (account) {
64 | user = account.input.user;
65 |
66 | return respond(client.recoveryEmailResendCode(account.signIn.sessionToken, opts), RequestMocks.recoveryEmailResendCode);
67 | })
68 | .then(
69 | function(res) {
70 | assert.ok(res);
71 |
72 | return respond(mail.wait(user, 3), RequestMocks.resetMailWithServiceAndRedirectNoSignup);
73 | })
74 | .then(
75 | function (emails) {
76 | // second email, the code is resent.
77 | var code = emails[2].html.match(/code=([A-Za-z0-9]+)/);
78 | assert.ok(code, 'code found');
79 | var service = emails[2].html.match(/service=([A-Za-z0-9]+)/);
80 | assert.ok(service, 'service found');
81 | var redirectTo = emails[2].html.match(/redirectTo=([A-Za-z0-9]+)/);
82 | assert.ok(redirectTo, 'redirectTo found');
83 | var resume = emails[2].html.match(/resume=([A-Za-z0-9]+)/);
84 | assert.ok(resume, 'resume found');
85 |
86 | assert.ok(code[1], 'code is returned');
87 | assert.equal(service[1], 'sync', 'service is returned');
88 | assert.equal(redirectTo[1], 'https', 'redirectTo is returned');
89 | assert.equal(resume[1], 'resumejwt', 'resume is returned');
90 | },
91 | assert.notOk
92 | );
93 | });
94 |
95 | });
96 | }
97 | });
98 |
--------------------------------------------------------------------------------
/tests/lib/recoveryKeys.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/addons/sinon'
10 | ], function (tdd, assert, Environment, sinon) {
11 |
12 | with (tdd) {
13 | suite('recovery key', function () {
14 | var account;
15 | var accountHelper;
16 | var respond;
17 | var client;
18 | var email;
19 | var RequestMocks;
20 | var env;
21 | var xhr;
22 | var xhrOpen;
23 | var xhrSend;
24 | var keys;
25 | var passwordForgotToken;
26 | var accountResetToken;
27 | var mail;
28 | var newPassword = '~(_8^(I)';
29 | var recoveryKeyId = 'edc243a821582ee9e979583be9989ee7';
30 | var bundle = 'eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIiwia2lkIjoiODE4NDIwZjBkYTU4ZDIwZjZhZTR' +
31 | 'kMmM5YmVhYjkyNTEifQ..D29EXHp8ubLvftaZ.xHJd2Nl2Uco2RyywYPLkUU7fHpgO2FztY12Zjpq1ffiyLRIUcQVfmiNC6aMiHB' +
32 | 'l7Hp-lXEbb5mR1uXHrTH9iRXEBVaAfyf9KEAWOukWGVSH8EaOkr7cfu2Yr0K93Ec8glsssjiKp8NGB8VKTUJ-lmBv2cIrG68V4eTUVDo' +
33 | 'DhMbXhrF-Mv4JNeh338pPeatTnyg.Ow2bhEYWxzxfSPMxVwKmSA';
34 |
35 | beforeEach(function () {
36 | env = new Environment();
37 | accountHelper = env.accountHelper;
38 | respond = env.respond;
39 | client = env.client;
40 | RequestMocks = env.RequestMocks;
41 | mail = env.mail;
42 |
43 | return accountHelper.newVerifiedAccount()
44 | .then(function (newAccount) {
45 | account = newAccount;
46 | email = account.input.email;
47 | return respond(client.accountKeys(account.signIn.keyFetchToken, account.signIn.unwrapBKey), RequestMocks.accountKeys);
48 | })
49 | .then(function (result) {
50 | keys = result;
51 | xhr = env.xhr;
52 | xhrOpen = sinon.spy(xhr.prototype, 'open');
53 | xhrSend = sinon.spy(xhr.prototype, 'send');
54 | });
55 | });
56 |
57 | afterEach(function () {
58 | xhrOpen.restore();
59 | xhrSend.restore();
60 | });
61 |
62 | test('#can create and get a recovery key that can be used to reset an account', function () {
63 | return respond(client.createRecoveryKey(account.signIn.sessionToken, recoveryKeyId, bundle), RequestMocks.createRecoveryKey)
64 | .then(function (res) {
65 | assert.ok(res);
66 | return respond(client.passwordForgotSendCode(email), RequestMocks.passwordForgotSendCode);
67 | })
68 | .then(function (result) {
69 | passwordForgotToken = result.passwordForgotToken;
70 | assert.ok(passwordForgotToken, 'passwordForgotToken is returned');
71 |
72 | return respond(mail.wait(account.input.user, 4), RequestMocks.resetMailpasswordForgotRecoveryKey);
73 | })
74 | .then(function (emails) {
75 | var code = emails[3].html.match(/code=([A-Za-z0-9]+)/)[1];
76 | assert.ok(code, 'code is returned: ' + code);
77 |
78 | return respond(client.passwordForgotVerifyCode(code, passwordForgotToken, {accountResetWithRecoveryKey: true}), RequestMocks.passwordForgotVerifyCode);
79 | })
80 | .then(function(result) {
81 | accountResetToken = result.accountResetToken;
82 | assert.ok(accountResetToken, 'accountResetToken is returned');
83 |
84 | assert.equal(xhrOpen.args[3][0], 'POST', 'method is correct');
85 | assert.include(xhrOpen.args[3][1], '/password/forgot/verify_code', 'path is correct');
86 | var sentData = JSON.parse(xhrSend.args[3][0]);
87 | assert.equal(Object.keys(sentData).length, 2);
88 | assert.equal(sentData.accountResetWithRecoveryKey, true, 'param set');
89 | return respond(client.getRecoveryKey(accountResetToken, recoveryKeyId), RequestMocks.getRecoveryKey);
90 | })
91 | .then(function (res) {
92 | assert.equal(xhrOpen.args[4][0], 'GET', 'method is correct');
93 | assert.include(xhrOpen.args[4][1], '/recoveryKey/' + recoveryKeyId, 'path is correct');
94 | assert.ok(res.recoveryData, 'contains recovery data');
95 |
96 | var options = {
97 | keys: true,
98 | metricsContext: {
99 | deviceId: '0123456789abcdef0123456789abcdef',
100 | flowBeginTime: 1480615985437,
101 | flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
102 | utmCampaign: 'mock-campaign',
103 | utmContent: 'mock-content',
104 | utmMedium: 'mock-medium',
105 | utmSource: 'mock-source',
106 | utmTerm: 'mock-term'
107 | },
108 | sessionToken: true
109 | };
110 | return respond(client.resetPasswordWithRecoveryKey(accountResetToken, email, newPassword, recoveryKeyId, keys, options), RequestMocks.accountReset);
111 | })
112 | .then(function (res) {
113 | assert.ok(res.keyFetchToken);
114 | assert.ok(res.sessionToken);
115 | assert.ok(res.unwrapBKey);
116 | assert.ok(res.uid);
117 |
118 | // Attempt to login with new password and retrieve keys
119 | return respond(client.signIn(email, newPassword, {keys: true}), RequestMocks.signInWithKeys);
120 | })
121 | .then(function (res) {
122 | return respond(client.accountKeys(res.keyFetchToken, res.unwrapBKey), RequestMocks.accountKeys);
123 | })
124 | .then(function (res) {
125 | if (!env.useRemoteServer) {
126 | assert.ok(res.kB, 'kB exists');
127 | } else {
128 | assert.equal(res.kB, keys.kB, 'kB is equal to original kB');
129 | }
130 | });
131 | });
132 |
133 | test('#can create and delete recovery key', function () {
134 | return respond(client.createRecoveryKey(account.signIn.sessionToken, recoveryKeyId, bundle), RequestMocks.createRecoveryKey)
135 | .then(function (res) {
136 | assert.ok(res);
137 | return respond(client.deleteRecoveryKey(account.signIn.sessionToken), RequestMocks.deleteRecoveryKey);
138 | })
139 | .then(function (res) {
140 | assert.ok(res);
141 | assert.equal(xhrOpen.args[1][0], 'DELETE', 'method is correct');
142 | assert.include(xhrOpen.args[1][1], '/recoveryKey', 'path is correct');
143 | });
144 | });
145 |
146 | test('#can check if recovery exist using sessionToken', function () {
147 | return respond(client.recoveryKeyExists(account.signIn.sessionToken), RequestMocks.recoveryKeyExistsFalse)
148 | .then(function (res) {
149 | assert.equal(res.exists, false, 'recovery key does not exist');
150 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
151 | assert.include(xhrOpen.args[0][1], '/recoveryKey/exists', 'path is correct');
152 | return respond(client.createRecoveryKey(account.signIn.sessionToken, recoveryKeyId, bundle), RequestMocks.createRecoveryKey);
153 | })
154 | .then(function (res) {
155 | assert.ok(res);
156 | return respond(client.recoveryKeyExists(account.signIn.sessionToken), RequestMocks.recoveryKeyExistsTrue);
157 | })
158 | .then(function (res) {
159 | assert.equal(res.exists, true, 'recovery key exists');
160 | assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
161 | assert.include(xhrOpen.args[2][1], '/recoveryKey/exists', 'path is correct');
162 | });
163 | });
164 |
165 | test('#can check if recovery exist using email', function () {
166 | return respond(client.recoveryKeyExists(undefined, account.input.email), RequestMocks.recoveryKeyExistsFalse)
167 | .then(function (res) {
168 | assert.equal(res.exists, false, 'recovery key does not exist');
169 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
170 | assert.include(xhrOpen.args[0][1], '/recoveryKey/exists', 'path is correct');
171 |
172 | return respond(client.createRecoveryKey(account.signIn.sessionToken, recoveryKeyId, bundle), RequestMocks.createRecoveryKey);
173 | })
174 | .then(function (res) {
175 | assert.ok(res);
176 | return respond(client.recoveryKeyExists(undefined, account.input.email), RequestMocks.recoveryKeyExistsTrue);
177 | })
178 | .then(function (res) {
179 | assert.equal(res.exists, true, 'recovery key exists');
180 | assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
181 | assert.include(xhrOpen.args[2][1], '/recoveryKey/exists', 'path is correct');
182 | });
183 | });
184 | });
185 | }
186 | });
187 |
--------------------------------------------------------------------------------
/tests/lib/request.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'tests/addons/sinon',
7 | 'intern!tdd',
8 | 'intern/chai!assert',
9 | 'tests/addons/environment',
10 | 'client/lib/request',
11 | 'tests/mocks/errors'
12 | ], function (sinon, tdd, assert, Environment, Request, ErrorMocks) {
13 | with (tdd) {
14 | suite('request module', function () {
15 | var RequestMocks;
16 | var request;
17 | var env;
18 |
19 | beforeEach(function () {
20 | env = new Environment();
21 | RequestMocks = env.RequestMocks;
22 | request = new Request(env.authServerUrl, env.xhr);
23 | });
24 |
25 | test('#heartbeat', function () {
26 | var heartbeatRequest = env.respond(request.send('/__heartbeat__', 'GET'), RequestMocks.heartbeat)
27 | .then(
28 | function (res) {
29 | assert.ok(res);
30 | },
31 | assert.notOk
32 | );
33 |
34 | return heartbeatRequest;
35 | });
36 |
37 | test('#error', function () {
38 | request = new Request('http://', env.xhr);
39 |
40 | request.send('/', 'GET')
41 | .then(
42 | assert.notOk,
43 | function () {
44 | assert.ok(true);
45 | }
46 | );
47 |
48 | });
49 |
50 | test('#timeout', function () {
51 | request = new Request('http://google.com:81', env.xhr, { timeout: 200 });
52 |
53 | var timeoutRequest = env.respond(request.send('/', 'GET'), ErrorMocks.timeout);
54 |
55 | return timeoutRequest.then(
56 | assert.notOk,
57 | function (err) {
58 | assert.equal(err.error, 'Timeout error');
59 | }
60 | );
61 | });
62 |
63 | test('#bad response format error', function () {
64 | request = new Request('http://example.com/', env.xhr);
65 |
66 | // Trigger an error response that's in HTML
67 | var response = env.respond(request.send('/nonexistent', 'GET'), ErrorMocks.badResponseFormat);
68 |
69 | return response.then(
70 | assert.notOk,
71 | function (err) {
72 | assert.equal(err.error, 'Unknown error');
73 | }
74 | );
75 | });
76 |
77 | test('#ensure is usable', function () {
78 | request = new Request('http://google.com:81', env.xhr, { timeout: 200 });
79 | sinon.stub(env.xhr.prototype, 'open').throws();
80 |
81 | return env.respond(request.send('/__heartbeat__', 'GET'), RequestMocks.heartbeat)
82 | .then(
83 | null,
84 | function (err) {
85 | assert.ok(err);
86 | env.xhr.prototype.open.restore();
87 | }
88 | );
89 | });
90 | });
91 | }
92 | });
93 |
--------------------------------------------------------------------------------
/tests/lib/session.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/addons/sinon'
10 | ], function (tdd, assert, Environment, sinon) {
11 |
12 | with (tdd) {
13 | suite('session', function () {
14 | var accountHelper;
15 | var respond;
16 | var requests;
17 | var client;
18 | var RequestMocks;
19 | var ErrorMocks;
20 | var xhr;
21 |
22 | beforeEach(function () {
23 | var env = new Environment();
24 | accountHelper = env.accountHelper;
25 | respond = env.respond;
26 | requests = env.requests;
27 | client = env.client;
28 | RequestMocks = env.RequestMocks;
29 | ErrorMocks = env.ErrorMocks;
30 | xhr = env.xhr;
31 | sinon.spy(xhr.prototype, 'open');
32 | sinon.spy(xhr.prototype, 'send');
33 | });
34 |
35 | afterEach(function () {
36 | xhr.prototype.open.restore();
37 | xhr.prototype.send.restore();
38 | });
39 |
40 | test('#destroy', function () {
41 |
42 | return accountHelper.newVerifiedAccount()
43 | .then(function (account) {
44 |
45 | return respond(client.sessionDestroy(account.signIn.sessionToken), RequestMocks.sessionDestroy);
46 | })
47 | .then(
48 | function(res) {
49 | assert.ok(res, 'got response');
50 | },
51 | assert.notOk
52 | );
53 | });
54 |
55 | test('#status', function () {
56 |
57 | return accountHelper.newVerifiedAccount()
58 | .then(function (account) {
59 |
60 | return respond(client.sessionStatus(account.signIn.sessionToken), RequestMocks.sessionStatus);
61 | })
62 | .then(
63 | function(res) {
64 | assert.isNotNull(res);
65 | },
66 | assert.notOk
67 | );
68 | });
69 |
70 | test('#status error with a false token', function () {
71 |
72 | return accountHelper.newVerifiedAccount()
73 | .then(function () {
74 | var fakeToken = 'e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f10000';
75 |
76 | return respond(client.passwordForgotStatus(fakeToken), ErrorMocks.invalidAuthToken);
77 | })
78 | .then(
79 | assert.notOk,
80 | function (err) {
81 | assert.equal(err.code, 401);
82 | assert.equal(err.errno, 110);
83 | }
84 | );
85 | });
86 |
87 | test('#sessions', function () {
88 |
89 | return accountHelper.newVerifiedAccount()
90 | .then(function (account) {
91 | return respond(client.sessions(account.signIn.sessionToken), RequestMocks.sessions);
92 | })
93 | .then(
94 | function (res) {
95 | assert.equal(res.length, 2);
96 | var s = res[0];
97 | assert.ok(s.id);
98 | assert.ok(s.deviceType);
99 | assert.equal(s.isDevice, false);
100 | assert.ok(s.lastAccessTime);
101 | assert.ok(s.lastAccessTimeFormatted);
102 | },
103 | assert.notOk
104 | );
105 | });
106 |
107 |
108 | test('#sessions error', function () {
109 |
110 | return accountHelper.newVerifiedAccount()
111 | .then(function (account) {
112 | var fakeToken = 'e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f10000';
113 |
114 | return respond(client.sessions(fakeToken), ErrorMocks.invalidAuthToken);
115 | })
116 | .then(
117 | assert.notOk,
118 | function (err) {
119 | assert.equal(err.code, 401);
120 | assert.equal(err.errno, 110);
121 | }
122 | );
123 | });
124 |
125 | test('#reauth', function () {
126 |
127 | return accountHelper.newVerifiedAccount()
128 | .then(function (account) {
129 | var email = account.input.email;
130 | var password = account.input.password;
131 |
132 | return respond(client.sessionReauth(account.signIn.sessionToken, email, password), RequestMocks.sessionReauth)
133 | .then(
134 | function(res) {
135 | assert.ok(res.uid);
136 | assert.ok(res.verified);
137 | assert.ok(res.authAt);
138 | assert.notOk(res.keyFetchToken);
139 | assert.notOk(res.unwrapBKey);
140 |
141 | var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
142 | assert.equal(args[0], 'POST');
143 | assert.include(args[1], '/session/reauth');
144 |
145 | var payload = JSON.parse(xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]);
146 | assert.equal(Object.keys(payload).length, 2);
147 | assert.equal(payload.email, email);
148 | assert.equal(payload.authPW.length, 64);
149 | },
150 | assert.notOk
151 | );
152 | });
153 | });
154 |
155 | test('#reauth with keys', function () {
156 |
157 | return accountHelper.newVerifiedAccount()
158 | .then(function (account) {
159 | var email = account.input.email;
160 | var password = account.input.password;
161 |
162 | return respond(client.sessionReauth(account.signIn.sessionToken, email, password, {keys: true}), RequestMocks.sessionReauthWithKeys)
163 | .then(
164 | function (res) {
165 | assert.ok(res.uid);
166 | assert.ok(res.verified);
167 | assert.ok(res.authAt);
168 | assert.ok(res.keyFetchToken);
169 | assert.ok(res.unwrapBKey);
170 |
171 | var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
172 | assert.equal(args[0], 'POST');
173 | assert.include(args[1], '/session/reauth?keys=true');
174 |
175 | var payload = JSON.parse(xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]);
176 | assert.equal(Object.keys(payload).length, 2);
177 | assert.equal(payload.email, email);
178 | assert.equal(payload.authPW.length, 64);
179 | },
180 | assert.notOk
181 | );
182 | });
183 | });
184 |
185 | test('#reauth with incorrect password', function () {
186 |
187 | return accountHelper.newVerifiedAccount()
188 | .then(function (account) {
189 | var email = account.input.email;
190 | var password = 'incorrect password';
191 |
192 | return respond(client.sessionReauth(account.signIn.sessionToken, email, password), ErrorMocks.accountIncorrectPassword)
193 | .then(
194 | function () {
195 | assert.fail();
196 | },
197 | function (res) {
198 | assert.equal(res.code, 400);
199 | assert.equal(res.errno, 103);
200 | }
201 | );
202 | });
203 | });
204 |
205 | test('#reauth with incorrect email case', function () {
206 |
207 | return accountHelper.newVerifiedAccount()
208 | .then(function (account) {
209 | var numSetupRequests = requests ? requests.length : null;
210 | var sessionToken = account.signIn.sessionToken;
211 | var incorrectCaseEmail = account.input.email.charAt(0).toUpperCase() + account.input.email.slice(1);
212 | var password = account.input.password;
213 |
214 | respond(ErrorMocks.incorrectEmailCase);
215 | return respond(client.sessionReauth(sessionToken, incorrectCaseEmail, password), RequestMocks.sessionReauth)
216 | .then(
217 | function (res) {
218 | assert.property(res, 'uid');
219 | assert.property(res, 'verified');
220 | assert.property(res, 'authAt');
221 |
222 | if (requests) {
223 | assert.equal(requests.length - numSetupRequests, 2);
224 | }
225 |
226 | var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 2];
227 | assert.equal(args[0], 'POST');
228 | assert.include(args[1], '/session/reauth');
229 |
230 | var payload = JSON.parse(xhr.prototype.send.args[xhr.prototype.send.args.length - 2][0]);
231 | assert.equal(Object.keys(payload).length, 2);
232 | assert.equal(payload.email, incorrectCaseEmail);
233 | assert.equal(payload.authPW.length, 64);
234 |
235 | args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
236 | assert.equal(args[0], 'POST');
237 | assert.include(args[1], '/session/reauth');
238 |
239 | payload = JSON.parse(xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]);
240 | assert.equal(Object.keys(payload).length, 3);
241 | assert.notEqual(payload.email, incorrectCaseEmail);
242 | assert.equal(payload.originalLoginEmail, incorrectCaseEmail);
243 | assert.equal(payload.authPW.length, 64);
244 | },
245 | assert.notOk
246 | );
247 | });
248 | });
249 |
250 | test('#reauth with incorrect email case with skipCaseError', function () {
251 |
252 | return accountHelper.newVerifiedAccount()
253 | .then(function (account) {
254 | var numSetupRequests = requests ? requests.length : null;
255 | var sessionToken = account.signIn.sessionToken;
256 | var incorrectCaseEmail = account.input.email.charAt(0).toUpperCase() + account.input.email.slice(1);
257 | var password = account.input.password;
258 |
259 | return respond(client.sessionReauth(sessionToken, incorrectCaseEmail, password, {skipCaseError: true}), ErrorMocks.incorrectEmailCase)
260 | .then(
261 | function () {
262 | assert.fail();
263 | },
264 | function (res) {
265 | assert.equal(res.code, 400);
266 | assert.equal(res.errno, 120);
267 |
268 | if (requests) {
269 | assert.equal(requests.length - numSetupRequests, 1);
270 | }
271 |
272 | var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
273 | assert.equal(args[0], 'POST');
274 | assert.include(args[1], '/session/reauth');
275 |
276 | var payload = JSON.parse(xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]);
277 | assert.equal(Object.keys(payload).length, 2);
278 | assert.equal(payload.email, incorrectCaseEmail);
279 | assert.equal(payload.authPW.length, 64);
280 | }
281 | );
282 | });
283 | });
284 |
285 | test('#reauth with all the options', function () {
286 |
287 | return accountHelper.newVerifiedAccount()
288 | .then(function (account) {
289 | var sessionToken = account.signIn.sessionToken;
290 | var email = account.input.email;
291 | var password = account.input.password;
292 | var options = {
293 | keys: true,
294 | metricsContext: {
295 | entrypoint: 'mock-entrypoint',
296 | utmCampaign: 'mock-utm-campaign',
297 | utmContent: 'mock-utm-content',
298 | utmMedium: 'mock-utm-medium',
299 | utmSource: 'mock-utm-source',
300 | utmTerm: 'mock-utm-term'
301 | },
302 | originalLoginEmail: email.toUpperCase(),
303 | reason: 'password_change',
304 | redirectTo: 'http://127.0.0.1',
305 | resume: 'RESUME_TOKEN',
306 | service: 'sync',
307 | verificationMethod: 'email-2fa'
308 | };
309 |
310 | return respond(client.sessionReauth(sessionToken, email, password, options), RequestMocks.sessionReauthWithKeys)
311 | .then(
312 | function (res) {
313 | assert.ok(res.uid);
314 | assert.ok(res.verified);
315 | assert.ok(res.authAt);
316 | assert.ok(res.keyFetchToken);
317 | assert.ok(res.unwrapBKey);
318 |
319 | var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
320 | assert.equal(args[0], 'POST');
321 | assert.include(args[1], '/session/reauth?keys=true');
322 |
323 | var payload = JSON.parse(xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]);
324 | assert.equal(Object.keys(payload).length, 9);
325 | assert.equal(payload.email, email);
326 | assert.equal(payload.authPW.length, 64);
327 | assert.deepEqual(payload.metricsContext, options.metricsContext);
328 | assert.equal(payload.originalLoginEmail, options.originalLoginEmail);
329 | assert.equal(payload.reason, options.reason);
330 | assert.equal(payload.redirectTo, options.redirectTo);
331 | assert.equal(payload.resume, options.resume);
332 | assert.equal(payload.service, options.service);
333 | assert.equal(payload.verificationMethod, options.verificationMethod);
334 | }
335 | );
336 | });
337 | });
338 | });
339 | }
340 | });
341 |
--------------------------------------------------------------------------------
/tests/lib/signIn.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/lib/push-constants'
10 | ], function (tdd, assert, Environment, PushTestConstants) {
11 |
12 | with (tdd) {
13 | suite('signIn', function () {
14 | var ErrorMocks;
15 | var RequestMocks;
16 | var accountHelper;
17 | var client;
18 | var mail;
19 | var respond;
20 | var requests;
21 |
22 | beforeEach(function () {
23 | var env = new Environment();
24 | ErrorMocks = env.ErrorMocks;
25 | RequestMocks = env.RequestMocks;
26 | accountHelper = env.accountHelper;
27 | client = env.client;
28 | mail = env.mail;
29 | respond = env.respond;
30 | requests = env.requests;
31 | });
32 |
33 | test('#basic', function () {
34 | var email = 'test' + new Date().getTime() + '@restmail.net';
35 | var password = 'iliketurtles';
36 |
37 | return respond(client.signUp(email, password), RequestMocks.signUp)
38 | .then(function () {
39 |
40 | return respond(client.signIn(email, password), RequestMocks.signIn);
41 | })
42 | .then(
43 | function (res) {
44 | assert.ok(res.sessionToken);
45 | },
46 | assert.notOk
47 | );
48 | });
49 |
50 | test('#with keys', function () {
51 | var email = 'test' + new Date().getTime() + '@restmail.net';
52 | var password = 'iliketurtles';
53 |
54 | return respond(client.signUp(email, password), RequestMocks.signUp)
55 | .then(function (res) {
56 | return respond(client.signIn(email, password, {keys: true}), RequestMocks.signInWithKeys);
57 | })
58 | .then(
59 | function (res) {
60 | assert.ok(res.sessionToken);
61 | assert.ok(res.keyFetchToken);
62 | assert.ok(res.unwrapBKey);
63 | },
64 | assert.notOk
65 | );
66 | });
67 |
68 | test('#with service', function () {
69 | var email = 'test' + new Date().getTime() + '@restmail.net';
70 | var password = 'iliketurtles';
71 |
72 | return respond(client.signUp(email, password), RequestMocks.signUp)
73 | .then(function () {
74 | return respond(client.signIn(email, password, {service: 'sync'}), RequestMocks.signIn);
75 | });
76 | });
77 |
78 | test('#with reason', function () {
79 | var email = 'test' + new Date().getTime() + '@restmail.net';
80 | var password = 'iliketurtles';
81 |
82 | return respond(client.signUp(email, password), RequestMocks.signUp)
83 | .then(function () {
84 | return respond(client.signIn(email, password, {reason: 'password_change'}), RequestMocks.signIn);
85 | });
86 | });
87 |
88 | test('#with Sync/redirectTo', function () {
89 | var user = 'confirm' + new Date().getTime();
90 | var email = user + '@restmail.net';
91 | var password = 'iliketurtles';
92 | var opts = {
93 | keys: true,
94 | metricsContext: {
95 | context: 'fx_desktop_v2'
96 | },
97 | redirectTo: 'http://sync.127.0.0.1/after_reset',
98 | service: 'sync'
99 | };
100 |
101 | return respond(client.signUp(email, password, { preVerified: true }), RequestMocks.signUp)
102 | .then(function () {
103 |
104 | return respond(client.signIn(email, password, opts), RequestMocks.signIn);
105 | })
106 | .then(function (res) {
107 | assert.ok(res.uid);
108 | return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
109 | })
110 | .then(
111 | function (emails) {
112 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
113 | var redirectTo = emails[0].html.match(/redirectTo=([A-Za-z0-9]+)/)[1];
114 |
115 | assert.ok(code, 'code is returned');
116 | assert.ok(redirectTo, 'redirectTo is returned');
117 |
118 | },
119 | assert.notOk
120 | );
121 | });
122 |
123 | test('#with Sync/resume', function () {
124 | var user = 'confirm' + new Date().getTime();
125 | var email = user + '@restmail.net';
126 | var password = 'iliketurtles';
127 | var opts = {
128 | keys: true,
129 | metricsContext: {
130 | context: 'fx_desktop_v2'
131 | },
132 | redirectTo: 'http://sync.127.0.0.1/after_reset',
133 | resume: 'resumejwt',
134 | service: 'sync'
135 | };
136 |
137 | return respond(client.signUp(email, password, { preVerified: true }), RequestMocks.signUp)
138 | .then(function () {
139 | return respond(client.signIn(email, password, opts), RequestMocks.signIn);
140 | })
141 | .then(function (res) {
142 | assert.ok(res.uid);
143 | return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
144 | })
145 | .then(
146 | function (emails) {
147 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
148 | var resume = emails[0].html.match(/resume=([A-Za-z0-9]+)/)[1];
149 |
150 | assert.ok(code, 'code is returned');
151 | assert.ok(resume, 'resume is returned');
152 |
153 | },
154 | assert.notOk
155 | );
156 | });
157 |
158 |
159 | test('#incorrect email case', function () {
160 |
161 | return accountHelper.newVerifiedAccount()
162 | .then(function (account) {
163 | var numSetupRequests = requests ? requests.length : null;
164 | var incorrectCaseEmail = account.input.email.charAt(0).toUpperCase() + account.input.email.slice(1);
165 |
166 | respond(ErrorMocks.incorrectEmailCase);
167 | return respond(client.signIn(incorrectCaseEmail, account.input.password), RequestMocks.signIn)
168 | .then(
169 | function (res) {
170 | assert.property(res, 'sessionToken');
171 | if (requests) {
172 | assert.equal(requests.length - numSetupRequests, 2);
173 | }
174 | },
175 | assert.notOk
176 | );
177 | });
178 | });
179 |
180 | test('#incorrect email case with skipCaseError', function () {
181 |
182 | return accountHelper.newVerifiedAccount()
183 | .then(function (account) {
184 | var numSetupRequests = requests ? requests.length : null;
185 | var incorrectCaseEmail = account.input.email.charAt(0).toUpperCase() + account.input.email.slice(1);
186 |
187 | return respond(client.signIn(incorrectCaseEmail, account.input.password, {skipCaseError: true}), ErrorMocks.incorrectEmailCase)
188 | .then(
189 | function () {
190 | assert.fail();
191 | },
192 | function (res) {
193 | assert.equal(res.code, 400);
194 | assert.equal(res.errno, 120);
195 | if (requests) {
196 | assert.equal(requests.length - numSetupRequests, 1);
197 | }
198 | }
199 | );
200 | });
201 | });
202 |
203 | test('#incorrectPassword', function () {
204 |
205 | return accountHelper.newVerifiedAccount()
206 | .then(function (account) {
207 | return respond(client.signIn(account.input.email, 'wrong password'), ErrorMocks.accountIncorrectPassword);
208 | })
209 | .then(
210 | function () {
211 | assert.fail();
212 | },
213 | function (res) {
214 | assert.equal(res.code, 400);
215 | assert.equal(res.errno, 103);
216 | }
217 | );
218 | });
219 |
220 | test('#with metricsContext metadata', function () {
221 | var email = 'test' + new Date().getTime() + '@restmail.net';
222 | var password = 'iliketurtles';
223 |
224 | return respond(client.signUp(email, password), RequestMocks.signUp)
225 | .then(function () {
226 | return respond(
227 | client.signIn(email, password, {
228 | metricsContext: {},
229 | reason: 'signin'
230 | }),
231 | RequestMocks.signIn
232 | );
233 | })
234 | .then(
235 | function (resp) {
236 | assert.ok(resp);
237 | },
238 | assert.notOk
239 | );
240 | });
241 | });
242 | }
243 | });
244 |
--------------------------------------------------------------------------------
/tests/lib/signUp.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/addons/sinon'
10 | ], function (tdd, assert, Environment, sinon) {
11 |
12 | with (tdd) {
13 | suite('signUp', function () {
14 | var accountHelper;
15 | var respond;
16 | var mail;
17 | var client;
18 | var RequestMocks;
19 | var ErrorMocks;
20 | var xhr;
21 | var xhrOpen;
22 | var xhrSend;
23 |
24 | beforeEach(function () {
25 | var env = new Environment();
26 | accountHelper = env.accountHelper;
27 | respond = env.respond;
28 | mail = env.mail;
29 | client = env.client;
30 | RequestMocks = env.RequestMocks;
31 | ErrorMocks = env.ErrorMocks;
32 | xhr = env.xhr;
33 | xhrOpen = sinon.spy(xhr.prototype, 'open');
34 | xhrSend = sinon.spy(xhr.prototype, 'send');
35 | });
36 |
37 | afterEach(function () {
38 | xhrOpen.restore();
39 | xhrSend.restore();
40 | });
41 |
42 | test('#basic', function () {
43 | var email = 'test' + new Date().getTime() + '@restmail.net';
44 | var password = 'iliketurtles';
45 |
46 | return respond(client.signUp(email, password), RequestMocks.signUp)
47 | .then(
48 | function (res) {
49 | assert.property(res, 'uid', 'uid should be returned on signUp');
50 | assert.property(res, 'sessionToken', 'sessionToken should be returned on signUp');
51 | assert.notProperty(res, 'keyFetchToken', 'keyFetchToken should not be returned on signUp');
52 |
53 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
54 | assert.include(xhrOpen.args[0][1], '/account/create', 'path is correct');
55 | var sentData = JSON.parse(xhrSend.args[0][0]);
56 | assert.equal(Object.keys(sentData).length, 2);
57 | assert.equal(sentData.email, email, 'email is correct');
58 | assert.equal(sentData.authPW.length, 64, 'length of authPW');
59 | },
60 | assert.notOk
61 | );
62 | });
63 |
64 | test('#withKeys', function () {
65 | var email = 'test' + new Date().getTime() + '@restmail.net';
66 | var password = 'iliketurtles';
67 | var opts = {
68 | keys: true
69 | };
70 |
71 | return respond(client.signUp(email, password, opts), RequestMocks.signUpKeys)
72 | .then(
73 | function (res) {
74 | assert.property(res, 'uid', 'uid should be returned on signUp');
75 | assert.property(res, 'sessionToken', 'sessionToken should be returned on signUp');
76 | assert.property(res, 'keyFetchToken', 'keyFetchToken should be returned on signUp');
77 | assert.property(res, 'unwrapBKey', 'unwrapBKey should be returned on signUp');
78 |
79 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
80 | assert.include(xhrOpen.args[0][1], '/account/create?keys=true', 'path is correct');
81 | },
82 | assert.notOk
83 | );
84 | });
85 |
86 | test('#create account with service, redirectTo, and resume', function () {
87 | var user = 'test' + new Date().getTime();
88 | var email = user + '@restmail.net';
89 | var password = 'iliketurtles';
90 | var opts = {
91 | service: 'sync',
92 | redirectTo: 'https://sync.127.0.0.1/after_reset',
93 | resume: 'resumejwt'
94 | };
95 |
96 | return respond(client.signUp(email, password, opts), RequestMocks.signUp)
97 | .then(function (res) {
98 | assert.ok(res.uid);
99 | return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
100 | })
101 | .then(
102 | function (emails) {
103 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
104 | var service = emails[0].html.match(/service=([A-Za-z0-9]+)/)[1];
105 | var redirectTo = emails[0].html.match(/redirectTo=([A-Za-z0-9]+)/)[1];
106 | var resume = emails[0].html.match(/resume=([A-Za-z0-9]+)/)[1];
107 |
108 | assert.ok(code, 'code is returned');
109 | assert.ok(service, 'service is returned');
110 | assert.ok(redirectTo, 'redirectTo is returned');
111 | assert.ok(resume, 'resume is returned');
112 |
113 | assert.include(xhrOpen.args[0][1], '/account/create', 'path is correct');
114 | var sentData = JSON.parse(xhrSend.args[0][0]);
115 | assert.equal(Object.keys(sentData).length, 5);
116 | assert.equal(sentData.email, email, 'email is correct');
117 | assert.equal(sentData.authPW.length, 64, 'length of authPW');
118 | assert.equal(sentData.service, opts.service);
119 | assert.equal(sentData.resume, opts.resume);
120 | assert.equal(sentData.redirectTo, opts.redirectTo);
121 | },
122 | assert.notOk
123 | );
124 | });
125 |
126 | test('#withService', function () {
127 | var user = 'test' + new Date().getTime();
128 | var email = user + '@restmail.net';
129 | var password = 'iliketurtles';
130 | var opts = {
131 | service: 'sync'
132 | };
133 |
134 | return respond(client.signUp(email, password, opts), RequestMocks.signUp)
135 | .then(function (res) {
136 | assert.ok(res.uid);
137 | return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
138 | })
139 | .then(
140 | function (emails) {
141 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
142 | var service = emails[0].html.match(/service=([A-Za-z0-9]+)/)[1];
143 |
144 | assert.ok(code, 'code is returned');
145 | assert.ok(service, 'service is returned');
146 | },
147 | assert.notOk
148 | );
149 | });
150 |
151 | test('#withRedirectTo', function () {
152 | var user = 'test' + new Date().getTime();
153 | var email = user + '@restmail.net';
154 | var password = 'iliketurtles';
155 | var opts = {
156 | redirectTo: 'http://sync.127.0.0.1/after_reset'
157 | };
158 |
159 | return respond(client.signUp(email, password, opts), RequestMocks.signUp)
160 | .then(function (res) {
161 | assert.ok(res.uid);
162 | return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
163 | })
164 | .then(
165 | function (emails) {
166 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
167 | var redirectTo = emails[0].html.match(/redirectTo=([A-Za-z0-9]+)/)[1];
168 |
169 | assert.ok(code, 'code is returned');
170 | assert.ok(redirectTo, 'redirectTo is returned');
171 |
172 | },
173 | assert.notOk
174 | );
175 | });
176 |
177 | test('#withResume', function () {
178 | var user = 'test' + new Date().getTime();
179 | var email = user + '@restmail.net';
180 | var password = 'iliketurtles';
181 | var opts = {
182 | resume: 'resumejwt'
183 | };
184 |
185 | return respond(client.signUp(email, password, opts), RequestMocks.signUp)
186 | .then(function (res) {
187 | assert.ok(res.uid);
188 | return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
189 | })
190 | .then(
191 | function (emails) {
192 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
193 | var resume = emails[0].html.match(/resume=([A-Za-z0-9]+)/)[1];
194 |
195 | assert.ok(code, 'code is returned');
196 | assert.ok(resume, 'resume is returned');
197 |
198 | },
199 | assert.notOk
200 | );
201 | });
202 |
203 | test('#preVerified', function () {
204 | var email = 'test' + new Date().getTime() + '@restmail.net';
205 | var password = 'iliketurtles';
206 | var opts = {
207 | preVerified: true
208 | };
209 |
210 | return respond(client.signUp(email, password, opts), RequestMocks.signUp)
211 | .then(function (res) {
212 | assert.ok(res.uid);
213 |
214 | return respond(client.signIn(email, password), RequestMocks.signIn);
215 | })
216 | .then(function(res) {
217 | assert.equal(res.verified, true, '== account is verified');
218 | });
219 | });
220 |
221 | test('#accountExists', function () {
222 | return accountHelper.newVerifiedAccount()
223 | .then(function (account) {
224 | return respond(client.signUp(account.input.email, 'somepass'), ErrorMocks.accountExists);
225 | })
226 | .then(
227 | function (res) {
228 | assert.fail();
229 | },
230 | function (err) {
231 | assert.equal(err.code, 400);
232 | assert.equal(err.errno, 101);
233 | }
234 | );
235 | });
236 |
237 | test('#with metricsContext metadata', function () {
238 | var email = 'test' + new Date().getTime() + '@restmail.net';
239 | var password = 'iliketurtles';
240 |
241 | return respond(
242 | client.signUp(email, password, {
243 | metricsContext: {
244 | deviceId: '0123456789abcdef0123456789abcdef',
245 | flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
246 | flowBeginTime: Date.now(),
247 | utmCampaign: 'mock-campaign',
248 | utmContent: 'mock-content',
249 | utmMedium: 'mock-medium',
250 | utmSource: 'mock-source',
251 | utmTerm: 'mock-term',
252 | forbiddenProperty: 666
253 | }
254 | }),
255 | RequestMocks.signUp
256 | )
257 | .then(
258 | function (resp) {
259 | assert.ok(resp);
260 | },
261 | assert.notOk
262 | );
263 | });
264 | });
265 | }
266 | });
267 |
--------------------------------------------------------------------------------
/tests/lib/signinCodes.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | var SIGNIN_CODE = '123456-_';
6 | var FLOW_ID = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
7 | var FLOW_BEGIN_TIME = Date.now();
8 |
9 | define([
10 | 'intern!tdd',
11 | 'intern/chai!assert',
12 | 'tests/addons/environment'
13 | ], function (tdd, assert, Environment) {
14 | var env = new Environment();
15 |
16 | with (tdd) {
17 | suite('signinCodes', function () {
18 | var respond;
19 | var client;
20 | var RequestMocks;
21 |
22 | beforeEach(function () {
23 | env = new Environment();
24 | respond = env.respond;
25 | client = env.client;
26 | RequestMocks = env.RequestMocks;
27 | });
28 |
29 | if (env.useRemoteServer) {
30 | // This test is intended to run against a local auth-server. To test
31 | // against a mock auth-server would be pointless for this assertion.
32 | test('consumeSigninCode with invalid signinCode', function () {
33 | return client.consumeSigninCode(SIGNIN_CODE, FLOW_ID, FLOW_BEGIN_TIME)
34 | .then(function () {
35 | assert.fail('client.consumeSigninCode should reject if signinCode is invalid');
36 | }, function (err) {
37 | assert.ok(err, 'client.consumeSigninCode should return an error');
38 | assert.equal(err.code, 400, 'client.consumeSigninCode should return a 400 response');
39 | assert.equal(err.errno, 146, 'client.consumeSigninCode should return errno 146');
40 | });
41 | });
42 | } else {
43 | // This test is intended to run against a mock auth-server. To test
44 | // against a local auth-server, we'd need to know a valid signinCode.
45 | test('consumeSigninCode', function () {
46 | return respond(client.consumeSigninCode(SIGNIN_CODE, FLOW_ID, FLOW_BEGIN_TIME), RequestMocks.consumeSigninCode)
47 | .then(assert.ok, assert.fail);
48 | });
49 | }
50 |
51 |
52 | test('consumeSigninCode with missing code', function () {
53 | return client.consumeSigninCode(null, FLOW_ID, FLOW_BEGIN_TIME)
54 | .then(function () {
55 | assert.fail('client.consumeSigninCode should reject if code is missing');
56 | }, function (err) {
57 | assert.equal(err.message, 'Missing code');
58 | });
59 | });
60 |
61 | test('consumeSigninCode with missing flowId', function () {
62 | return client.consumeSigninCode(SIGNIN_CODE, null, FLOW_BEGIN_TIME)
63 | .then(function () {
64 | assert.fail('client.consumeSigninCode should reject if flowId is missing');
65 | }, function (err) {
66 | assert.equal(err.message, 'Missing flowId');
67 | });
68 | });
69 |
70 | test('consumeSigninCode with missing flowBeginTime', function () {
71 | return client.consumeSigninCode(SIGNIN_CODE, FLOW_ID, null)
72 | .then(function () {
73 | assert.fail('client.consumeSigninCode should reject if flowBeginTime is missing');
74 | }, function (err) {
75 | assert.equal(err.message, 'Missing flowBeginTime');
76 | });
77 | });
78 | });
79 | }
80 | });
81 |
82 |
--------------------------------------------------------------------------------
/tests/lib/sms.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/lib/push-constants'
10 | ], function (tdd, assert, Environment) {
11 |
12 | // These tests are intended to run against a mock auth-server. To test
13 | // against a local auth-server, you will need to have it correctly
14 | // configured to send sms and specify a real phone number here.
15 | var env = new Environment();
16 | if (env.useRemoteServer) {
17 | return;
18 | }
19 |
20 | var PHONE_NUMBER = '+14071234567';
21 | var MESSAGE_ID = 1;
22 |
23 | with (tdd) {
24 | suite('sms', function () {
25 | var accountHelper;
26 | var respond;
27 | var client;
28 | var RequestMocks;
29 |
30 | beforeEach(function () {
31 | env = new Environment();
32 | accountHelper = env.accountHelper;
33 | respond = env.respond;
34 | client = env.client;
35 | RequestMocks = env.RequestMocks;
36 | });
37 |
38 | test('#send connect device', function () {
39 |
40 | return accountHelper.newVerifiedAccount()
41 | .then(function (account) {
42 |
43 | return respond(client.sendSms(
44 | account.signIn.sessionToken,
45 | PHONE_NUMBER,
46 | MESSAGE_ID
47 | ), RequestMocks.sendSmsConnectDevice);
48 | })
49 | .then(
50 | function (resp) {
51 | assert.ok(resp);
52 | },
53 | assert.notOk
54 | );
55 | });
56 |
57 | test('status', function () {
58 | return accountHelper.newVerifiedAccount()
59 | .then(
60 | function (account) {
61 | return respond(
62 | client.smsStatus(account.signIn.sessionToken),
63 | RequestMocks.smsStatus
64 | );
65 | }
66 | )
67 | .then(
68 | function (resp) {
69 | assert.ok(resp);
70 | assert.ok(resp.ok);
71 | assert.ok(resp.country);
72 | },
73 | assert.notOk
74 | );
75 | });
76 |
77 | test('status with country', function () {
78 | return accountHelper.newVerifiedAccount()
79 | .then(
80 | function (account) {
81 | return respond(
82 | client.smsStatus(account.signIn.sessionToken, { country: 'RO' }),
83 | RequestMocks.smsStatus
84 | );
85 | }
86 | )
87 | .then(
88 | function (resp) {
89 | assert.ok(resp);
90 | assert.ok(resp.ok);
91 | assert.ok(resp.country, 'RO');
92 | },
93 | assert.notOk
94 | );
95 | });
96 | });
97 | }
98 | });
99 |
100 |
--------------------------------------------------------------------------------
/tests/lib/tokenCodes.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment'
9 | ], function (tdd, assert, Environment) {
10 |
11 | with (tdd) {
12 | suite('tokenCodes', function () {
13 | var account;
14 | var accountHelper;
15 | var respond;
16 | var client;
17 | var mail;
18 | var RequestMocks;
19 | var env = new Environment();
20 |
21 | beforeEach(function () {
22 | env = new Environment();
23 | accountHelper = env.accountHelper;
24 | respond = env.respond;
25 | client = env.client;
26 | mail = env.mail;
27 | RequestMocks = env.RequestMocks;
28 | return accountHelper.newVerifiedAccount({username: 'confirm.' + Date.now()})
29 | .then(function (newAccount) {
30 | account = newAccount;
31 | });
32 | });
33 |
34 | if (env.useRemoteServer) {
35 | // This test is intended to run against a local auth-server. To test
36 | // against a mock auth-server would be pointless for this assertion.
37 | test('verify session with invalid tokenCode', function () {
38 | var opts = {verificationMethod: 'email-2fa', keys:true};
39 | return respond(client.signIn(account.input.email, account.input.password, opts), RequestMocks.signInWithVerificationMethodEmail2faResponse)
40 | .then(function (res) {
41 | assert.equal(res.verificationMethod, 'email-2fa', 'should return correct verificationMethod');
42 | assert.equal(res.verificationReason, 'login', 'should return correct verificationReason');
43 | return respond(mail.wait(account.input.user, 3), RequestMocks.signInWithVerificationMethodEmail2faCode);
44 | })
45 | .then(function (emails) {
46 | // should contain token code
47 | var code = emails[2].headers['x-signin-verify-code'];
48 | code = code === '000000' ? '000001' : '000000';
49 | return client.verifyTokenCode(account.signIn.sessionToken, account.signIn.uid, code);
50 | })
51 | .then(function () {
52 | assert.fail('should reject if tokenCode is invalid');
53 | }, function (err) {
54 | assert.ok(err, 'should return an error');
55 | assert.equal(err.code, 400, 'should return a 400 response');
56 | assert.equal(err.errno, 152, 'should return errno 152');
57 | });
58 | });
59 | }
60 |
61 | test('#verify session with valid tokenCode', function () {
62 | var code;
63 | var opts = {verificationMethod: 'email-2fa', keys:true};
64 | return respond(client.signIn(account.input.email, account.input.password, opts), RequestMocks.signInWithVerificationMethodEmail2faResponse)
65 | .then(function (res) {
66 | assert.equal(res.verificationMethod, 'email-2fa', 'should return correct verificationMethod');
67 | assert.equal(res.verificationReason, 'login', 'should return correct verificationReason');
68 | return respond(mail.wait(account.input.user, 3), RequestMocks.signInWithVerificationMethodEmail2faCode);
69 | })
70 | .then(function (emails) {
71 | // should contain token code
72 | code = emails[2].headers['x-signin-verify-code'];
73 | assert.ok(code, 'code is returned');
74 | return respond(client.verifyTokenCode(account.signIn.sessionToken, account.signIn.uid, code), RequestMocks.sessionVerifyTokenCodeSuccess);
75 | }, assert.notOk)
76 | .then(function (res) {
77 | assert.ok(res, 'res is ok');
78 | }, assert.notOk);
79 | });
80 | });
81 | }
82 | });
83 |
--------------------------------------------------------------------------------
/tests/lib/totp.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/addons/sinon',
10 | 'node_modules/otplib/otplib-browser'
11 | ], function (tdd, assert, Environment, sinon, otplib) {
12 |
13 | with (tdd) {
14 | suite('totp', function () {
15 | var authenticator;
16 | var account;
17 | var accountHelper;
18 | var respond;
19 | var client;
20 | var RequestMocks;
21 | var env;
22 | var xhr;
23 | var xhrOpen;
24 | var xhrSend;
25 | var secret;
26 | var opts = {
27 | metricsContext: {
28 | flowBeginTime: Date.now(),
29 | flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
30 | },
31 | service: 'sync'
32 | };
33 |
34 | beforeEach(function () {
35 | env = new Environment();
36 | accountHelper = env.accountHelper;
37 | respond = env.respond;
38 | client = env.client;
39 | RequestMocks = env.RequestMocks;
40 |
41 | return accountHelper.newVerifiedAccount()
42 | .then(function (newAccount) {
43 | account = newAccount;
44 | return respond(client.createTotpToken(account.signIn.sessionToken), RequestMocks.createTotpToken);
45 | })
46 | .then(function (res) {
47 | assert.ok(res.qrCodeUrl, 'should return QR code data encoded url');
48 | assert.ok(res.secret, 'should return secret that is encoded in url');
49 |
50 | // Create a new authenticator instance with shared options
51 | authenticator = new otplib.authenticator.Authenticator();
52 | authenticator.options = otplib.authenticator.options;
53 | secret = res.secret;
54 |
55 | xhr = env.xhr;
56 | xhrOpen = sinon.spy(xhr.prototype, 'open');
57 | xhrSend = sinon.spy(xhr.prototype, 'send');
58 | });
59 | });
60 |
61 | afterEach(function () {
62 | xhrOpen.restore();
63 | xhrSend.restore();
64 | });
65 |
66 | test('#createTotpToken - fails if already exists', function () {
67 | return respond(client.createTotpToken(account.signIn.sessionToken), RequestMocks.createTotpTokenDuplicate)
68 | .then(assert.fail, function (err) {
69 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
70 | assert.include(xhrOpen.args[0][1], '/totp/create', 'path is correct');
71 | assert.equal(err.errno, 154, 'token already exists for account errno');
72 | });
73 | });
74 |
75 | test('#deleteTotpToken', function () {
76 | return respond(client.deleteTotpToken(account.signIn.sessionToken), RequestMocks.deleteTotpToken)
77 | .then(function (res) {
78 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
79 | assert.include(xhrOpen.args[0][1], '/totp/destroy', 'path is correct');
80 | assert.ok(res, 'should return empty response');
81 | });
82 | });
83 |
84 | test('#checkTotpTokenExists - does not exist returns false', function () {
85 | return accountHelper.newVerifiedAccount()
86 | .then(function (newAccount) {
87 | return respond(client.checkTotpTokenExists(newAccount.signIn.sessionToken), RequestMocks.checkTotpTokenExistsFalse)
88 | .then(function (res) {
89 | assert.equal(xhrOpen.args[4][0], 'GET', 'method is correct');
90 | assert.include(xhrOpen.args[4][1], '/totp/exists', 'path is correct');
91 | assert.equal(res.exists, false);
92 | });
93 | });
94 | });
95 |
96 | test('#checkTotpTokenExists - created token but not verified returns false', function () {
97 | return respond(client.checkTotpTokenExists(account.signIn.sessionToken), RequestMocks.checkTotpTokenExistsFalse)
98 | .then(function (res) {
99 | assert.equal(xhrOpen.args[0][0], 'GET', 'method is correct');
100 | assert.include(xhrOpen.args[0][1], '/totp/exists', 'path is correct');
101 | assert.equal(res.exists, false);
102 | });
103 | });
104 |
105 | test('#checkTotpTokenExists - verified token returns true', function () {
106 | var code = authenticator.generate(secret);
107 | return respond(client.verifyTotpCode(account.signIn.sessionToken, code), RequestMocks.verifyTotpCodeTrue)
108 | .then(function (res) {
109 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
110 | assert.include(xhrOpen.args[0][1], '/session/verify/totp', 'path is correct');
111 | var sentData = JSON.parse(xhrSend.args[0][0]);
112 | assert.equal(Object.keys(sentData).length, 1);
113 | assert.equal(sentData.code, code, 'code is correct');
114 |
115 | assert.equal(res.success, true);
116 | return respond(client.checkTotpTokenExists(account.signIn.sessionToken), RequestMocks.checkTotpTokenExistsTrue)
117 | .then(function (res) {
118 | assert.equal(xhrOpen.args[1][0], 'GET', 'method is correct');
119 | assert.include(xhrOpen.args[1][1], '/totp/exists', 'path is correct');
120 | assert.equal(res.exists, true);
121 | });
122 | });
123 | });
124 |
125 | test('#verifyTotpCode - succeeds for valid code', function () {
126 | var code = authenticator.generate(secret);
127 | return respond(client.verifyTotpCode(account.signIn.sessionToken, code, opts), RequestMocks.verifyTotpCodeTrue)
128 | .then(function (res) {
129 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
130 | assert.include(xhrOpen.args[0][1], '/session/verify/totp', 'path is correct');
131 | var sentData = JSON.parse(xhrSend.args[0][0]);
132 | assert.lengthOf(Object.keys(sentData), 2);
133 | assert.equal(sentData.code, code, 'code is correct');
134 | assert.equal(sentData.service, opts.service, 'service is correct');
135 |
136 |
137 | assert.equal(res.success, true);
138 | });
139 | });
140 |
141 | test('#verifyTotpCode - fails for invalid code', function () {
142 | var code = authenticator.generate(secret) === '000000' ? '000001' : '000000';
143 | return respond(client.verifyTotpCode(account.signIn.sessionToken, code, opts), RequestMocks.verifyTotpCodeFalse)
144 | .then(function (res) {
145 | assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
146 | assert.include(xhrOpen.args[0][1], '/session/verify/totp', 'path is correct');
147 | var sentData = JSON.parse(xhrSend.args[0][0]);
148 | assert.lengthOf(Object.keys(sentData), 2);
149 | assert.equal(sentData.code, code, 'code is correct');
150 | assert.equal(sentData.service, opts.service, 'service is correct');
151 |
152 | assert.equal(res.success, false);
153 | });
154 | });
155 | });
156 | }
157 | });
158 |
--------------------------------------------------------------------------------
/tests/lib/unbundle.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'node_modules/sjcl/sjcl',
9 | 'client/lib/credentials'
10 | ], function (tdd, assert, sjcl, credentials) {
11 | with (tdd) {
12 |
13 | suite('unbundle', function () {
14 |
15 | test('#vector 1', function () {
16 | // credentials.unbundleKeyFetchResponse(bundleKey, 'account/keys', payload.bundle);
17 | // Vectors generated from fxa-auth-server
18 | var bundleKey = 'ba0a107dab60f3b065ff7a642d14fe824fbd71bc5c99087e9e172a1abd1634f1';
19 | var keyInfo = 'account/keys';
20 | var bundle = 'e47eb17e487eb4495e79846d5e0c16ea51ef51ff5ef59cd8f626f95f572ec64dcc7b97fcbc0d0ece0cc93dbe6ac84974066830280ccacf5de13a8460524238cf543edfc5027aabeddc107e9fd429a25ce6f5d94917f2a6435380ee5f11353814';
21 | var bitBundle = sjcl.codec.hex.toBits(bundle);
22 |
23 | return credentials.deriveBundleKeys(bundleKey, keyInfo)
24 | .then(
25 | function (keys) {
26 | assert.equal(sjcl.codec.hex.fromBits(keys.hmacKey), '17ab463653a94c9a6419b48781930edefe500395e3b4e7879a2be15999757022', '== hmacKey equal');
27 | assert.equal(sjcl.codec.hex.fromBits(keys.xorKey), '85de16c3218a126404668bf9b7acfb6ce2b7e03c8889047ba48b8b854c6d8beb3ae100e145ca6d69cb519a872a83af788771954455716143bc08225ea8644d85', '== xorKey equal');
28 |
29 | var keyAWrapB = credentials.xor(sjcl.bitArray.bitSlice(bitBundle, 0, 8 * 64), keys.xorKey);
30 | assert.equal(sjcl.codec.hex.fromBits(keyAWrapB), '61a0a7bd69f4a62d5a1f0f94e9a0ed86b358b1c3d67c98a352ad72da1b434da6f69a971df9c763a7c798a739404be60c8119a56c59bbae1e5d32a63efa26754a', '== xorBuffers equal');
31 | var keyObj = {
32 | kA: sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(keyAWrapB, 0, 8 * 32)),
33 | wrapKB: sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(keyAWrapB, 8 * 32, 8 * 64))
34 | };
35 |
36 | return keyObj;
37 | }
38 | ).then(
39 | function(result) {
40 | assert.equal(result.kA, '61a0a7bd69f4a62d5a1f0f94e9a0ed86b358b1c3d67c98a352ad72da1b434da6', '== kA equal');
41 | assert.equal(result.wrapKB, 'f69a971df9c763a7c798a739404be60c8119a56c59bbae1e5d32a63efa26754a', '== wrapKB equal');
42 | }
43 | );
44 | });
45 |
46 | test('#vector 2', function () {
47 |
48 | var bundleKey = 'dedd009a8275a4f672bb4b41e14a117812c0b2f400c85fa058e0293f3f45726a';
49 | var bundle = 'df4717238a738501bd2ad8f7114ef193ea69751a40108149bfb88a5643a8d683a1e75b705d4db135130f0896dbac0819ab7d54334e0cd4f9c945e0a7ada91899756cedf4384be404844050270310bc2b396f100eeda0c7b428cfe77c40a873ae';
50 | return credentials.unbundleKeyFetchResponse(bundleKey, bundle)
51 | .then(
52 | function(result) {
53 | assert.equal(result.kA, '939282904b808c6003ea31aeb14bc766d2ab70ba7dcaa54f820efcf4762b9619', '== kA equal');
54 | assert.equal(result.wrapKB, '849ac9f71643ace46dcdd384633ec1bffe565852806ee2f859c3eba7fafeafec', '== wrapKB equal');
55 | }
56 | );
57 | });
58 |
59 | });
60 | }
61 | });
62 |
--------------------------------------------------------------------------------
/tests/lib/uriVersion.js:
--------------------------------------------------------------------------------
1 |
2 | /* This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 |
6 | define([
7 | 'intern!tdd',
8 | 'intern/chai!assert',
9 | 'client/FxAccountClient'
10 | ], function (tdd, assert, FxAccountClient) {
11 |
12 | var xhr = function () {};
13 | var serverUri = 'https://mock.server';
14 | var VERSION = FxAccountClient.VERSION;
15 |
16 | with (tdd) {
17 | suite('fxa client', function () {
18 | test('#version appended to uri when not present', function () {
19 | var client = new FxAccountClient(serverUri, { xhr: xhr });
20 | assert.equal(serverUri + '/' + VERSION, client.request.baseUri);
21 | });
22 |
23 | test('#version not appended to uri when already present', function () {
24 | var uri = serverUri + '/' + VERSION;
25 | var client = new FxAccountClient(uri, { xhr: xhr });
26 | assert.equal(uri, client.request.baseUri);
27 | });
28 | });
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/tests/lib/verifyCode.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([
6 | 'intern!tdd',
7 | 'intern/chai!assert',
8 | 'tests/addons/environment',
9 | 'tests/addons/sinon'
10 | ], function (tdd, assert, Environment, sinon) {
11 |
12 | with (tdd) {
13 | suite('verifyCode', function () {
14 | var respond;
15 | var mail;
16 | var client;
17 | var RequestMocks;
18 | var xhr;
19 | var xhrOpen;
20 | var xhrSend;
21 |
22 | beforeEach(function () {
23 | var env = new Environment();
24 | respond = env.respond;
25 | mail = env.mail;
26 | client = env.client;
27 | RequestMocks = env.RequestMocks;
28 | xhr = env.xhr;
29 | xhrOpen = sinon.spy(xhr.prototype, 'open');
30 | xhrSend = sinon.spy(xhr.prototype, 'send');
31 | });
32 |
33 | afterEach(function () {
34 | xhrOpen.restore();
35 | xhrSend.restore();
36 | });
37 |
38 | test('#verifyEmail', function () {
39 | var user = 'test3' + new Date().getTime();
40 | var email = user + '@restmail.net';
41 | var password = 'iliketurtles';
42 | var uid;
43 |
44 | return respond(client.signUp(email, password), RequestMocks.signUp)
45 | .then(function (result) {
46 | uid = result.uid;
47 | assert.ok(uid, 'uid is returned');
48 |
49 | return respond(mail.wait(user), RequestMocks.mail);
50 | })
51 | .then(function (emails) {
52 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
53 | assert.ok(code, 'code is returned');
54 |
55 | return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
56 | })
57 | .then(
58 | function (result) {
59 | assert.ok(result);
60 | },
61 | assert.notOk
62 | );
63 | });
64 |
65 | test('#verifyEmailCheckStatus', function () {
66 | var user = 'test4' + new Date().getTime();
67 | var email = user + '@restmail.net';
68 | var password = 'iliketurtles';
69 | var uid;
70 | var sessionToken;
71 |
72 | return respond(client.signUp(email, password), RequestMocks.signUp)
73 | .then(function (result) {
74 | uid = result.uid;
75 | assert.ok(uid, 'uid is returned');
76 |
77 | return respond(client.signIn(email, password), RequestMocks.signIn);
78 | })
79 | .then(function (result) {
80 | assert.ok(result.sessionToken, 'sessionToken is returned');
81 | sessionToken = result.sessionToken;
82 |
83 | return respond(client.recoveryEmailStatus(sessionToken),
84 | RequestMocks.recoveryEmailUnverified);
85 | })
86 | .then(function (result) {
87 | assert.equal(result.verified, false, 'Email should not be verified.');
88 |
89 | return respond(mail.wait(user, 2), RequestMocks.mailUnverifiedSignin);
90 | })
91 | .then(function (emails) {
92 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
93 | assert.ok(code, 'code is returned: ' + code);
94 |
95 | return respond(client.verifyCode(uid, code),
96 | RequestMocks.verifyCode);
97 | })
98 | .then(function (result) {
99 |
100 | return respond(client.recoveryEmailStatus(sessionToken),
101 | RequestMocks.recoveryEmailVerified);
102 | })
103 | .then(
104 | function (result) {
105 | assert.equal(result.verified, true, 'Email should be verified.');
106 | },
107 | assert.notOk
108 | );
109 | });
110 |
111 | test('#verifyEmail with service param', function () {
112 | var user = 'test5' + new Date().getTime();
113 | var email = user + '@restmail.net';
114 | var password = 'iliketurtles';
115 | var uid;
116 |
117 | return respond(client.signUp(email, password), RequestMocks.signUp)
118 | .then(function (result) {
119 | uid = result.uid;
120 | assert.ok(uid, 'uid is returned');
121 |
122 | return respond(mail.wait(user), RequestMocks.mail);
123 | })
124 | .then(function (emails) {
125 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
126 | assert.ok(code, 'code is returned');
127 |
128 | return respond(client.verifyCode(uid, code, { service: 'sync' }),
129 | RequestMocks.verifyCode);
130 | })
131 | .then(
132 | function (result) {
133 | assert.ok(result);
134 | },
135 | assert.notOk
136 | );
137 | });
138 |
139 | test('#verifyEmail with reminder param', function () {
140 | var user = 'test6' + new Date().getTime();
141 | var email = user + '@restmail.net';
142 | var password = 'iliketurtles';
143 | var uid;
144 |
145 | return respond(client.signUp(email, password), RequestMocks.signUp)
146 | .then(function (result) {
147 | uid = result.uid;
148 | assert.ok(uid, 'uid is returned');
149 |
150 | return respond(mail.wait(user), RequestMocks.mail);
151 | })
152 | .then(function (emails) {
153 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
154 | assert.ok(code, 'code is returned');
155 |
156 | return respond(client.verifyCode(uid, code, { reminder: 'first' }),
157 | RequestMocks.verifyCode);
158 | })
159 | .then(
160 | function (result) {
161 | assert.ok(result);
162 | },
163 | assert.notOk
164 | );
165 | });
166 |
167 | test('#verifyEmail with marketingOptIn param', function () {
168 | var user = 'test7' + new Date().getTime();
169 | var email = user + '@restmail.net';
170 | var password = 'iliketurtles';
171 | var uid;
172 |
173 | return respond(client.signUp(email, password), RequestMocks.signUp)
174 | .then(function (result) {
175 | uid = result.uid;
176 | assert.ok(uid, 'uid is returned');
177 |
178 | return respond(mail.wait(user), RequestMocks.mail);
179 | })
180 | .then(function (emails) {
181 | var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
182 | assert.ok(code, 'code is returned');
183 |
184 | return respond(client.verifyCode(uid, code, { marketingOptIn: true }),
185 | RequestMocks.verifyCode);
186 | })
187 | .then(
188 | function (result) {
189 | assert.ok(result);
190 | assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
191 | assert.include(xhrOpen.args[2][1], '/recovery_email/verify_code', 'path is correct');
192 | var sentData = JSON.parse(xhrSend.args[2][0]);
193 | assert.equal(sentData.marketingOptIn, true);
194 | },
195 | assert.notOk
196 | );
197 | });
198 | });
199 | }
200 | });
201 |
--------------------------------------------------------------------------------
/tests/mocks/errors.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | define([], function () {
6 | return {
7 | // status code 400, errno 101: attempt to create an account that already exists
8 | accountExists: {
9 | status: 400,
10 | headers: {},
11 | body: '{"code":400, "errno": 101}'
12 | },
13 | // status code 400, errno 102: attempt to access an account that does not exist
14 | accountDoesNotExist: {
15 | status: 400,
16 | headers: {},
17 | body: '{"code":400, "errno": 102}'
18 | },
19 | // status code 400, errno 103: incorrect password
20 | accountIncorrectPassword: {
21 | status: 400,
22 | headers: {},
23 | body: '{"code":400, "errno": 103, "message":"Incorrect password"}'
24 | },
25 | // status code 400, errno 104: attempt to operate on an unverified account
26 | accountUnverified: {
27 | status: 400,
28 | headers: {},
29 | body: '{"code":400, "errno": 104}'
30 | },
31 | // status code 400, errno 105: invalid verification code
32 | invalidVerification: {
33 | status: 400,
34 | headers: {},
35 | body: '{"code":400, "errno": 105}'
36 | },
37 | // status code 400, errno 106: request body was not valid json
38 | invalidJson: {
39 | status: 400,
40 | headers: {},
41 | body: '{"code":400, "errno": 106}'
42 | },
43 | // status code 400, errno 107: request body contains invalid parameters
44 | requestInvalidParams: {
45 | status: 400,
46 | headers: {},
47 | body: '{"code":400, "errno": 107}'
48 | },
49 | // status code 400, errno 107: request body contains invalid parameters
50 | requestMissingParams: {
51 | status: 400,
52 | headers: {},
53 | body: '{"code":400, "errno": 108}'
54 | },
55 | // status code 401, errno 109: invalid request signature
56 | invalidRequestSignature: {
57 | status: 401,
58 | headers: {},
59 | body: '{"code":401, "errno": 109}'
60 | },
61 | // status code 401, errno 110: invalid authentication token
62 | invalidAuthToken: {
63 | status: 401,
64 | headers: {},
65 | body: '{"code":401, "errno": 110}'
66 | },
67 | // status code 401, errno 111: invalid authentication timestamp
68 | invalidAuthTimestamp: {
69 | status: 401,
70 | headers: {},
71 | body: '{"code":401, "errno": 111}'
72 | },
73 | // status code 411, errno 112: content-length header was not provided
74 | missingContentLength: {
75 | status: 411,
76 | headers: {},
77 | body: '{"code":411, "errno": 112}'
78 | },
79 | // status code 413, errno 113: request body too large
80 | requestTooLarge: {
81 | status: 413,
82 | headers: {},
83 | body: '{"code":413, "errno": 113}'
84 | },
85 | // status code 429, errno 114: client has sent too many requests (see backoff protocol)
86 | sentTooManyRequests: {
87 | status: 429,
88 | headers: {},
89 | body: '{"code":429, "errno": 114}'
90 | },
91 | // status code 429, errno 115: invalid authentication nonce
92 | invalidAuthNonce: {
93 | status: 401,
94 | headers: {},
95 | body: '{"code":401, "errno": 115}'
96 | },
97 | // status code 410, errno 116: endpoint is no longer supported
98 | endpointNotSupported: {
99 | status: 410,
100 | headers: {},
101 | body: '{"code":410, "errno": 116}'
102 | },
103 | // status code 400, errno 117: incorrect login method for this account
104 | incorrectLoginMethod: {
105 | status: 400,
106 | headers: {},
107 | body: '{"code":400, "errno": 117}'
108 | },
109 | // status code 400, errno 118: incorrect key retrieval method for this account
110 | incorrectKeyMethod: {
111 | status: 400,
112 | headers: {},
113 | body: '{"code":400, "errno": 118}'
114 | },
115 | // status code 400, errno 119: incorrect API version for this account
116 | incorrectAPIVersion: {
117 | status: 400,
118 | headers: {},
119 | body: '{"code":400, "errno": 119}'
120 | },
121 | // status code 400, errno 120: incorrect email case
122 | incorrectEmailCase: {
123 | status: 400,
124 | headers: {},
125 | body: '{"code":400, "errno": 120, "email": "a@b.com"}'
126 | },
127 | // status code 503, errno 201: service temporarily unavailable to due high load (see backoff protocol)
128 | temporarilyUnavailable: {
129 | status: 503,
130 | headers: {},
131 | body: '{"code":503, "errno": 201}'
132 | },
133 | // any status code, errno 999: unknown error
134 | unknownError: {
135 | status: 400,
136 | headers: {},
137 | body: '{"code":400, "errno": 999}'
138 | },
139 | timeout: {
140 | status: 400,
141 | headers: {},
142 | body: ''
143 | },
144 | badResponseFormat: {
145 | status: 404,
146 | headers: {},
147 | body: 'Something is wrong.'
148 | },
149 | signInBlocked: {
150 | status: 429,
151 | headers: {},
152 | body: JSON.stringify({
153 | code: 429,
154 | errno: 125,
155 | verificationMethod: 'email-captcha',
156 | verificationReason: 'login'
157 | })
158 | },
159 | signInInvalidUnblockCode: {
160 | status: 400,
161 | body: '{"code":400, "errno": 127}'
162 | }
163 | };
164 | });
165 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | /* eslint-disable */
6 | const path = require('path');
7 | const webpack = require('webpack');
8 |
9 | module.exports = {
10 | context: path.resolve(__dirname),
11 | entry: {
12 | 'fxa-client': './client/FxAccountClient',
13 | 'fxa-client.min': './client/FxAccountClient',
14 | },
15 |
16 | output: {
17 | filename: '[name].js',
18 | library: 'FxAccountClient',
19 | libraryTarget: 'umd',
20 | path: path.resolve(__dirname, 'build'),
21 | publicPath: '/'
22 | },
23 |
24 | resolve: {
25 | extensions: ['.js'],
26 | modules: [
27 | path.resolve(__dirname, 'client')
28 | ],
29 | alias: {
30 | 'es6-promise': path.resolve(__dirname, 'node_modules/es6-promise/dist/es6-promise'),
31 | sjcl: path.resolve(__dirname, 'node_modules/sjcl/sjcl')
32 | }
33 | },
34 |
35 | plugins: [
36 | new webpack.optimize.UglifyJsPlugin({
37 | include: /\.min\.js$/,
38 | output: {
39 | comments: false
40 | },
41 | compress: {
42 | unsafe_comps: true,
43 | properties: true,
44 | keep_fargs: false,
45 | pure_getters: true,
46 | collapse_vars: true,
47 | unsafe: true,
48 | warnings: false,
49 | screw_ie8: true,
50 | sequences: true,
51 | dead_code: true,
52 | drop_debugger: true,
53 | comparisons: true,
54 | conditionals: true,
55 | evaluate: true,
56 | booleans: true,
57 | loops: true,
58 | unused: true,
59 | hoist_funs: true,
60 | if_return: true,
61 | join_vars: true,
62 | cascade: true,
63 | drop_console: true
64 | },
65 | sourceMap: true
66 | })
67 | ],
68 |
69 | node: {
70 | global: true,
71 | process: false,
72 | Buffer: false,
73 | __filename: false,
74 | __dirname: false,
75 | setImmediate: false
76 | },
77 |
78 | module: {},
79 | stats: { colors: true },
80 |
81 | // See https://webpack.js.org/configuration/devtool/ to
82 | // configure source maps to personal preferences.
83 | devtool: 'source-map'
84 | };
85 | /* eslint-enable */
86 |
--------------------------------------------------------------------------------