зеркало из https://github.com/mozilla/fxa.git
feat(auth-client): replace fxa-js-client with fxa-auth-client
- We have two clients for the auth-server API, one in `content-server/app/scripts/lib/auth` and `fxa-js-client`. The content-server one was meant to replace fxa-js-client but I didn't finish the work to get it running on nodejs. - Extracts the content-server implementation into `fxa-auth-client` - Wraps the implementation with shims to work with nodejs in `server.ts` - Updates references to fxa-js-client to use fxa-auth-client - Removes fxa-js-client
This commit is contained in:
Родитель
96161f3775
Коммит
7358a3cae5
|
@ -116,7 +116,7 @@ jobs:
|
|||
'123done' \
|
||||
'browserid-verifier' \
|
||||
'fortress' \
|
||||
'fxa-js-client' \
|
||||
'fxa-auth-client' \
|
||||
'fxa-geodb' \
|
||||
'fxa-email-event-proxy' \
|
||||
'fxa-customs-server' \
|
||||
|
|
|
@ -114,11 +114,6 @@ packages/fxa-email-service/fxa-auth-db-mysql
|
|||
packages/fxa-email-service/target
|
||||
packages/fxa-email-service/.sourcehash
|
||||
|
||||
# fxa-js-client
|
||||
packages/fxa-js-client/components
|
||||
packages/fxa-js-client/docs
|
||||
packages/fxa-js-client/fxa-auth-server
|
||||
|
||||
# fxa-profile-server
|
||||
packages/fxa-profile-server/var
|
||||
packages/fxa-profile-server/BUCKET_NAME
|
||||
|
|
|
@ -407,6 +407,7 @@ In addition to the ecosystem docs, each package has it's own README.md and `docs
|
|||
- 123done [README](./packages/123done/README.md)
|
||||
- browserid-verifier [README](./packages/browserid-verifier/README.md)
|
||||
- fortress [README](./packages/fortress/README.md)
|
||||
- fxa-auth-client [README](./packages/fxa-auth-client/README.md)
|
||||
- fxa-auth-db-mysql [README](./packages/fxa-auth-db-mysql/README.md) / [docs/](./packages/fxa-auth-db-mysql/docs)
|
||||
- fxa-auth-server [README](./packages/fxa-auth-server/README.md) / [docs/](./packages/fxa-auth-server/docs)
|
||||
- fxa-content-server [README](./packages/fxa-content-server/README.md) / [docs/](./packages/fxa-content-server/docs)
|
||||
|
@ -414,7 +415,6 @@ In addition to the ecosystem docs, each package has it's own README.md and `docs
|
|||
- fxa-email-service [README](./packages/fxa-email-service/README.md) / [docs/](./packages/fxa-email-service/docs)
|
||||
- fxa-event-broker [README](./packages/fxa-event-broker/README.md) / [docs/](./packages/fxa-event-broker/docs)
|
||||
- fxa-geodb [README](./packages/fxa-geodb/README.md)
|
||||
- fxa-js-client [README](./packages/fxa-js-client/README.md)
|
||||
- fxa-payments-server [README](./packages/fxa-payments-server/README.md)
|
||||
- fxa-profile-server [README](./packages/fxa-profile-server/README.md) / [docs/](./packages/fxa-profile-server/docs)
|
||||
- fxa-shared [README](./packages/fxa-shared/README.md)
|
||||
|
|
|
@ -24,7 +24,7 @@ npx yarn workspaces focus --production \
|
|||
fxa-event-broker \
|
||||
fxa-geodb \
|
||||
fxa-graphql-api \
|
||||
fxa-js-client \
|
||||
fxa-auth-client \
|
||||
fxa-metrics-processor \
|
||||
fxa-payments-server \
|
||||
fxa-profile-server \
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"123done",
|
||||
"fortress",
|
||||
"fxa-auth-server",
|
||||
"fxa-js-client",
|
||||
"fxa-auth-client",
|
||||
"fxa-shared",
|
||||
"fxa-profile-server",
|
||||
"fxa-payments-server"
|
||||
|
@ -56,7 +56,7 @@
|
|||
"fxa-auth-db-mysql",
|
||||
"fxa-shared"
|
||||
],
|
||||
"fxa-js-client": [
|
||||
"fxa-auth-client": [
|
||||
"fxa-auth-server"
|
||||
],
|
||||
"fxa-profile-server": [
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import AuthClient from './lib/client';
|
||||
export default AuthClient;
|
|
@ -18,7 +18,7 @@ enum tokenType {
|
|||
passwordChangeToken = 'passwordChangeToken',
|
||||
}
|
||||
|
||||
interface MetricsContext {
|
||||
export interface MetricsContext {
|
||||
deviceId?: string;
|
||||
flowId?: string;
|
||||
flowBeginTime?: number;
|
||||
|
@ -73,7 +73,7 @@ export default class AuthClient {
|
|||
private uri: string;
|
||||
private localtimeOffsetMsec: number;
|
||||
|
||||
private constructor(authServerUri: string) {
|
||||
constructor(authServerUri: string) {
|
||||
if (new RegExp(`/${AuthClient.VERSION}$`).test(authServerUri)) {
|
||||
this.uri = authServerUri;
|
||||
} else {
|
||||
|
@ -85,6 +85,7 @@ export default class AuthClient {
|
|||
static async create(authServerUri: string) {
|
||||
if (typeof TextEncoder === 'undefined') {
|
||||
await import(
|
||||
// @ts-ignore
|
||||
/* webpackChunkName: "fast-text-encoding" */ 'fast-text-encoding'
|
||||
);
|
||||
}
|
||||
|
@ -1002,7 +1003,7 @@ export default class AuthClient {
|
|||
);
|
||||
}
|
||||
|
||||
async recoveryKeyExists(sessionToken: string | undefined, email: string) {
|
||||
async recoveryKeyExists(sessionToken: string | undefined, email?: string) {
|
||||
if (sessionToken) {
|
||||
return this.sessionPost('/recoveryKey/exists', sessionToken, { email });
|
||||
}
|
|
@ -91,7 +91,7 @@ export async function deriveBundleKeys(
|
|||
);
|
||||
const hmacKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
new Uint8Array(keyMaterial, 0, 32),
|
||||
new Uint8Array(keyMaterial.slice(0, 32)),
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: 'SHA-256',
|
||||
|
@ -100,7 +100,7 @@ export async function deriveBundleKeys(
|
|||
true,
|
||||
['verify']
|
||||
);
|
||||
const xorKey = new Uint8Array(keyMaterial, 32);
|
||||
const xorKey = new Uint8Array(keyMaterial.slice(32));
|
||||
return {
|
||||
hmacKey,
|
||||
xorKey,
|
|
@ -25,9 +25,9 @@ export async function deriveHawkCredentials(token: string, context: string) {
|
|||
baseKey,
|
||||
32 * 3 * 8
|
||||
);
|
||||
const id = new Uint8Array(keyMaterial, 0, 32);
|
||||
const authKey = new Uint8Array(keyMaterial, 32, 32);
|
||||
const bundleKey = new Uint8Array(keyMaterial, 64);
|
||||
const id = new Uint8Array(keyMaterial.slice(0, 32));
|
||||
const authKey = new Uint8Array(keyMaterial.slice(32, 64));
|
||||
const bundleKey = new Uint8Array(keyMaterial.slice(64));
|
||||
|
||||
return {
|
||||
id: uint8ToHex(id),
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "fxa-auth-client",
|
||||
"version": "1.180.0",
|
||||
"description": "",
|
||||
"main": "dist/server/server.js",
|
||||
"exports": {
|
||||
".": "./dist/server/server.js",
|
||||
"./browser": "./dist/browser/browser.js",
|
||||
"./lib/": "./lib/"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "(tsc --build tsconfig.browser.json && tsc --build) || true",
|
||||
"build": "tsc --build tsconfig.browser.json && tsc --build",
|
||||
"test": "mocha -r ts-node/register test/*"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@peculiar/webcrypto": "^1.1.2",
|
||||
"abab": "^2.0.0",
|
||||
"node-fetch": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/assert": "^1.5.1",
|
||||
"@types/fast-text-encoding": "^1",
|
||||
"@types/mocha": "^7",
|
||||
"@types/node-fetch": "^2",
|
||||
"asmcrypto.js": "^0.22.0",
|
||||
"fast-text-encoding": "^1.0.0",
|
||||
"mocha": "^7.1.2",
|
||||
"ts-node": "^8.10.1",
|
||||
"typescript": "3.8.3",
|
||||
"webcrypto-liner": "git://github.com/mozilla-fxa/webcrypto-liner.git#6b3ad971b3b1f0d4da3855c6ceee9b3afa9f0eeb"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { Crypto } from '@peculiar/webcrypto';
|
||||
import fetch, { Headers } from 'node-fetch';
|
||||
import { btoa } from 'abab';
|
||||
import AuthClient from './lib/client';
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
fetch: typeof fetch,
|
||||
Headers: typeof Headers,
|
||||
crypto: Crypto,
|
||||
btoa: typeof btoa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.crypto = new Crypto();
|
||||
global.fetch = fetch;
|
||||
global.Headers = Headers;
|
||||
global.btoa = btoa;
|
||||
|
||||
export default AuthClient;
|
||||
export * from './lib/client';
|
|
@ -1,7 +1,8 @@
|
|||
import { assert } from 'chai';
|
||||
import { getCredentials, unbundleKeyFetchResponse } from 'lib/auth/crypto';
|
||||
import assert from 'assert';
|
||||
import '../server'; // must import this to run with nodejs
|
||||
import { getCredentials, unbundleKeyFetchResponse } from 'fxa-auth-client/lib/crypto';
|
||||
|
||||
describe('lib/auth/crypto', () => {
|
||||
describe('lib/crypto', () => {
|
||||
describe('getCredentials', () => {
|
||||
it('returns the correct authPW and unwrapBKey', async () => {
|
||||
const email = 'andré@example.org';
|
|
@ -1,8 +1,9 @@
|
|||
import { assert } from 'chai';
|
||||
import { deriveHawkCredentials, hawkHeader } from 'lib/auth/hawk';
|
||||
import { uint8ToHex } from 'lib/auth/utils';
|
||||
import assert from 'assert';
|
||||
import '../server'; // must import this to run with nodejs
|
||||
import { deriveHawkCredentials, hawkHeader } from 'fxa-auth-client/lib/hawk';
|
||||
import { uint8ToHex } from 'fxa-auth-client/lib/utils';
|
||||
|
||||
describe('lib/auth/hawk', () => {
|
||||
describe('lib/hawk', () => {
|
||||
describe('header', () => {
|
||||
const encoder = new TextEncoder();
|
||||
const credentials = {
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../_dev/tsconfig.browser.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "./dist/browser",
|
||||
"types": ["mocha"]
|
||||
},
|
||||
"include": ["./lib/**/*", "./browser.ts"],
|
||||
"exclude": ["dist","node_modules"]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../_dev/tsconfig.node.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "./dist/server",
|
||||
"types": ["mocha"]
|
||||
},
|
||||
"include": ["./lib/**/*", "./server.ts"],
|
||||
"exclude": ["dist","node_modules"]
|
||||
}
|
|
@ -117,7 +117,7 @@ Use the `FXA_L10N_SHA` to pin L10N files to certain SHA. If not set then the `ma
|
|||
|
||||
## Reference Client
|
||||
|
||||
[fxa-js-client](../fxa-js-client)
|
||||
[fxa-auth-client](../fxa-auth-client)
|
||||
|
||||
## Dev Deployment
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ For a prose description of the client/server protocol
|
|||
and details on how each parameter is derived,
|
||||
see the [API design document](https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol).
|
||||
For a reference client implementation,
|
||||
see [`mozilla/fxa-js-client`](https://github.com/mozilla/fxa-js-client).
|
||||
see [`fxa-auth-client`](https://github.com/mozilla/fxa/tree/main/packages/fxa-auth-client).
|
||||
|
||||
<!--end-abstract-->
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import Session from './session';
|
|||
import SignInReasons from './sign-in-reasons';
|
||||
import VerificationReasons from './verification-reasons';
|
||||
import VerificationMethods from './verification-methods';
|
||||
import AuthClient from './auth/client';
|
||||
import AuthClient from 'fxa-auth-client/browser';
|
||||
|
||||
function trim(str) {
|
||||
return $.trim(str);
|
||||
|
@ -72,7 +72,7 @@ function wrapClientToNormalizeErrors(client) {
|
|||
return wrappedClient;
|
||||
}
|
||||
|
||||
// Class method decorator to get an fxa-js-client instance and pass
|
||||
// Class method decorator to get an fxa-auth-client instance and pass
|
||||
// it as the first argument to the method.
|
||||
function withClient(callback) {
|
||||
return function (...args) {
|
||||
|
@ -83,7 +83,7 @@ function withClient(callback) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a delegate method to the fxa-js-client.
|
||||
* Create a delegate method to the fxa-auth-client.
|
||||
*
|
||||
* @param {String} method to delegate to.
|
||||
* @returns {Function}
|
||||
|
@ -92,7 +92,7 @@ function createClientDelegate(method) {
|
|||
return function (...args) {
|
||||
return this._getClient().then((client) => {
|
||||
if (!_.isFunction(client[method])) {
|
||||
throw new Error(`Invalid method on fxa-js-client: ${method}`);
|
||||
throw new Error(`Invalid method on fxa-auth-client: ${method}`);
|
||||
}
|
||||
return client[method](...args);
|
||||
});
|
||||
|
@ -225,7 +225,7 @@ FxaClientWrapper.prototype = {
|
|||
* relier's context.
|
||||
* @param {Boolean} [options.skipCaseError] - if set to true, INCORRECT_EMAIL_CASE
|
||||
* errors will be returned to be handled locally instead of automatically
|
||||
* being retried in the fxa-js-client.
|
||||
* being retried in the fxa-auth-client.
|
||||
* @param {String} [options.unblockCode] - Unblock code.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
@ -313,7 +313,7 @@ FxaClientWrapper.prototype = {
|
|||
* verification link if the user must verify their email.
|
||||
* @param {Boolean} [options.skipCaseError] - if set to true, INCORRECT_EMAIL_CASE
|
||||
* errors will be returned to be handled locally instead of automatically
|
||||
* being retried in the fxa-js-client.
|
||||
* being retried in the fxa-auth-client.
|
||||
* @param {String} [options.unblockCode] - Unblock code.
|
||||
* @param {String} [options.originalLoginEmail] - the original email address as entered
|
||||
* by the user, if different from the one used for login.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import $ from 'jquery';
|
||||
import AuthErrors from 'lib/auth-errors';
|
||||
import chai from 'chai';
|
||||
import AuthClient from 'lib/auth/client';
|
||||
import AuthClient from 'fxa-auth-client/browser';
|
||||
import FxaClientWrapper from 'lib/fxa-client';
|
||||
import OAuthRelier from 'models/reliers/oauth';
|
||||
import RecoveryKey from 'lib/crypto/recovery-keys';
|
||||
|
@ -1567,7 +1567,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('sendSms', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
sinon.stub(realClient, 'sendSms').callsFake(() => Promise.resolve());
|
||||
|
||||
return client
|
||||
|
@ -1624,7 +1624,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('securityEvents', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const events = [
|
||||
{
|
||||
name: 'account.login',
|
||||
|
@ -1654,7 +1654,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('deleteSecurityEvents', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
sinon.stub(realClient, 'deleteSecurityEvents').callsFake(() => {
|
||||
return Promise.resolve({});
|
||||
});
|
||||
|
@ -1671,7 +1671,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('smsStatus', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
sinon.stub(realClient, 'smsStatus').callsFake(() =>
|
||||
Promise.resolve({
|
||||
country: 'GB',
|
||||
|
@ -1693,7 +1693,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('consumeSigninCode', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const resp = {
|
||||
email: 'testuser@testuser.com',
|
||||
};
|
||||
|
@ -1711,7 +1711,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('createTotpToken', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const resp = {
|
||||
qrCodeUrl: 'data:image/png;base64,iVBOR:',
|
||||
secret: 'superdupersecretcode',
|
||||
|
@ -1728,7 +1728,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('deleteTotpToken', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const resp = {};
|
||||
sinon
|
||||
.stub(realClient, 'deleteTotpToken')
|
||||
|
@ -1742,7 +1742,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('checkTotpTokenExists', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const resp = {
|
||||
exists: true,
|
||||
};
|
||||
|
@ -1758,7 +1758,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('verifyTotpCode', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const resp = {
|
||||
success: true,
|
||||
};
|
||||
|
@ -1788,7 +1788,7 @@ describe('lib/fxa-client', function () {
|
|||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const bundle = 'some cool base64 encrypted data';
|
||||
const keys = {
|
||||
kB: '12341234',
|
||||
|
@ -1859,7 +1859,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('verifyIdToken', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
sinon
|
||||
.stub(realClient, 'verifyIdToken')
|
||||
.callsFake(() => Promise.resolve());
|
||||
|
@ -1873,7 +1873,7 @@ describe('lib/fxa-client', function () {
|
|||
});
|
||||
|
||||
describe('createCadReminder', () => {
|
||||
it('delegates to the fxa-js-client', () => {
|
||||
it('delegates to the fxa-auth-client', () => {
|
||||
const sessionToken = 'cool token bro';
|
||||
sinon.stub(realClient, 'createCadReminder').resolves();
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ import Session from 'lib/session';
|
|||
require('./spec/head/startup-styles');
|
||||
require('./spec/lib/app-start');
|
||||
require('./spec/lib/auth-errors');
|
||||
require('./spec/lib/auth/crypto');
|
||||
require('./spec/lib/auth/hawk');
|
||||
require('./spec/lib/channels/duplex');
|
||||
require('./spec/lib/channels/fx-desktop-v1');
|
||||
require('./spec/lib/channels/inter-tab');
|
||||
|
|
|
@ -90,13 +90,13 @@ Views represent either an entire screen or a portion of a screen. Users enter da
|
|||
|
||||
A templates is a serialized HTML representation of a View. A view renders a template using data available to it and writes the rendered template to the DOM. Templates use the [mustache](http://mustache.github.io/) templating library.
|
||||
|
||||
#### Clients (fxa-js-client, fxa-oauth-client, fxa-profile-client)
|
||||
#### Clients (fxa-auth-client, fxa-oauth-client, fxa-profile-client)
|
||||
|
||||
Communication with external servers are done via client libraries.
|
||||
|
||||
##### [fxa-js-client](https://github.com/mozilla/fxa-js-client)
|
||||
##### [fxa-auth-client](https://github.com/mozilla/fxa/tree/main/packages/fxa-auth-client)
|
||||
|
||||
The fxa-js-client communicates with the Firefox Accounts [Auth Server](https://github.com/mozilla/fxa/blob/main/packages/fxa-auth-server/). The fxa-js-client is used for all aspects of authenticating a user - sign up, sign in, password reset, etc.
|
||||
The fxa-auth-client communicates with the Firefox Accounts [Auth Server](https://github.com/mozilla/fxa/blob/main/packages/fxa-auth-server/). The fxa-auth-client is used for all aspects of authenticating a user - sign up, sign in, password reset, etc.
|
||||
|
||||
##### [oauth-client](https://github.com/mozilla/fxa/blob/main/packages/fxa-content-server/app/scripts/lib/oauth-client.js)
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
"extract-loader": "2.0.1",
|
||||
"fast-text-encoding": "^1.0.1",
|
||||
"file-loader": "^4.3.0",
|
||||
"fxa-auth-client": "workspace:*",
|
||||
"fxa-common-password-list": "0.0.4",
|
||||
"fxa-crypto-relier": "2.3.0",
|
||||
"fxa-geodb": "workspace:*",
|
||||
|
|
|
@ -7,7 +7,6 @@ const cp = require('child_process');
|
|||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
const nodeXMLHttpRequest = require('xmlhttprequest');
|
||||
const path = require('path');
|
||||
const pollUntil = require('@theintern/leadfoot/helpers/pollUntil').default;
|
||||
const Querystring = require('querystring');
|
||||
|
@ -21,7 +20,7 @@ const Url = require('url');
|
|||
const otplib = require('otplib');
|
||||
otplib.authenticator.options = { encoding: 'hex' };
|
||||
|
||||
const FxaClient = require('../../../../fxa-js-client/client/FxAccountClient');
|
||||
const AuthClient = require('fxa-auth-client').default;
|
||||
const config = intern._config;
|
||||
|
||||
const AUTH_SERVER_ROOT = config.fxaAuthRoot;
|
||||
|
@ -660,14 +659,12 @@ const noSuchElement = thenify(function (selector, timeoutMS = 0) {
|
|||
});
|
||||
|
||||
/**
|
||||
* Get an fxa-js-client instance
|
||||
* Get an fxa-auth-client instance
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getFxaClient() {
|
||||
return new FxaClient(AUTH_SERVER_ROOT, {
|
||||
xhr: nodeXMLHttpRequest.XMLHttpRequest,
|
||||
});
|
||||
return new AuthClient(AUTH_SERVER_ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -94,7 +94,7 @@ set -o xtrace # echo the following commands
|
|||
|
||||
# The build below requires dependencies `yum install -y gcc-c++ GraphicMagick`.
|
||||
|
||||
npx yarn workspaces focus fxa-content-server fxa-js-client
|
||||
npx yarn workspaces focus fxa-content-server
|
||||
|
||||
# output the Firefox version number
|
||||
$FXA_FIREFOX_BINARY --version 2>/dev/null # squelch annoying 'GLib-CRITICAL **' message
|
||||
|
|
|
@ -78,7 +78,8 @@ const webpackConfig = {
|
|||
'webcrypto-liner': require.resolve('webcrypto-liner/build/shim'),
|
||||
// Webpack 4 doesn't support the "exports" property of package.json
|
||||
// so unfortunately we need to remap it here as well.
|
||||
'fxa-react/components/Survey': require.resolve('fxa-react/components/Survey')
|
||||
'fxa-react/components/Survey': require.resolve('fxa-react/components/Survey'),
|
||||
'fxa-auth-client/browser': require.resolve('fxa-auth-client/browser')
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"convict": "^6.0.0",
|
||||
"convict-format-with-moment": "^6.0.0",
|
||||
"convict-format-with-validator": "^6.0.0",
|
||||
"fxa-js-client": "^1.0.25",
|
||||
"fxa-auth-client": "workspace:*",
|
||||
"get-stream": "^5.1.0",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-parse-resolve-info": "^4.7.0",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import 'reflect-metadata';
|
||||
|
||||
import express from 'express';
|
||||
import FxAccountClient from 'fxa-js-client';
|
||||
import AuthClient from 'fxa-auth-client';
|
||||
import { graphqlUploadExpress } from 'graphql-upload';
|
||||
import mozlog from 'mozlog';
|
||||
import { Container } from 'typedi';
|
||||
|
@ -38,7 +38,7 @@ async function run() {
|
|||
// Setup the auth client
|
||||
Container.set(
|
||||
fxAccountClientToken,
|
||||
FxAccountClient(config.authServer.url, {})
|
||||
new AuthClient(config.authServer.url)
|
||||
);
|
||||
|
||||
// Setup the databases
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Client } from 'fxa-js-client';
|
||||
import AuthClient from 'fxa-auth-client';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { AuthenticationError } from 'apollo-server';
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { fxAccountClientToken } from './constants';
|
|||
@Service()
|
||||
export class SessionTokenAuth {
|
||||
@Inject(fxAccountClientToken)
|
||||
private authClient!: Client;
|
||||
private authClient!: AuthClient;
|
||||
|
||||
public async lookupUserId(sessionToken: string): Promise<string> {
|
||||
try {
|
||||
|
|
|
@ -6,10 +6,10 @@ import { Token } from 'typedi';
|
|||
import { AppConfig } from '../config';
|
||||
import { Logger } from 'mozlog';
|
||||
import { RedisConfig } from '../config';
|
||||
import { Client } from 'fxa-js-client';
|
||||
import AuthClient from 'fxa-auth-client';
|
||||
|
||||
export const configContainerToken = new Token<AppConfig>();
|
||||
export const loggerContainerToken = new Token<Logger>();
|
||||
|
||||
export const fxAccountClientToken = new Token<Client>();
|
||||
export const fxAccountClientToken = new Token<AuthClient>();
|
||||
export const authRedisConfig = new Token<RedisConfig>();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { DataSource, DataSourceConfig } from 'apollo-datasource';
|
||||
import { Client, MetricContext } from 'fxa-js-client';
|
||||
import AuthClient, { MetricsContext } from 'fxa-auth-client';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
import { fxAccountClientToken } from '../constants';
|
||||
|
@ -25,7 +25,7 @@ export function snakeToCamelObject(obj: { [key: string]: any }) {
|
|||
}
|
||||
|
||||
export class AuthServerSource extends DataSource {
|
||||
private authClient: Client;
|
||||
private authClient: AuthClient;
|
||||
|
||||
constructor(private token: string = '') {
|
||||
super();
|
||||
|
@ -50,9 +50,9 @@ export class AuthServerSource extends DataSource {
|
|||
}
|
||||
|
||||
public createTotpToken(
|
||||
options: MetricContext
|
||||
): ReturnType<Client['createTotpToken']> {
|
||||
return this.authClient.createTotpToken(this.token, options);
|
||||
options: MetricsContext
|
||||
): ReturnType<AuthClient['createTotpToken']> {
|
||||
return this.authClient.createTotpToken(this.token, { metricsContext: options });
|
||||
}
|
||||
|
||||
public destroyTotpToken(): Promise<any> {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { DataSource, DataSourceConfig } from 'apollo-datasource';
|
||||
import { Client } from 'fxa-js-client';
|
||||
import AuthClient from 'fxa-auth-client';
|
||||
import superagent from 'superagent';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { configContainerToken, fxAccountClientToken } from '../constants';
|
|||
import { Context } from '../server';
|
||||
|
||||
export class ProfileServerSource extends DataSource {
|
||||
private authClient: Client;
|
||||
private authClient: AuthClient;
|
||||
private oauthToken: String | undefined;
|
||||
private oauthClientId: string;
|
||||
private profileServerUrl: string;
|
||||
|
|
|
@ -34,17 +34,17 @@ export class MetricsContext {
|
|||
public planId?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
public utmCampaign?: number;
|
||||
public utmCampaign?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
public utmContent?: number;
|
||||
public utmContent?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
public utmMedium?: number;
|
||||
public utmMedium?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
public utmSource?: number;
|
||||
public utmSource?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
public utmTerm?: number;
|
||||
public utmTerm?: string;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"emitDecoratorMetadata": true,
|
||||
"noEmitHelpers": true,
|
||||
"importHelpers": true,
|
||||
"types": ["fxa-js-client", "mocha", "mozlog", "node"]
|
||||
"types": ["mocha", "mozlog", "node"]
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": ["plugin:fxa/client"],
|
||||
"plugins": ["fxa"],
|
||||
"rules": {
|
||||
"strict": "off",
|
||||
"space-unary-ops": [2, {"words": false}]
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"disallowKeywords": ["eval"],
|
||||
"disallowKeywordsOnNewLine": ["else"],
|
||||
"requireSpaceBeforeBinaryOperators": ["?", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
"disallowMultipleLineStrings": true,
|
||||
"requireSpaceAfterBinaryOperators": ["?", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
"disallowSpaceAfterObjectKeys": true,
|
||||
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-"],
|
||||
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
|
||||
"maximumLineLength": 420,
|
||||
"requireCapitalizedConstructors": true,
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return"],
|
||||
"validateIndentation": 2,
|
||||
"validateLineBreaks": "LF",
|
||||
"jsDoc": {
|
||||
"checkAnnotations": "jsdoc3",
|
||||
"checkTypes": true
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"comment_1179": "1179 is prototype pollution in minimist, used by eslint, grunt, webpack. Doesn't affect us, as we don't pass untrusted external inputs to those libraries.",
|
||||
"comment_1488": "1488 is a DoS against acorn (via webpack), which only applies if untrusted user content is passed in.",
|
||||
"exceptions": [
|
||||
"https://npmjs.com/advisories/1179",
|
||||
"https://npmjs.com/advisories/1488"
|
||||
]
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
AUTHORS
|
||||
LICENSE
|
||||
.*
|
||||
*.sh
|
|
@ -1,6 +0,0 @@
|
|||
Vlad Filippov
|
||||
Zach Carter
|
||||
Peter deHaan
|
||||
Glen Mailer
|
||||
Brian Warner
|
||||
Shane Tomlinson
|
|
@ -1,87 +0,0 @@
|
|||
<a name="0.1.30"></a>
|
||||
|
||||
### 0.1.30 (2015-07-20)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- **client:** throw harder on bad client init ([c3b06b4d](https://github.com/mozilla/fxa-js-client/commit/c3b06b4df48ef01910c2c98fee01b676e0ea58af))
|
||||
- **tests:** fix account.js tests ([c0cc0d59](https://github.com/mozilla/fxa-js-client/commit/c0cc0d5915589366f6e6af01259065f70975eb60))
|
||||
|
||||
<a name="0.1.29"></a>
|
||||
|
||||
### 0.1.29 (2015-06-10)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- **docs:** include keys for signUp ([dff42d0d](https://github.com/mozilla/fxa-js-client/commit/dff42d0d185524cfccf02a67af0e4c696875e54c), closes [#140](https://github.com/mozilla/fxa-js-client/issues/140))
|
||||
- **tests:** remove account devices, add unlock verify code ([13c1b836](https://github.com/mozilla/fxa-js-client/commit/13c1b836ffe9ad5cdde43a4a809d83d33795ce75), closes [#151](https://github.com/mozilla/fxa-js-client/issues/151))
|
||||
|
||||
#### Features
|
||||
|
||||
- **client:**
|
||||
- Pass along `reason` in `signIn` ([b33b1d53](https://github.com/mozilla/fxa-js-client/commit/b33b1d53d8b49e1e069c733d897a063309346194))
|
||||
- signIn can now pass along a `service` option. ([0188dbb2](https://github.com/mozilla/fxa-js-client/commit/0188dbb233286cefd3145f53b39e8279ad0c6e40))
|
||||
|
||||
<a name="0.1.28"></a>
|
||||
|
||||
### 0.1.28 (2015-02-12)
|
||||
|
||||
#### Features
|
||||
|
||||
- **client:** Add account unlock functionality. ([2f8e642c](https://github.com/mozilla/fxa-js-client/commit/2f8e642c3600e29fedd3913b60e417f376593754))
|
||||
|
||||
<a name="0.1.27"></a>
|
||||
|
||||
### 0.1.27 (2014-12-09)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- **docs:** fixes typo for certificateSign ([ab22f068](https://github.com/mozilla/fxa-js-client/commit/ab22f0682bae8a70768562fd9f3b6057243f3475))
|
||||
|
||||
#### Features
|
||||
|
||||
- **client:** Return `unwrapBKey` in `signUp` if `keys=true` is specified. ([1cd19e52](https://github.com/mozilla/fxa-js-client/commit/1cd19e52feb188905ae41c5d66e540fa2b1aee5b))
|
||||
|
||||
<a name="0.1.26"></a>
|
||||
|
||||
### 0.1.26 (2014-09-23)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- **request:** return an error object when the response is an HTML error page ([38a25556](https://github.com/mozilla/fxa-js-client/commit/38a25556001c2afcc9f9e87901964bca04bca624))
|
||||
|
||||
<a name="0.1.25"></a>
|
||||
|
||||
### 0.1.25 (2014-09-15)
|
||||
|
||||
#### Features
|
||||
|
||||
- **client:** Pass along the `resume` parameter to the auth-server ([07cff4ec](https://github.com/mozilla/fxa-js-client/commit/07cff4ec9568f2243400755dbed7ce4c077aa02b))
|
||||
|
||||
<a name="0.1.24"></a>
|
||||
|
||||
### 0.1.24 (2014-09-03)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- **tests:** use a locale that is supported by the auth-mailer for header tests ([2e13d22e](https://github.com/mozilla/fxa-js-client/commit/2e13d22e30751b8cea836fe5585a696fdbb79149))
|
||||
|
||||
#### Features
|
||||
|
||||
- **client:** signUp accepts a `preVerifyToken` option. ([35b4b232](https://github.com/mozilla/fxa-js-client/commit/35b4b2326a452520efb7901ae53411f1b42baabe))
|
||||
|
||||
<a name="0.1.23"></a>
|
||||
|
||||
### 0.1.23 (2014-06-12)
|
||||
|
||||
<a name="0.1.22"></a>
|
||||
|
||||
### 0.1.22 (2014-06-11)
|
||||
|
||||
<a name="0.1.20"></a>
|
||||
|
||||
### 0.1.20 (2014-05-16)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- **xhr:** make the default payload null ([83666223](https://github.com/mozilla/fxa-js-client/commit/83666223b6fdf4c6993bb4fefce9f0d63c6b38d4))
|
|
@ -1,101 +0,0 @@
|
|||
# Contribution guidelines to fxa-js-client
|
||||
|
||||
Anyone is welcome to help with Firefox Accounts. Feel free to get in touch with other community members on Matrix, the
|
||||
mailing list or through issues here on GitHub.
|
||||
|
||||
- Matrix: [#fxa:mozilla.org](https://chat.mozilla.org/#/room/#fxa:mozilla.org)
|
||||
- Mailing list: <https://mail.mozilla.org/listinfo/dev-fxacct>
|
||||
- and of course, [the issues list](https://github.com/mozilla/fxa-js-client/issues)
|
||||
|
||||
UPDATE: On March 2020, Mozilla moved from IRC to Matrix. For more information on Matrix, check out the following wiki article: <https://wiki.mozilla.org/Matrix>.
|
||||
|
||||
## Bug Reports
|
||||
|
||||
You can file issues here on GitHub. Please try to include as much information as you can and under what conditions
|
||||
you saw the issue.
|
||||
|
||||
## Sending Pull Requests
|
||||
|
||||
Patches should be submitted as pull requests (PR).
|
||||
|
||||
Before submitting a PR:
|
||||
|
||||
- Your code must run and pass all the automated tests before you submit your PR for review. "Work in progress" pull requests are allowed to be submitted, but should be clearly labeled as such and should not be merged until all tests pass and the code has been reviews.
|
||||
- Run `grunt lint` to make sure your code passes linting.
|
||||
- Run `npm test` to make sure all tests still pass.
|
||||
- Your patch should include new tests that cover your changes. It is your and your reviewer's responsibility to ensure your patch includes adequate tests.
|
||||
|
||||
When submitting a PR:
|
||||
|
||||
- You agree to license your code under the project's open source license ([MPL 2.0](/LICENSE)).
|
||||
- Base your branch off the current `main` (see below for an example workflow).
|
||||
- Add both your code and new tests if relevant.
|
||||
- Run `grunt lint` and `npm test` to make sure your code passes linting and tests.
|
||||
- Please do not include merge commits in pull requests; include only commits with the new relevant code.
|
||||
|
||||
See the main [README.md](/README.md) for information on prerequisites, installing, running and testing.
|
||||
|
||||
## Code Review
|
||||
|
||||
This project is production Mozilla code and subject to our [engineering practices and quality standards](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Committing_Rules_and_Responsibilities). Every patch must be peer reviewed. This project is part of the [Firefox Accounts module](https://wiki.mozilla.org/Modules/Other#Firefox_Accounts), and your patch must be reviewed by one of the listed module owners or peers.
|
||||
|
||||
## Build Library
|
||||
|
||||
Note: Java is required to build the library due to a custom SJCL build.
|
||||
|
||||
```
|
||||
npm run-script setup
|
||||
npm start
|
||||
```
|
||||
|
||||
The `build` directory should have `fxa-client.js` and `fxa-client.min.js`.
|
||||
|
||||
## Development
|
||||
|
||||
`grunt build` - builds the regular and minified version of the library
|
||||
|
||||
`grunt dev` - builds the library, runs eslint, shows library size, runs tests, watches for changes
|
||||
|
||||
`grunt debug` - builds the regular library, runs test, watches for changes. Helpful when you are debugging.
|
||||
|
||||
### SJCL Notes
|
||||
|
||||
Currently [SJCL](http://bitwiseshiftleft.github.io/sjcl/) is built with `./configure --without-random --without-ocb2 --without-gcm --without-ccm`.
|
||||
Adjust this if you need other SJCL features.
|
||||
|
||||
## Testing
|
||||
|
||||
This package uses [Mocha](https://mochajs.org/) to test its code. By default `npm test` will first lint the code and then test all files under `tests/lib`, and `npm run test-local` will run the suite against the local fxa-auth-server running on port 9000.
|
||||
|
||||
Test specific tests with the following commands:
|
||||
|
||||
```bash
|
||||
# Test only tests/lib/sms.js
|
||||
npx mocha tests/lib/sms.js
|
||||
|
||||
# Grep for "interface is correct"
|
||||
npx mocha tests/lib -g "interface is correct"
|
||||
```
|
||||
|
||||
Refer to Mocha's [CLI documentation](https://mochajs.org/#command-line-usage) for more advanced test configuration.
|
||||
|
||||
## Git Commit Guidelines
|
||||
|
||||
We loosely follow the [Angular commit guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#type) of `<type>(<scope>): <subject>` where `type` must be one of:
|
||||
|
||||
- **feat**: A new feature
|
||||
- **fix**: A bug fix
|
||||
- **docs**: Documentation only changes
|
||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
semi-colons, etc)
|
||||
- **refactor**: A code change that neither fixes a bug or adds a feature
|
||||
- **perf**: A code change that improves performance
|
||||
- **test**: Adding missing tests
|
||||
- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||
generation
|
||||
|
||||
## Documentation
|
||||
|
||||
Running `grunt doc` will create a `docs` directory, browse the documentation by opening `docs/index.html`.
|
||||
|
||||
Write documentation using [YUIDoc Syntax](http://yui.github.io/yuidoc/syntax/).
|
|
@ -1,28 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
// load all grunt tasks matching the `grunt-*` pattern
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
var pkg = grunt.file.readJSON('package.json');
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: pkg,
|
||||
pkgReadOnly: pkg,
|
||||
});
|
||||
|
||||
// load local Grunt tasks
|
||||
grunt.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('build', 'Build client', ['clean', 'lint', 'webpack:app']);
|
||||
|
||||
grunt.registerTask('lint', 'Alias for eslint', ['eslint']);
|
||||
|
||||
grunt.registerTask('default', ['build']);
|
||||
|
||||
grunt.registerTask('dev', ['watch:dev']);
|
||||
|
||||
grunt.registerTask('debug', ['watch:debug']);
|
||||
};
|
|
@ -1,354 +0,0 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# fxa-js-client
|
||||
|
||||
> Web client that talks to the Firefox Accounts API server
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
<script src="../build/fxa-client.js"></script>
|
||||
var client = new FxAccountClient();
|
||||
// Sign Up
|
||||
client.signUp(email, password);
|
||||
// Sign In
|
||||
client.signIn(email, password);
|
||||
```
|
||||
|
||||
See [Library Documentation](http://mozilla.github.io/fxa-js-client/classes/FxAccountClient.html) for more.
|
||||
|
||||
## Development
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for details on building, developing, and testing the library.
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,162 +0,0 @@
|
|||
/* eslint-disable id-blacklist */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const sjcl = require('sjcl');
|
||||
const hkdf = require('./hkdf');
|
||||
const pbkdf2 = require('./pbkdf2');
|
||||
|
||||
// Key wrapping and stretching configuration.
|
||||
var NAMESPACE = 'identity.mozilla.com/picl/v1/';
|
||||
var PBKDF2_ROUNDS = 1000;
|
||||
var STRETCHED_PASS_LENGTH_BYTES = 32 * 8;
|
||||
|
||||
var HKDF_SALT = sjcl.codec.hex.toBits('00');
|
||||
var HKDF_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* Key Wrapping with a name
|
||||
*
|
||||
* @method kw
|
||||
* @static
|
||||
* @param {String} name The name of the salt
|
||||
* @return {bitArray} the salt combination with the namespace
|
||||
*/
|
||||
function kw(name) {
|
||||
return sjcl.codec.utf8String.toBits(NAMESPACE + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key Wrapping with a name and an email
|
||||
*
|
||||
* @method kwe
|
||||
* @static
|
||||
* @param {String} name The name of the salt
|
||||
* @param {String} email The email of the user.
|
||||
* @return {bitArray} the salt combination with the namespace
|
||||
*/
|
||||
function kwe(name, email) {
|
||||
return sjcl.codec.utf8String.toBits(NAMESPACE + name + ':' + email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @class credentials
|
||||
* @constructor
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* Setup credentials
|
||||
*
|
||||
* @method setup
|
||||
* @param {String} emailInput
|
||||
* @param {String} passwordInput
|
||||
* @return {Promise} A promise that will be fulfilled with `result` of generated credentials
|
||||
*/
|
||||
setup: function (emailInput, passwordInput) {
|
||||
var result = {};
|
||||
var email = kwe('quickStretch', emailInput);
|
||||
var password = sjcl.codec.utf8String.toBits(passwordInput);
|
||||
|
||||
result.emailUTF8 = emailInput;
|
||||
result.passwordUTF8 = passwordInput;
|
||||
|
||||
return pbkdf2
|
||||
.derive(password, email, PBKDF2_ROUNDS, STRETCHED_PASS_LENGTH_BYTES)
|
||||
.then(function (quickStretchedPW) {
|
||||
result.quickStretchedPW = quickStretchedPW;
|
||||
|
||||
return hkdf(
|
||||
quickStretchedPW,
|
||||
kw('authPW'),
|
||||
HKDF_SALT,
|
||||
HKDF_LENGTH
|
||||
).then(function (authPW) {
|
||||
result.authPW = authPW;
|
||||
|
||||
return hkdf(
|
||||
quickStretchedPW,
|
||||
kw('unwrapBkey'),
|
||||
HKDF_SALT,
|
||||
HKDF_LENGTH
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(function (unwrapBKey) {
|
||||
result.unwrapBKey = unwrapBKey;
|
||||
return result;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Wrap
|
||||
*
|
||||
* @method wrap
|
||||
* @param {bitArray} bitArray1
|
||||
* @param {bitArray} bitArray2
|
||||
* @return {bitArray} wrap result of the two bitArrays
|
||||
*/
|
||||
xor: function (bitArray1, bitArray2) {
|
||||
var result = [];
|
||||
|
||||
for (var i = 0; i < bitArray1.length; i++) {
|
||||
result[i] = bitArray1[i] ^ bitArray2[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* Unbundle the WrapKB
|
||||
* @param {String} key Bundle Key in hex
|
||||
* @param {String} bundle Key bundle in hex
|
||||
* @returns {*}
|
||||
*/
|
||||
unbundleKeyFetchResponse: function (key, bundle) {
|
||||
var self = this;
|
||||
var bitBundle = sjcl.codec.hex.toBits(bundle);
|
||||
|
||||
return this.deriveBundleKeys(key, 'account/keys').then(function (keys) {
|
||||
var ciphertext = sjcl.bitArray.bitSlice(bitBundle, 0, 8 * 64);
|
||||
var expectedHmac = sjcl.bitArray.bitSlice(bitBundle, 8 * -32);
|
||||
var hmac = new sjcl.misc.hmac(keys.hmacKey, sjcl.hash.sha256);
|
||||
hmac.update(ciphertext);
|
||||
|
||||
if (!sjcl.bitArray.equal(hmac.digest(), expectedHmac)) {
|
||||
throw new Error('Bad HMac');
|
||||
}
|
||||
|
||||
var keyAWrapB = self.xor(
|
||||
sjcl.bitArray.bitSlice(bitBundle, 0, 8 * 64),
|
||||
keys.xorKey
|
||||
);
|
||||
|
||||
return {
|
||||
kA: sjcl.codec.hex.fromBits(
|
||||
sjcl.bitArray.bitSlice(keyAWrapB, 0, 8 * 32)
|
||||
),
|
||||
wrapKB: sjcl.codec.hex.fromBits(
|
||||
sjcl.bitArray.bitSlice(keyAWrapB, 8 * 32)
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Derive the HMAC and XOR keys required to encrypt a given size of payload.
|
||||
* @param {String} key Hex Bundle Key
|
||||
* @param {String} keyInfo Bundle Key Info
|
||||
* @returns {Object} hmacKey, xorKey
|
||||
*/
|
||||
deriveBundleKeys: function (key, keyInfo) {
|
||||
var bitKeyInfo = kw(keyInfo);
|
||||
var salt = sjcl.codec.hex.toBits('');
|
||||
key = sjcl.codec.hex.toBits(key);
|
||||
|
||||
return hkdf(key, bitKeyInfo, salt, 3 * 32).then(function (keyMaterial) {
|
||||
return {
|
||||
hmacKey: sjcl.bitArray.bitSlice(keyMaterial, 0, 8 * 32),
|
||||
xorKey: sjcl.bitArray.bitSlice(keyMaterial, 8 * 32),
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = {
|
||||
INVALID_TIMESTAMP: 111,
|
||||
INCORRECT_EMAIL_CASE: 120,
|
||||
};
|
|
@ -1,586 +0,0 @@
|
|||
/* eslint-disable no-prototype-builtins, no-useless-escape, no-console */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const sjcl = require('sjcl');
|
||||
|
||||
/*
|
||||
HTTP Hawk Authentication Scheme
|
||||
Copyright (c) 2012-2013, Eran Hammer <eran@hueniverse.com>
|
||||
MIT Licensed
|
||||
*/
|
||||
|
||||
// Declare namespace
|
||||
|
||||
var hawk = {};
|
||||
|
||||
hawk.client = {
|
||||
// Generate an Authorization header for a given request
|
||||
|
||||
/*
|
||||
uri: 'http://example.com/resource?a=b'
|
||||
method: HTTP verb (e.g. 'GET', 'POST')
|
||||
options: {
|
||||
|
||||
// Required
|
||||
|
||||
credentials: {
|
||||
id: 'dh37fgj492je',
|
||||
key: 'aoijedoaijsdlaksjdl',
|
||||
algorithm: 'sha256' // 'sha1', 'sha256'
|
||||
},
|
||||
|
||||
// Optional
|
||||
|
||||
ext: 'application-specific', // Application specific data sent via the ext attribute
|
||||
timestamp: Date.now() / 1000, // A pre-calculated timestamp in seconds
|
||||
nonce: '2334f34f', // A pre-generated nonce
|
||||
localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)
|
||||
payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided)
|
||||
contentType: 'application/json', // Payload content-type (ignored if hash provided)
|
||||
hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash
|
||||
app: '24s23423f34dx', // Oz application id
|
||||
dlg: '234sz34tww3sd' // Oz delegated-by application id
|
||||
}
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
header: function (uri, method, options) {
|
||||
/*eslint complexity: [2, 21] */
|
||||
var result = {
|
||||
field: '',
|
||||
artifacts: {},
|
||||
};
|
||||
|
||||
// Validate inputs
|
||||
|
||||
if (
|
||||
!uri ||
|
||||
(typeof uri !== 'string' && typeof uri !== 'object') ||
|
||||
!method ||
|
||||
typeof method !== 'string' ||
|
||||
!options ||
|
||||
typeof options !== 'object'
|
||||
) {
|
||||
result.err = 'Invalid argument type';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Application time
|
||||
|
||||
var timestamp =
|
||||
options.timestamp ||
|
||||
Math.floor(
|
||||
(hawk.utils.now() + (options.localtimeOffsetMsec || 0)) / 1000
|
||||
);
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (
|
||||
!credentials ||
|
||||
!credentials.id ||
|
||||
!credentials.key ||
|
||||
!credentials.algorithm
|
||||
) {
|
||||
result.err = 'Invalid credential object';
|
||||
return result;
|
||||
}
|
||||
|
||||
if (
|
||||
hawk.utils.baseIndexOf(hawk.crypto.algorithms, credentials.algorithm) ===
|
||||
-1
|
||||
) {
|
||||
result.err = 'Unknown algorithm';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse URI
|
||||
|
||||
if (typeof uri === 'string') {
|
||||
uri = hawk.utils.parseUri(uri);
|
||||
}
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var artifacts = {
|
||||
ts: timestamp,
|
||||
nonce: options.nonce || hawk.utils.randomString(6),
|
||||
method: method,
|
||||
resource: uri.relative,
|
||||
host: uri.hostname,
|
||||
port: uri.port,
|
||||
hash: options.hash,
|
||||
ext: options.ext,
|
||||
app: options.app,
|
||||
dlg: options.dlg,
|
||||
};
|
||||
|
||||
result.artifacts = artifacts;
|
||||
|
||||
// Calculate payload hash
|
||||
|
||||
if (!artifacts.hash && options.hasOwnProperty('payload')) {
|
||||
artifacts.hash = hawk.crypto.calculatePayloadHash(
|
||||
options.payload,
|
||||
credentials.algorithm,
|
||||
options.contentType
|
||||
);
|
||||
}
|
||||
|
||||
var mac = hawk.crypto.calculateMac('header', credentials, artifacts);
|
||||
|
||||
// Construct header
|
||||
|
||||
var hasExt =
|
||||
artifacts.ext !== null &&
|
||||
artifacts.ext !== undefined &&
|
||||
artifacts.ext !== ''; // Other falsey values allowed
|
||||
var header =
|
||||
'Hawk id="' +
|
||||
credentials.id +
|
||||
'", ts="' +
|
||||
artifacts.ts +
|
||||
'", nonce="' +
|
||||
artifacts.nonce +
|
||||
(artifacts.hash ? '", hash="' + artifacts.hash : '') +
|
||||
(hasExt
|
||||
? '", ext="' + hawk.utils.escapeHeaderAttribute(artifacts.ext)
|
||||
: '') +
|
||||
'", mac="' +
|
||||
mac +
|
||||
'"';
|
||||
|
||||
if (artifacts.app) {
|
||||
header +=
|
||||
', app="' +
|
||||
artifacts.app +
|
||||
(artifacts.dlg ? '", dlg="' + artifacts.dlg : '') +
|
||||
'"';
|
||||
}
|
||||
|
||||
result.field = header;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// Validate server response
|
||||
|
||||
/*
|
||||
request: object created via 'new XMLHttpRequest()' after response received
|
||||
artifacts: object recieved from header().artifacts
|
||||
options: {
|
||||
payload: optional payload received
|
||||
required: specifies if a Server-Authorization header is required. Defaults to 'false'
|
||||
}
|
||||
*/
|
||||
|
||||
authenticate: function (request, credentials, artifacts, options) {
|
||||
options = options || {};
|
||||
|
||||
if (request.getResponseHeader('www-authenticate')) {
|
||||
// Parse HTTP WWW-Authenticate header
|
||||
|
||||
var attrsAuth = hawk.utils.parseAuthorizationHeader(
|
||||
request.getResponseHeader('www-authenticate'),
|
||||
['ts', 'tsm', 'error']
|
||||
);
|
||||
if (!attrsAuth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attrsAuth.ts) {
|
||||
var tsm = hawk.crypto.calculateTsMac(attrsAuth.ts, credentials);
|
||||
if (tsm !== attrsAuth.tsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hawk.utils.setNtpOffset(
|
||||
attrsAuth.ts - Math.floor(new Date().getTime() / 1000)
|
||||
); // Keep offset at 1 second precision
|
||||
}
|
||||
}
|
||||
|
||||
// Parse HTTP Server-Authorization header
|
||||
|
||||
if (
|
||||
!request.getResponseHeader('server-authorization') &&
|
||||
!options.required
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var attributes = hawk.utils.parseAuthorizationHeader(
|
||||
request.getResponseHeader('server-authorization'),
|
||||
['mac', 'ext', 'hash']
|
||||
);
|
||||
if (!attributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var modArtifacts = {
|
||||
ts: artifacts.ts,
|
||||
nonce: artifacts.nonce,
|
||||
method: artifacts.method,
|
||||
resource: artifacts.resource,
|
||||
host: artifacts.host,
|
||||
port: artifacts.port,
|
||||
hash: attributes.hash,
|
||||
ext: attributes.ext,
|
||||
app: artifacts.app,
|
||||
dlg: artifacts.dlg,
|
||||
};
|
||||
|
||||
var mac = hawk.crypto.calculateMac('response', credentials, modArtifacts);
|
||||
if (mac !== attributes.mac) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.hasOwnProperty('payload')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!attributes.hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var calculatedHash = hawk.crypto.calculatePayloadHash(
|
||||
options.payload,
|
||||
credentials.algorithm,
|
||||
request.getResponseHeader('content-type')
|
||||
);
|
||||
return calculatedHash === attributes.hash;
|
||||
},
|
||||
|
||||
message: function (host, port, message, options) {
|
||||
// Validate inputs
|
||||
|
||||
if (
|
||||
!host ||
|
||||
typeof host !== 'string' ||
|
||||
!port ||
|
||||
typeof port !== 'number' ||
|
||||
message === null ||
|
||||
message === undefined ||
|
||||
typeof message !== 'string' ||
|
||||
!options ||
|
||||
typeof options !== 'object'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Application time
|
||||
|
||||
var timestamp =
|
||||
options.timestamp ||
|
||||
Math.floor(
|
||||
(hawk.utils.now() + (options.localtimeOffsetMsec || 0)) / 1000
|
||||
);
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (
|
||||
!credentials ||
|
||||
!credentials.id ||
|
||||
!credentials.key ||
|
||||
!credentials.algorithm
|
||||
) {
|
||||
// Invalid credential object
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var artifacts = {
|
||||
ts: timestamp,
|
||||
nonce: options.nonce || hawk.utils.randomString(6),
|
||||
host: host,
|
||||
port: port,
|
||||
hash: hawk.crypto.calculatePayloadHash(message, credentials.algorithm),
|
||||
};
|
||||
|
||||
// Construct authorization
|
||||
|
||||
var result = {
|
||||
id: credentials.id,
|
||||
ts: artifacts.ts,
|
||||
nonce: artifacts.nonce,
|
||||
hash: artifacts.hash,
|
||||
mac: hawk.crypto.calculateMac('message', credentials, artifacts),
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
authenticateTimestamp: function (message, credentials, updateClock) {
|
||||
// updateClock defaults to true
|
||||
|
||||
var tsm = hawk.crypto.calculateTsMac(message.ts, credentials);
|
||||
if (tsm !== message.tsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (updateClock !== false) {
|
||||
hawk.utils.setNtpOffset(
|
||||
message.ts - Math.floor(new Date().getTime() / 1000)
|
||||
); // Keep offset at 1 second precision
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
hawk.crypto = {
|
||||
headerVersion: '1',
|
||||
|
||||
algorithms: ['sha1', 'sha256'],
|
||||
|
||||
calculateMac: function (type, credentials, options) {
|
||||
var normalized = hawk.crypto.generateNormalizedString(type, options);
|
||||
var hmac = new sjcl.misc.hmac(credentials.key, sjcl.hash.sha256);
|
||||
hmac.update(normalized);
|
||||
|
||||
return sjcl.codec.base64.fromBits(hmac.digest());
|
||||
},
|
||||
|
||||
generateNormalizedString: function (type, options) {
|
||||
var normalized =
|
||||
'hawk.' +
|
||||
hawk.crypto.headerVersion +
|
||||
'.' +
|
||||
type +
|
||||
'\n' +
|
||||
options.ts +
|
||||
'\n' +
|
||||
options.nonce +
|
||||
'\n' +
|
||||
(options.method || '').toUpperCase() +
|
||||
'\n' +
|
||||
(options.resource || '') +
|
||||
'\n' +
|
||||
options.host.toLowerCase() +
|
||||
'\n' +
|
||||
options.port +
|
||||
'\n' +
|
||||
(options.hash || '') +
|
||||
'\n';
|
||||
|
||||
if (options.ext) {
|
||||
normalized += options.ext.replace('\\', '\\\\').replace('\n', '\\n');
|
||||
}
|
||||
|
||||
normalized += '\n';
|
||||
|
||||
if (options.app) {
|
||||
normalized += options.app + '\n' + (options.dlg || '') + '\n';
|
||||
}
|
||||
|
||||
return normalized;
|
||||
},
|
||||
|
||||
calculatePayloadHash: function (payload, algorithm, contentType) {
|
||||
var hash = new sjcl.hash.sha256();
|
||||
hash
|
||||
.update('hawk.' + hawk.crypto.headerVersion + '.payload\n')
|
||||
.update(hawk.utils.parseContentType(contentType) + '\n')
|
||||
.update(payload || '')
|
||||
.update('\n');
|
||||
|
||||
return sjcl.codec.base64.fromBits(hash.finalize());
|
||||
},
|
||||
|
||||
calculateTsMac: function (ts, credentials) {
|
||||
var hmac = new sjcl.misc.hmac(credentials.key, sjcl.hash.sha256);
|
||||
hmac.update('hawk.' + hawk.crypto.headerVersion + '.ts\n' + ts + '\n');
|
||||
|
||||
return sjcl.codec.base64.fromBits(hmac.digest());
|
||||
},
|
||||
};
|
||||
|
||||
hawk.utils = {
|
||||
storage: {
|
||||
// localStorage compatible interface
|
||||
_cache: {},
|
||||
setItem: function (key, value) {
|
||||
hawk.utils.storage._cache[key] = value;
|
||||
},
|
||||
getItem: function (key) {
|
||||
return hawk.utils.storage._cache[key];
|
||||
},
|
||||
},
|
||||
|
||||
setStorage: function (storage) {
|
||||
var ntpOffset = hawk.utils.getNtpOffset() || 0;
|
||||
hawk.utils.storage = storage;
|
||||
hawk.utils.setNtpOffset(ntpOffset);
|
||||
},
|
||||
|
||||
setNtpOffset: function (offset) {
|
||||
try {
|
||||
hawk.utils.storage.setItem('hawk_ntp_offset', offset);
|
||||
} catch (err) {
|
||||
console.error('[hawk] could not write to storage.');
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
getNtpOffset: function () {
|
||||
return parseInt(hawk.utils.storage.getItem('hawk_ntp_offset') || '0', 10);
|
||||
},
|
||||
|
||||
now: function () {
|
||||
return new Date().getTime() + hawk.utils.getNtpOffset();
|
||||
},
|
||||
|
||||
escapeHeaderAttribute: function (attribute) {
|
||||
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');
|
||||
},
|
||||
|
||||
parseContentType: function (header) {
|
||||
if (!header) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return header
|
||||
.split(';')[0]
|
||||
.replace(/^\s+|\s+$/g, '')
|
||||
.toLowerCase();
|
||||
},
|
||||
|
||||
parseAuthorizationHeader: function (header, keys) {
|
||||
if (!header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something]
|
||||
if (!headerParts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var scheme = headerParts[1];
|
||||
if (scheme.toLowerCase() !== 'hawk') {
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributesString = headerParts[2];
|
||||
if (!attributesString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributes = {};
|
||||
var verify = attributesString.replace(
|
||||
/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g,
|
||||
function ($0, $1, $2) {
|
||||
// Check valid attribute names
|
||||
|
||||
if (keys.indexOf($1) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
|
||||
|
||||
if (
|
||||
$2.match(
|
||||
/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/
|
||||
) === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
|
||||
if (attributes.hasOwnProperty($1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes[$1] = $2;
|
||||
return '';
|
||||
}
|
||||
);
|
||||
|
||||
if (verify !== '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
},
|
||||
|
||||
randomString: function (size) {
|
||||
var randomSource =
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
var len = randomSource.length;
|
||||
|
||||
var result = [];
|
||||
for (var i = 0; i < size; ++i) {
|
||||
result[i] = randomSource[Math.floor(Math.random() * len)];
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
|
||||
baseIndexOf: function (array, value, fromIndex) {
|
||||
var index = (fromIndex || 0) - 1,
|
||||
length = array ? array.length : 0;
|
||||
|
||||
while (++index < length) {
|
||||
if (array[index] === value) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
parseUri: function (input) {
|
||||
// Based on: parseURI 1.2.2
|
||||
// http://blog.stevenlevithan.com/archives/parseuri
|
||||
// (c) Steven Levithan <stevenlevithan.com>
|
||||
// MIT License
|
||||
|
||||
var keys = [
|
||||
'source',
|
||||
'protocol',
|
||||
'authority',
|
||||
'userInfo',
|
||||
'user',
|
||||
'password',
|
||||
'hostname',
|
||||
'port',
|
||||
'resource',
|
||||
'relative',
|
||||
'pathname',
|
||||
'directory',
|
||||
'file',
|
||||
'query',
|
||||
'fragment',
|
||||
];
|
||||
|
||||
var uriRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?(((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?)(?:#(.*))?)/;
|
||||
var uriByNumber = uriRegex.exec(input);
|
||||
var uri = {};
|
||||
|
||||
var i = 15;
|
||||
while (i--) {
|
||||
uri[keys[i]] = uriByNumber[i] || '';
|
||||
}
|
||||
|
||||
if (uri.port === null || uri.port === '') {
|
||||
uri.port =
|
||||
uri.protocol.toLowerCase() === 'http'
|
||||
? '80'
|
||||
: uri.protocol.toLowerCase() === 'https'
|
||||
? '443'
|
||||
: '';
|
||||
}
|
||||
|
||||
return uri;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = hawk;
|
|
@ -1,39 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const sjcl = require('sjcl');
|
||||
const hkdf = require('./hkdf');
|
||||
|
||||
var PREFIX_NAME = 'identity.mozilla.com/picl/v1/';
|
||||
var bitSlice = sjcl.bitArray.bitSlice;
|
||||
var salt = sjcl.codec.hex.toBits('');
|
||||
|
||||
/**
|
||||
* @class hawkCredentials
|
||||
* @method deriveHawkCredentials
|
||||
* @param {String} tokenHex
|
||||
* @param {String} context
|
||||
* @param {int} size
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function deriveHawkCredentials(tokenHex, context, size) {
|
||||
var token = sjcl.codec.hex.toBits(tokenHex);
|
||||
var info = sjcl.codec.utf8String.toBits(PREFIX_NAME + context);
|
||||
|
||||
return hkdf(token, info, salt, size || 3 * 32).then(function (out) {
|
||||
var authKey = bitSlice(out, 8 * 32, 8 * 64);
|
||||
var bundleKey = bitSlice(out, 8 * 64);
|
||||
|
||||
return {
|
||||
algorithm: 'sha256',
|
||||
id: sjcl.codec.hex.fromBits(bitSlice(out, 0, 8 * 32)),
|
||||
key: authKey,
|
||||
bundleKey: bundleKey,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = deriveHawkCredentials;
|
|
@ -1,56 +0,0 @@
|
|||
/* eslint-disable camelcase */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const sjcl = require('sjcl');
|
||||
|
||||
/**
|
||||
* hkdf - The HMAC-based Key Derivation Function
|
||||
* based on https://github.com/mozilla/node-hkdf
|
||||
*
|
||||
* @class hkdf
|
||||
* @param {bitArray} ikm Initial keying material
|
||||
* @param {bitArray} info Key derivation data
|
||||
* @param {bitArray} salt Salt
|
||||
* @param {integer} length Length of the derived key in bytes
|
||||
* @return promise object- It will resolve with `output` data
|
||||
*/
|
||||
function hkdf(ikm, info, salt, length) {
|
||||
var mac = new sjcl.misc.hmac(salt, sjcl.hash.sha256);
|
||||
mac.update(ikm);
|
||||
|
||||
// compute the PRK
|
||||
var prk = mac.digest();
|
||||
|
||||
// hash length is 32 because only sjcl.hash.sha256 is used at this moment
|
||||
var hashLength = 32;
|
||||
var num_blocks = Math.ceil(length / hashLength);
|
||||
var prev = sjcl.codec.hex.toBits('');
|
||||
var output = '';
|
||||
|
||||
for (var i = 0; i < num_blocks; i++) {
|
||||
var hmac = new sjcl.misc.hmac(prk, sjcl.hash.sha256);
|
||||
|
||||
var input = sjcl.bitArray.concat(
|
||||
sjcl.bitArray.concat(prev, info),
|
||||
sjcl.codec.utf8String.toBits(String.fromCharCode(i + 1))
|
||||
);
|
||||
|
||||
hmac.update(input);
|
||||
|
||||
prev = hmac.digest();
|
||||
output += sjcl.codec.hex.fromBits(prev);
|
||||
}
|
||||
|
||||
var truncated = sjcl.bitArray.clamp(
|
||||
sjcl.codec.hex.toBits(output),
|
||||
length * 8
|
||||
);
|
||||
|
||||
return Promise.resolve(truncated);
|
||||
}
|
||||
|
||||
module.exports = hkdf;
|
|
@ -1,25 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
marshall: function (data) {
|
||||
return {
|
||||
deviceId: data.deviceId,
|
||||
entrypoint: data.entrypoint,
|
||||
entrypointExperiment: data.entrypointExperiment,
|
||||
entrypointVariation: data.entrypointVariation,
|
||||
flowId: data.flowId,
|
||||
flowBeginTime: data.flowBeginTime,
|
||||
utmCampaign: data.utmCampaign,
|
||||
utmContent: data.utmContent,
|
||||
utmMedium: data.utmMedium,
|
||||
utmSource: data.utmSource,
|
||||
utmTerm: data.utmTerm,
|
||||
productId: data.productId,
|
||||
planId: data.planId,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const sjcl = require('sjcl');
|
||||
|
||||
/**
|
||||
* @class pbkdf2
|
||||
* @constructor
|
||||
*/
|
||||
var pbkdf2 = {
|
||||
/**
|
||||
* @method derive
|
||||
* @param {bitArray} input The password hex buffer.
|
||||
* @param {bitArray} salt The salt string buffer.
|
||||
* @return {int} iterations the derived key bit array.
|
||||
*/
|
||||
derive: function (input, salt, iterations, len) {
|
||||
var result = sjcl.misc.pbkdf2(input, salt, iterations, len, sjcl.misc.hmac);
|
||||
return Promise.resolve(result);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = pbkdf2;
|
|
@ -1,140 +0,0 @@
|
|||
/* eslint-disable id-blacklist */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const hawk = require('./hawk');
|
||||
const ERRORS = require('./errors');
|
||||
|
||||
/**
|
||||
* @class Request
|
||||
* @constructor
|
||||
* @param {String} baseUri Base URI
|
||||
* @param {Object} xhr XMLHttpRequest constructor
|
||||
* @param {Object} [options={}] Options
|
||||
* @param {Number} [options.localtimeOffsetMsec]
|
||||
* Local time offset with the remote auth server's clock
|
||||
*/
|
||||
function Request(baseUri, xhr, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
this.baseUri = baseUri;
|
||||
this._localtimeOffsetMsec = options.localtimeOffsetMsec;
|
||||
this.xhr = xhr || XMLHttpRequest;
|
||||
this.timeout = options.timeout || 30 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @method send
|
||||
* @param {String} path Request path
|
||||
* @param {String} method HTTP Method
|
||||
* @param {Object} credentials HAWK Headers
|
||||
* @param {Object} jsonPayload JSON Payload
|
||||
* @param {Object} [options={}] Options
|
||||
* @param {String} [options.retrying]
|
||||
* Flag indicating if the request is a retry
|
||||
* @param {Array} [options.headers]
|
||||
* A set of extra headers to add to the request
|
||||
* @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
|
||||
*/
|
||||
Request.prototype.send = function request(
|
||||
path,
|
||||
method,
|
||||
credentials,
|
||||
jsonPayload,
|
||||
options
|
||||
) {
|
||||
/*eslint complexity: [2, 8] */
|
||||
var xhr = new this.xhr();
|
||||
var uri = this.baseUri + path;
|
||||
var payload = null;
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
if (jsonPayload) {
|
||||
payload = JSON.stringify(jsonPayload);
|
||||
}
|
||||
|
||||
try {
|
||||
xhr.open(method, uri);
|
||||
} catch (e) {
|
||||
return Promise.reject({
|
||||
error: 'Unknown error',
|
||||
message: e.toString(),
|
||||
errno: 999,
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
xhr.timeout = self.timeout;
|
||||
// eslint-disable-next-line complexity
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var result = xhr.responseText;
|
||||
try {
|
||||
result = JSON.parse(xhr.responseText);
|
||||
} catch (e) {}
|
||||
|
||||
if (result.errno) {
|
||||
// Try to recover from a timeskew error and not already tried
|
||||
if (result.errno === ERRORS.INVALID_TIMESTAMP && !options.retrying) {
|
||||
var serverTime = result.serverTime;
|
||||
self._localtimeOffsetMsec =
|
||||
serverTime * 1000 - new Date().getTime();
|
||||
|
||||
// add to options that the request is retrying
|
||||
options.retrying = true;
|
||||
|
||||
return self
|
||||
.send(path, method, credentials, jsonPayload, options)
|
||||
.then(resolve, reject);
|
||||
} else {
|
||||
return reject(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof xhr.status === 'undefined' || xhr.status !== 200) {
|
||||
if (result.length === 0) {
|
||||
return reject({ error: 'Timeout error', errno: 999 });
|
||||
} else {
|
||||
return reject({
|
||||
error: 'Unknown error',
|
||||
message: result,
|
||||
errno: 999,
|
||||
code: xhr.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
|
||||
// calculate Hawk header if credentials are supplied
|
||||
if (credentials) {
|
||||
var hawkHeader = hawk.client.header(uri, method, {
|
||||
credentials: credentials,
|
||||
payload: payload,
|
||||
contentType: 'application/json',
|
||||
localtimeOffsetMsec: self._localtimeOffsetMsec || 0,
|
||||
});
|
||||
xhr.setRequestHeader('authorization', hawkHeader.field);
|
||||
}
|
||||
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
|
||||
if (options && options.headers) {
|
||||
// set extra headers for this request
|
||||
for (var header in options.headers) {
|
||||
xhr.setRequestHeader(header, options.headers[header]);
|
||||
}
|
||||
}
|
||||
|
||||
xhr.send(payload);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Request;
|
|
@ -1,28 +0,0 @@
|
|||
var util = require('util');
|
||||
|
||||
var FxAccountClient = require('../client/FxAccountClient.js');
|
||||
|
||||
function NodeFxAccountClient(uri, config) {
|
||||
if (!(this instanceof FxAccountClient)) {
|
||||
return new NodeFxAccountClient(uri, config);
|
||||
}
|
||||
|
||||
if (typeof uri !== 'string') {
|
||||
config = uri || {};
|
||||
uri = config.uri;
|
||||
}
|
||||
if (typeof config === 'undefined') {
|
||||
config = {};
|
||||
}
|
||||
|
||||
if (!config.xhr) {
|
||||
config.xhr = require('xhr2');
|
||||
}
|
||||
|
||||
FxAccountClient.call(this, uri, config);
|
||||
}
|
||||
|
||||
NodeFxAccountClient.VERSION = FxAccountClient.VERSION;
|
||||
|
||||
module.exports = NodeFxAccountClient;
|
||||
util.inherits(NodeFxAccountClient, FxAccountClient);
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"name": "fxa-js-client",
|
||||
"version": "1.0.25",
|
||||
"description": "Web client that talks to the Firefox Accounts API server",
|
||||
"author": "Mozilla",
|
||||
"license": "MPL-2.0",
|
||||
"scripts": {
|
||||
"audit": "npm audit --json | audit-filter --nsp-config=.nsprc --audit=-",
|
||||
"lint": "eslint .",
|
||||
"test": "npm run lint && mocha tests/lib --reporter dot --timeout 5000",
|
||||
"test-local": "AUTH_SERVER_URL=http://localhost:9000 npm test",
|
||||
"contributors": "git shortlog -s | cut -c8- | sort -f > CONTRIBUTORS.md",
|
||||
"format": "prettier --write --config ../../_dev/.prettierrc '**'"
|
||||
},
|
||||
"main": "node/index.js",
|
||||
"files": [
|
||||
"node/",
|
||||
"client/",
|
||||
"LICENSE"
|
||||
],
|
||||
"readmeFilename": "README.md",
|
||||
"homepage": "https://github.com/mozilla/fxa/tree/main/packages/fxa-js-client",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mozilla/fxa.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/mozilla/fxa/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"sjcl": "git://github.com/mozilla-fxa/sjcl.git#3d45d88ed9eaac98d88e7ff83e505db6896dd8c1",
|
||||
"xhr2": "0.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"audit-filter": "^0.5.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-fxa": "^2.0.2",
|
||||
"fxa-shared": "workspace:*",
|
||||
"grunt": "^1.1.0",
|
||||
"grunt-cli": "~1.2.0",
|
||||
"grunt-contrib-clean": "^2.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-copyright": "^0.3.0",
|
||||
"grunt-eslint": "^22.0.0",
|
||||
"grunt-open": "0.2.4",
|
||||
"grunt-webpack": "^3.1.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
"load-grunt-tasks": "^5.1.0",
|
||||
"mocha": "^7.1.2",
|
||||
"otplib": "^11.0.1",
|
||||
"prettier": "^2.0.5",
|
||||
"sinon": "^9.0.2",
|
||||
"webpack": "^4.41.3"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
'use strict';
|
||||
|
||||
grunt.config('clean', {
|
||||
build: ['build'],
|
||||
});
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
'use strict';
|
||||
|
||||
grunt.config('copyright', {
|
||||
app: {
|
||||
options: {
|
||||
pattern: /This Source Code Form is subject to the terms of the Mozilla Public/,
|
||||
},
|
||||
src: [
|
||||
'**/*.js',
|
||||
'!tests/addons/sinon.js',
|
||||
'!build/**',
|
||||
'!node_modules/**',
|
||||
'!docs/**',
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.config('eslint', {
|
||||
app: {
|
||||
eslintrc: '.eslintrc',
|
||||
src: ['Gruntfile.js', 'tasks/*.js', 'config/**/*.js', 'node/**/*.js'],
|
||||
},
|
||||
client: {
|
||||
options: { eslintrc: '.eslintrc' },
|
||||
src: ['client/*.js', 'client/lib/**/*'],
|
||||
},
|
||||
test: {
|
||||
options: {
|
||||
eslintrc: '.eslintrc',
|
||||
},
|
||||
src: ['tests/**/*.js', '!tests/addons/sinon.js'],
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
'use strict';
|
||||
|
||||
grunt.config('open', {
|
||||
dev: {
|
||||
path: 'docs/index.html',
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
'use strict';
|
||||
|
||||
grunt.config('watch', {
|
||||
dev: {
|
||||
options: {
|
||||
atBegin: true,
|
||||
},
|
||||
files: ['Gruntfile.js', 'client/**/*.js', 'tests/**/*.js'],
|
||||
tasks: ['build'],
|
||||
},
|
||||
debug: {
|
||||
options: {
|
||||
atBegin: true,
|
||||
},
|
||||
files: ['Gruntfile.js', 'client/**/*.js', 'tests/**/*.js'],
|
||||
tasks: ['webpack:app'],
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
module.exports = function (grunt) {
|
||||
const webpackConfig = require('../webpack.config');
|
||||
|
||||
grunt.config('webpack', {
|
||||
options: {
|
||||
stats: true,
|
||||
},
|
||||
app: webpackConfig,
|
||||
watch: Object.assign({ watch: true }, webpackConfig),
|
||||
});
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"extends": "../.eslintrc",
|
||||
"plugins": ["fxa"],
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const RequestMocks = require('../mocks/request');
|
||||
|
||||
function AccountHelper(client, mail, respond) {
|
||||
this.client = client;
|
||||
this.mail = mail;
|
||||
this.respond = respond;
|
||||
}
|
||||
AccountHelper.prototype.newVerifiedAccount = function (options) {
|
||||
var username = 'testHelp1';
|
||||
var domain = '@restmail.net';
|
||||
|
||||
if (options && options.domain) {
|
||||
domain = options.domain;
|
||||
}
|
||||
|
||||
if (options && options.username) {
|
||||
username = options.username;
|
||||
}
|
||||
|
||||
var user = username + new Date().getTime();
|
||||
var email = user + domain;
|
||||
var password = 'iliketurtles';
|
||||
var respond = this.respond;
|
||||
var mail = this.mail;
|
||||
var client = this.client;
|
||||
var uid;
|
||||
var result = {
|
||||
input: {
|
||||
user: user,
|
||||
email: email,
|
||||
password: password,
|
||||
},
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
uid = res.uid;
|
||||
result.signUp = res;
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
|
||||
.then(function (res) {
|
||||
result.verifyCode = res;
|
||||
|
||||
return respond(
|
||||
client.signIn(email, password, { keys: true }),
|
||||
RequestMocks.signInWithKeys
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
result.signIn = res;
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
AccountHelper.prototype.newUnverifiedAccount = function (options) {
|
||||
var username = 'testHelp2';
|
||||
var domain = '@restmail.net';
|
||||
|
||||
if (options && options.domain) {
|
||||
domain = options.domain;
|
||||
}
|
||||
|
||||
if (options && options.username) {
|
||||
username = options.username;
|
||||
}
|
||||
|
||||
var user = username + new Date().getTime();
|
||||
var email = user + domain;
|
||||
var password = 'iliketurtles';
|
||||
var respond = this.respond;
|
||||
var client = this.client;
|
||||
var result = {
|
||||
input: {
|
||||
user: user,
|
||||
email: email,
|
||||
password: password,
|
||||
},
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
result.signUp = res;
|
||||
|
||||
return respond(
|
||||
client.signIn(email, password, { keys: true }),
|
||||
RequestMocks.signInWithKeys
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
result.signIn = res;
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
AccountHelper.prototype.newUnconfirmedAccount = async function (options) {
|
||||
const username = 'testHelp3';
|
||||
const domain = '@restmail.net';
|
||||
|
||||
const user = username + new Date().getTime();
|
||||
const email = user + domain;
|
||||
const password = 'iliketurtles';
|
||||
const respond = this.respond;
|
||||
const client = this.client;
|
||||
|
||||
const signUp = await respond(
|
||||
client.signUp(email, password, options),
|
||||
RequestMocks.signUp
|
||||
);
|
||||
|
||||
return {
|
||||
input: {
|
||||
user: user,
|
||||
email: email,
|
||||
password: password,
|
||||
},
|
||||
signUp,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = AccountHelper;
|
|
@ -1,80 +0,0 @@
|
|||
/* eslint-disable id-blacklist */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
const XHR = require('xhr2');
|
||||
const Sinon = require('sinon');
|
||||
const FxAccountClient = require('../../client/FxAccountClient');
|
||||
const Restmail = require('../addons/restmail');
|
||||
const AccountHelper = require('../addons/accountHelper');
|
||||
const RequestMocks = require('../mocks/request');
|
||||
const ErrorMocks = require('../mocks/errors');
|
||||
function Environment() {
|
||||
var self = this;
|
||||
this.authServerUrl = process.env.AUTH_SERVER_URL || 'http://localhost:9000';
|
||||
this.useRemoteServer = !!process.env.AUTH_SERVER_URL;
|
||||
this.mailServerUrl = this.authServerUrl.match(/^http:\/\/127/)
|
||||
? 'http://localhost:9001'
|
||||
: 'http://restmail.net';
|
||||
|
||||
if (this.useRemoteServer) {
|
||||
this.xhr = XHR.XMLHttpRequest;
|
||||
// respond is a noop because we are using real XHR in this case
|
||||
this.respond = noop;
|
||||
} else {
|
||||
this.requests = [];
|
||||
this.responses = [];
|
||||
// switch to the fake XHR
|
||||
this.xhr = Sinon.useFakeXMLHttpRequest();
|
||||
this.xhr.onCreate = function (xhr) {
|
||||
if (self.requests.length < self.responses.length) {
|
||||
var mock = self.responses[self.requests.length];
|
||||
setTimeout(function () {
|
||||
xhr.respond(mock.status, mock.headers, mock.body);
|
||||
}, 0);
|
||||
}
|
||||
self.requests.push(xhr);
|
||||
};
|
||||
// respond calls a fake XHR response using SinonJS
|
||||
this.respond = function (returnValue, mock) {
|
||||
if (arguments.length < 2) {
|
||||
mock = returnValue;
|
||||
returnValue = null;
|
||||
}
|
||||
if (typeof mock === 'undefined') {
|
||||
console.log('Mock does not exist!');
|
||||
}
|
||||
// this has to be here to work in IE
|
||||
setTimeout(function () {
|
||||
if (self.responses.length < self.requests.length) {
|
||||
try {
|
||||
self.requests[self.responses.length].respond(
|
||||
mock.status,
|
||||
mock.headers,
|
||||
mock.body
|
||||
);
|
||||
} catch (e) {
|
||||
// mocking responses may cause `INVALID_STATE_ERR - 0` error here via the
|
||||
// FakeXHR interface in sinon.
|
||||
}
|
||||
}
|
||||
self.responses.push(mock);
|
||||
}, 0);
|
||||
return returnValue;
|
||||
};
|
||||
}
|
||||
// initialize a new FxA Client
|
||||
this.client = new FxAccountClient(this.authServerUrl, { xhr: this.xhr });
|
||||
// setup Restmail,
|
||||
this.mail = new Restmail(this.mailServerUrl, this.xhr);
|
||||
// account helper takes care of new verified and unverified accounts
|
||||
this.accountHelper = new AccountHelper(this.client, this.mail, this.respond);
|
||||
this.ErrorMocks = ErrorMocks;
|
||||
this.RequestMocks = RequestMocks;
|
||||
}
|
||||
|
||||
function noop(val) {
|
||||
return val;
|
||||
}
|
||||
|
||||
module.exports = Environment;
|
|
@ -1,39 +0,0 @@
|
|||
/* eslint-disable id-blacklist */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Request = require('../../client/lib/request');
|
||||
|
||||
function Restmail(server, xhr) {
|
||||
this.request = new Request(server, xhr);
|
||||
}
|
||||
|
||||
// utility function that waits for a restmail email to arrive
|
||||
Restmail.prototype.wait = function (user, number = 1, requestAttempts = 0) {
|
||||
const self = this;
|
||||
const path = '/mail/' + user;
|
||||
|
||||
if (requestAttempts > 0) {
|
||||
// only log if too many attempts, probably means the service is
|
||||
// not properly responding
|
||||
console.log('Waiting for email at:', path);
|
||||
}
|
||||
|
||||
return this.request.send(path, 'GET').then(function (result) {
|
||||
requestAttempts++;
|
||||
if (result.length === number) {
|
||||
return result;
|
||||
} else {
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(function () {
|
||||
self.wait(user, number, requestAttempts).then(resolve, reject);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Restmail;
|
|
@ -1,163 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
* {
|
||||
font-family: arial, sans-serif;
|
||||
}
|
||||
input[type='text'] {
|
||||
padding: 2px;
|
||||
font-size: 14px;
|
||||
width: 300px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>fxa-js-client tester</h1>
|
||||
|
||||
<label
|
||||
>Email:
|
||||
<input id="email" type="text" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Password:
|
||||
<input id="password" type="text" value="allyourbasearebelongtous" />
|
||||
</label>
|
||||
|
||||
<button id="create">Create Account</button>
|
||||
<button id="login">Login</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<label>
|
||||
uid:
|
||||
<input id="uid" type="text" value="" />
|
||||
</label>
|
||||
<label>
|
||||
verify Code:
|
||||
<input id="verifyCode" type="text" value="" />
|
||||
</label>
|
||||
<button id="verifyAccount">verifyAccount</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<label>
|
||||
sessionToken:
|
||||
<input id="sessionToken" type="text" value="" />
|
||||
</label>
|
||||
|
||||
<button id="recoveryEmailStatus">recoveryEmailStatus</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<button id="forgotPasswordSendCode">forgotPasswordSendCode</button>
|
||||
|
||||
<label>
|
||||
Verify Code:
|
||||
<input id="code" type="text" value="" />
|
||||
</label>
|
||||
<label>
|
||||
passwordForgotToken:
|
||||
<input id="passwordForgotToken" type="text" value="" />
|
||||
</label>
|
||||
|
||||
<button id="forgotPasswordVerifyCode">forgotPasswordVerifyCode</button>
|
||||
<button id="forgotPasswordResendCode">forgotPasswordResendCode</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<label>
|
||||
New Password:
|
||||
<input id="newPassword" type="text" value="" />
|
||||
</label>
|
||||
<label>
|
||||
accountResetToken:
|
||||
<input id="accountResetToken" type="text" value="" />
|
||||
</label>
|
||||
|
||||
<button id="accountReset">accountReset</button>
|
||||
</div>
|
||||
<hr />
|
||||
See Console....
|
||||
|
||||
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="../../build/fxa-client.js"></script>
|
||||
<script>
|
||||
var email = 'example' + new Date().getTime() + '@example.com';
|
||||
var password = 'allyourbasearebelongtous';
|
||||
|
||||
/**
|
||||
* Server URL, adjust this to test with a proxy.
|
||||
*/
|
||||
var server = 'http://localhost:9000';
|
||||
// Proxy:
|
||||
// var server = 'http://localhost:9133';
|
||||
var client = new FxAccountClient(server);
|
||||
|
||||
// Set Email value
|
||||
$('#email').val(email);
|
||||
|
||||
// Register Tester
|
||||
$('#create').click(function () {
|
||||
client.signUp($('#email').val(), $('#password').val());
|
||||
});
|
||||
|
||||
// Login Tester
|
||||
$('#login').click(function () {
|
||||
client.signIn($('#email').val(), $('#password').val());
|
||||
});
|
||||
|
||||
$('#recoveryEmailStatus').click(function () {
|
||||
var sessionToken = $('#sessionToken').val();
|
||||
|
||||
client.recoveryEmailStatus(sessionToken);
|
||||
});
|
||||
|
||||
$('#forgotPasswordSendCode').click(function () {
|
||||
var email = $('#email').val();
|
||||
|
||||
client.passwordForgotSendCode(email);
|
||||
});
|
||||
|
||||
$('#verifyAccount').click(function () {
|
||||
var uid = $('#uid').val();
|
||||
var verifyCode = $('#verifyCode').val();
|
||||
|
||||
client.verifyCode(uid, verifyCode);
|
||||
});
|
||||
|
||||
$('#forgotPasswordResendCode').click(function () {
|
||||
var email = $('#email').val();
|
||||
var passwordForgotToken = $('#passwordForgotToken').val();
|
||||
|
||||
client.passwordForgotResendCode(email, passwordForgotToken);
|
||||
});
|
||||
|
||||
$('#forgotPasswordVerifyCode').click(function () {
|
||||
var code = $('#code').val();
|
||||
var passwordForgotToken = $('#passwordForgotToken').val();
|
||||
|
||||
client.passwordForgotVerifyCode(code, passwordForgotToken);
|
||||
});
|
||||
|
||||
$('#accountReset').click(function () {
|
||||
var email = $('#email').val();
|
||||
var newPassword = $('#newPassword').val();
|
||||
var accountResetToken = $('#accountResetToken').val();
|
||||
|
||||
client.accountReset(email, newPassword, accountResetToken);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,38 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Auth Proxy tester for better IE debugging.
|
||||
*
|
||||
* Run using: node tests/examples/proxy.js
|
||||
*/
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var httpProxy = require('http-proxy');
|
||||
|
||||
var proxy = httpProxy.createProxyServer();
|
||||
var port = 9133;
|
||||
var targetAuthServer = 'http://localhost:9000';
|
||||
|
||||
http
|
||||
.createServer(function (req, res) {
|
||||
if (req.url === '/example.html') {
|
||||
res.end(fs.readFileSync('tests/examples/example.html'));
|
||||
} else if (req.url === '/build/fxa-client.js') {
|
||||
res.end(fs.readFileSync('build/fxa-client.js'));
|
||||
} else {
|
||||
proxy.web(req, res, {
|
||||
target: targetAuthServer,
|
||||
});
|
||||
}
|
||||
})
|
||||
.listen(port);
|
||||
|
||||
console.log(
|
||||
'Starting proxy on',
|
||||
port,
|
||||
'targeting',
|
||||
targetAuthServer,
|
||||
'fxa-auth-server'
|
||||
);
|
|
@ -1,608 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('account', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var mail;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var ErrorMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
mail = env.mail;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
});
|
||||
|
||||
it('#destroy', function () {
|
||||
var email;
|
||||
var password;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
email = account.input.email;
|
||||
password = account.input.password;
|
||||
|
||||
return respond(
|
||||
client.accountDestroy(email, password),
|
||||
RequestMocks.accountDestroy
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res, 'got response');
|
||||
|
||||
return respond(
|
||||
client.signIn(email, password),
|
||||
ErrorMocks.accountDoesNotExist
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.equal(error.errno, 102, 'Account is gone');
|
||||
assert.equal(error.code, 400, 'Correct status code');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#destroy with sessionToken', function () {
|
||||
var email;
|
||||
var password;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
email = account.input.email;
|
||||
password = account.input.password;
|
||||
|
||||
return respond(
|
||||
client.accountDestroy(
|
||||
email,
|
||||
password,
|
||||
{},
|
||||
account.signIn.sessionToken
|
||||
),
|
||||
RequestMocks.accountDestroy
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res, 'got response');
|
||||
|
||||
return respond(
|
||||
client.signIn(email, password),
|
||||
ErrorMocks.accountDoesNotExist
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.equal(error.errno, 102, 'Account is gone');
|
||||
assert.equal(error.code, 400, 'Correct status code');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#destroy with sessionToken, incorrect case', function () {
|
||||
var account;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client.accountDestroy(
|
||||
incorrectCaseEmail,
|
||||
account.input.password,
|
||||
{},
|
||||
account.signIn.sessionToken
|
||||
),
|
||||
RequestMocks.accountDestroy
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.signIn(
|
||||
account.input.email,
|
||||
account.input.password,
|
||||
{},
|
||||
account.signIn.sessionToken
|
||||
),
|
||||
ErrorMocks.accountDoesNotExist
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.ok(error);
|
||||
assert.equal(error.errno, 102);
|
||||
assert.equal(error.code, 400, 'Correct status code');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#destroy with sessionToken, incorrect case with skipCaseError', function () {
|
||||
var account;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client.accountDestroy(
|
||||
incorrectCaseEmail,
|
||||
account.input.password,
|
||||
{ skipCaseError: true },
|
||||
account.signIn.sessionToken
|
||||
),
|
||||
ErrorMocks.incorrectEmailCase
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 120);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#keys', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.accountKeys(
|
||||
account.signIn.keyFetchToken,
|
||||
account.signIn.unwrapBKey
|
||||
),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
assert.property(keys, 'kA');
|
||||
assert.property(keys, 'kB');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#destroy with incorrect case', function () {
|
||||
var account;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client.accountDestroy(incorrectCaseEmail, account.input.password),
|
||||
RequestMocks.accountDestroy
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.signIn(account.input.email, account.input.password),
|
||||
ErrorMocks.accountDoesNotExist
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.ok(error);
|
||||
assert.equal(error.errno, 102);
|
||||
assert.equal(error.code, 400, 'Correct status code');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#destroy with incorrect case with skipCaseError', function () {
|
||||
var account;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client.accountDestroy(incorrectCaseEmail, account.input.password, {
|
||||
skipCaseError: true,
|
||||
}),
|
||||
ErrorMocks.incorrectEmailCase
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 120);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Password Reset
|
||||
*/
|
||||
it('#reset password', function () {
|
||||
var user = 'test5' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
var passwordForgotToken;
|
||||
var accountResetToken;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(
|
||||
client.passwordForgotSendCode(email),
|
||||
RequestMocks.passwordForgotSendCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
passwordForgotToken = result.passwordForgotToken;
|
||||
assert.ok(passwordForgotToken, 'passwordForgotToken is returned');
|
||||
|
||||
return respond(
|
||||
mail.wait(user, 2),
|
||||
RequestMocks.resetMailpasswordForgotresetMail
|
||||
);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[1].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned: ' + code);
|
||||
|
||||
return respond(
|
||||
client.passwordForgotVerifyCode(code, passwordForgotToken),
|
||||
RequestMocks.passwordForgotVerifyCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
accountResetToken = result.accountResetToken;
|
||||
var newPassword = 'newturles';
|
||||
assert.ok(accountResetToken, 'accountResetToken is returned');
|
||||
|
||||
return respond(
|
||||
client.accountReset(email, newPassword, accountResetToken, {
|
||||
keys: true,
|
||||
metricsContext: {
|
||||
deviceId: '0123456789abcdef0123456789abcdef',
|
||||
entrypoint: 'mock-entrypoint',
|
||||
entrypointExperiment: 'mock-entrypoint-experiment',
|
||||
entrypointVariation: 'mock-entrypoint-variation',
|
||||
flowBeginTime: 1480615985437,
|
||||
flowId:
|
||||
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
utmCampaign: 'mock-campaign',
|
||||
utmContent: 'mock-content',
|
||||
utmMedium: 'mock-medium',
|
||||
utmSource: 'mock-source',
|
||||
utmTerm: 'mock-term',
|
||||
},
|
||||
sessionToken: true,
|
||||
}),
|
||||
RequestMocks.accountReset
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result.keyFetchToken);
|
||||
assert.ok(result.sessionToken);
|
||||
assert.ok(result.unwrapBKey);
|
||||
assert.ok(result.uid);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#passwordForgotSendCode with service, redirectTo, and resume', function () {
|
||||
var account;
|
||||
var opts = {
|
||||
service: 'sync',
|
||||
redirectTo: 'https://sync.localhost/after_reset',
|
||||
resume: 'resumejwt',
|
||||
};
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
|
||||
return respond(
|
||||
client.passwordForgotSendCode(account.input.email, opts),
|
||||
RequestMocks.passwordForgotSendCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result.passwordForgotToken);
|
||||
|
||||
return respond(
|
||||
mail.wait(account.input.user, 3),
|
||||
RequestMocks.resetMailWithServiceAndRedirect
|
||||
);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[2].html.match(/code=([A-Za-z0-9]+)/);
|
||||
assert.ok(code, 'code found');
|
||||
var service = emails[2].html.match(/service=([A-Za-z0-9]+)/);
|
||||
assert.ok(service, 'service found');
|
||||
var redirectTo = emails[2].html.match(/redirectTo=([A-Za-z0-9]+)/);
|
||||
assert.ok(redirectTo, 'redirectTo found');
|
||||
var resume = emails[2].html.match(/resume=([A-Za-z0-9]+)/);
|
||||
assert.ok(resume, 'resume found');
|
||||
|
||||
assert.ok(code[1], 'code is returned');
|
||||
assert.equal(service[1], 'sync', 'service is returned');
|
||||
assert.equal(redirectTo[1], 'https', 'redirectTo is returned');
|
||||
assert.equal(resume[1], 'resumejwt', 'resume is returned');
|
||||
});
|
||||
});
|
||||
|
||||
it('#passwordForgotStatus', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.passwordForgotSendCode(result.input.email),
|
||||
RequestMocks.passwordForgotSendCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.passwordForgotStatus(result.passwordForgotToken),
|
||||
RequestMocks.passwordForgotStatus
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.equal(result.tries, 3);
|
||||
assert.property(result, 'ttl');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#passwordForgotStatus error with a false token', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.passwordForgotSendCode(result.input.email),
|
||||
RequestMocks.passwordForgotSendCode
|
||||
);
|
||||
})
|
||||
.then(function () {
|
||||
var fakeToken =
|
||||
'e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f10000';
|
||||
|
||||
return respond(
|
||||
client.passwordForgotStatus(fakeToken),
|
||||
ErrorMocks.invalidAuthToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (err) {
|
||||
assert.equal(err.code, 401);
|
||||
assert.equal(err.errno, 110);
|
||||
});
|
||||
});
|
||||
|
||||
it('#accountStatus', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.accountStatus(result.signIn.uid),
|
||||
RequestMocks.accountStatus
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.exists, true);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#accountProfile', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.accountProfile(account.signIn.sessionToken),
|
||||
RequestMocks.accountProfile
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.isNotNull(res);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#accountStatus with wrong uid', function () {
|
||||
return respond(
|
||||
client.accountStatus('00047f01e387498e8ccc7fede1a74000'),
|
||||
RequestMocks.accountStatusFalse
|
||||
).then(function (res) {
|
||||
assert.equal(res.exists, false);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#accountStatus with no uid', function () {
|
||||
return client.accountStatus().then(
|
||||
function () {
|
||||
assert.fail('client.accountStatus should reject if uid is missing');
|
||||
},
|
||||
function (err) {
|
||||
assert.equal(err.message, 'Missing uid');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#accountStatusByEmail', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.accountStatusByEmail(result.input.email),
|
||||
RequestMocks.accountStatus
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.exists, true);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#accountStatusByEmail with wrong email', function () {
|
||||
return respond(
|
||||
client.accountStatusByEmail('invalid@email.com'),
|
||||
RequestMocks.accountStatusFalse
|
||||
).then(function (res) {
|
||||
assert.equal(res.exists, false);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#accountStatusByEmail with no email', function () {
|
||||
return client.accountStatusByEmail().then(
|
||||
function () {
|
||||
assert.fail(
|
||||
'client.accountStatusByEmail should reject if email is missing'
|
||||
);
|
||||
},
|
||||
function (err) {
|
||||
assert.equal(err.message, 'Missing email');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#login unblock accept', function () {
|
||||
var user = 'block.' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.signIn(email, password, { context: 'fx_desktop_v3' }),
|
||||
ErrorMocks.signInBlocked
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.equal(error.errno, 125);
|
||||
assert.equal(error.verificationMethod, 'email-captcha');
|
||||
assert.equal(error.verificationReason, 'login');
|
||||
|
||||
return respond(
|
||||
client.sendUnblockCode(email, { context: 'fx_desktop_v3' }),
|
||||
RequestMocks.sendUnblockCode
|
||||
);
|
||||
})
|
||||
.then(function () {
|
||||
return respond(mail.wait(user, 2), RequestMocks.unblockEmail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var unblockCode = emails[1].headers['x-unblock-code'];
|
||||
assert.ok(unblockCode, 'unblockCode is returned');
|
||||
|
||||
return respond(
|
||||
client.signIn(email, password, {
|
||||
unblockCode: unblockCode,
|
||||
context: 'fx_desktop_v3',
|
||||
}),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result.uid);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#login unblock reject', function () {
|
||||
var user = 'block.' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
var unblockCode;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
return respond(
|
||||
client.signIn(email, password, { context: 'fx_desktop_v3' }),
|
||||
ErrorMocks.signInBlocked
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.equal(error.errno, 125);
|
||||
assert.equal(error.verificationMethod, 'email-captcha');
|
||||
assert.equal(error.verificationReason, 'login');
|
||||
|
||||
return respond(
|
||||
client.sendUnblockCode(email, { context: 'fx_desktop_v3' }),
|
||||
RequestMocks.sendUnblockCode
|
||||
);
|
||||
})
|
||||
.then(function () {
|
||||
return respond(mail.wait(user, 2), RequestMocks.unblockEmail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
unblockCode = emails[1].headers['x-unblock-code'];
|
||||
assert.ok(unblockCode, 'unblockCode is returned');
|
||||
|
||||
return respond(
|
||||
client.rejectUnblockCode(uid, unblockCode),
|
||||
RequestMocks.rejectUnblockCode
|
||||
);
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.signIn(email, password, {
|
||||
unblockCode: unblockCode,
|
||||
context: 'fx_desktop_v3',
|
||||
}),
|
||||
ErrorMocks.signInInvalidUnblockCode
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.equal(error.errno, 127);
|
||||
});
|
||||
});
|
||||
|
||||
it('account()', async () => {
|
||||
const account = await accountHelper.newVerifiedAccount();
|
||||
const result = await respond(
|
||||
client.account(account.signIn.sessionToken),
|
||||
RequestMocks.account
|
||||
);
|
||||
assert.isArray(result.subscriptions);
|
||||
});
|
||||
});
|
|
@ -1,97 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
const sinon = require('sinon');
|
||||
|
||||
describe('attachedClients', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var ErrorMocks;
|
||||
var xhr;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
xhr = env.xhr;
|
||||
sinon.spy(xhr.prototype, 'open');
|
||||
sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhr.prototype.open.restore();
|
||||
xhr.prototype.send.restore();
|
||||
});
|
||||
|
||||
it('#attachedClients', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.attachedClients(account.signIn.sessionToken),
|
||||
RequestMocks.attachedClients
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.length, 2);
|
||||
var s = res[0];
|
||||
assert.property(s, 'clientId');
|
||||
assert.property(s, 'deviceId');
|
||||
assert.property(s, 'deviceType');
|
||||
assert.property(s, 'refreshTokenId');
|
||||
assert.ok(s.lastAccessTime);
|
||||
assert.ok(s.lastAccessTimeFormatted);
|
||||
assert.ok(s.sessionTokenId);
|
||||
s = res[1];
|
||||
assert.property(s, 'clientId');
|
||||
assert.property(s, 'deviceId');
|
||||
assert.property(s, 'deviceType');
|
||||
assert.property(s, 'refreshTokenId');
|
||||
assert.ok(s.lastAccessTimeFormatted);
|
||||
assert.ok(s.sessionTokenId);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#attachedClients error', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
var fakeToken =
|
||||
'e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f10000';
|
||||
|
||||
return respond(
|
||||
client.attachedClients(fakeToken),
|
||||
ErrorMocks.invalidAuthToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (err) {
|
||||
assert.equal(err.code, 401);
|
||||
assert.equal(err.errno, 110);
|
||||
});
|
||||
});
|
||||
|
||||
it('#destroy', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.attachedClientDestroy(account.signIn.sessionToken, {
|
||||
clientId: 'dcdb5ae7add825d2',
|
||||
}),
|
||||
RequestMocks.attachedClientDestroy
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res, 'got response');
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('certificateSign', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
it('#basic', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
var publicKey = {
|
||||
algorithm: 'RS',
|
||||
n:
|
||||
'4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123',
|
||||
e: '65537',
|
||||
};
|
||||
var duration = 86400000;
|
||||
|
||||
return respond(
|
||||
client.certificateSign(
|
||||
account.signIn.sessionToken,
|
||||
publicKey,
|
||||
duration
|
||||
),
|
||||
RequestMocks.certificateSign
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.property(res, 'cert', 'got cert');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#with service option', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
var publicKey = {
|
||||
algorithm: 'RS',
|
||||
n:
|
||||
'4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123',
|
||||
e: '65537',
|
||||
};
|
||||
var duration = 86400000;
|
||||
|
||||
return respond(
|
||||
client.certificateSign(
|
||||
account.signIn.sessionToken,
|
||||
publicKey,
|
||||
duration,
|
||||
{
|
||||
service: 'wibble',
|
||||
}
|
||||
),
|
||||
RequestMocks.certificateSign
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const sjcl = require('sjcl');
|
||||
const credentials = require('../../client/lib/credentials');
|
||||
describe('credentials', function () {
|
||||
it('#client stretch-KDF vectors', function () {
|
||||
var email = sjcl.codec.utf8String.fromBits(
|
||||
sjcl.codec.hex.toBits('616e6472c3a9406578616d706c652e6f7267')
|
||||
);
|
||||
var password = sjcl.codec.utf8String.fromBits(
|
||||
sjcl.codec.hex.toBits('70c3a4737377c3b67264')
|
||||
);
|
||||
|
||||
return credentials.setup(email, password).then(function (result) {
|
||||
var quickStretchedPW = sjcl.codec.hex.fromBits(result.quickStretchedPW);
|
||||
var authPW = sjcl.codec.hex.fromBits(result.authPW);
|
||||
var unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey);
|
||||
|
||||
assert.equal(
|
||||
quickStretchedPW,
|
||||
'e4e8889bd8bd61ad6de6b95c059d56e7b50dacdaf62bd84644af7e2add84345d',
|
||||
'== quickStretchedPW is equal'
|
||||
);
|
||||
assert.equal(
|
||||
authPW,
|
||||
'247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375',
|
||||
'== authPW is equal'
|
||||
);
|
||||
assert.equal(
|
||||
unwrapBKey,
|
||||
'de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28',
|
||||
'== unwrapBkey is equal'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#wrap', function () {
|
||||
var bit1 = sjcl.codec.hex.toBits(
|
||||
'c347de41c8a409c17b5b88e4985e1cd10585bb79b4a80d5e576eaf97cd1277fc'
|
||||
);
|
||||
var bit2 = sjcl.codec.hex.toBits(
|
||||
'3afd383d9bc1857318f24c5f293af62254f0476f0aaacfb929c61b534d0b5075'
|
||||
);
|
||||
var result = credentials.xor(bit1, bit2);
|
||||
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(result),
|
||||
'f9bae67c53658cb263a9c4bbb164eaf35175fc16be02c2e77ea8b4c480192789',
|
||||
'== wrap worked correctly'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,165 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const PushTestConstants = require('../mocks/pushConstants');
|
||||
var DEVICE_CALLBACK = PushTestConstants.DEVICE_CALLBACK;
|
||||
var DEVICE_PUBLIC_KEY = PushTestConstants.DEVICE_PUBLIC_KEY;
|
||||
var DEVICE_AUTH_KEY = PushTestConstants.DEVICE_AUTH_KEY;
|
||||
var DEVICE_NAME = PushTestConstants.DEVICE_NAME;
|
||||
var DEVICE_NAME_2 = PushTestConstants.DEVICE_NAME_2;
|
||||
var DEVICE_TYPE = PushTestConstants.DEVICE_TYPE;
|
||||
|
||||
describe('device', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
it('#register', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.deviceRegister(
|
||||
account.signIn.sessionToken,
|
||||
DEVICE_NAME,
|
||||
DEVICE_TYPE,
|
||||
{
|
||||
deviceCallback: DEVICE_CALLBACK,
|
||||
deviceAuthKey: DEVICE_AUTH_KEY,
|
||||
devicePublicKey: DEVICE_PUBLIC_KEY,
|
||||
}
|
||||
),
|
||||
RequestMocks.deviceRegister
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
assert.ok(res.id);
|
||||
assert.equal(res.name, DEVICE_NAME);
|
||||
assert.equal(res.pushCallback, DEVICE_CALLBACK);
|
||||
assert.equal(res.pushAuthKey, DEVICE_AUTH_KEY);
|
||||
assert.equal(res.pushPublicKey, DEVICE_PUBLIC_KEY);
|
||||
assert.equal(res.type, DEVICE_TYPE);
|
||||
},
|
||||
function (err) {
|
||||
console.log(err);
|
||||
assert.fail();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#update', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.deviceRegister(
|
||||
account.signIn.sessionToken,
|
||||
DEVICE_NAME,
|
||||
DEVICE_TYPE,
|
||||
{
|
||||
deviceCallback: DEVICE_CALLBACK,
|
||||
deviceAuthKey: DEVICE_AUTH_KEY,
|
||||
devicePublicKey: DEVICE_PUBLIC_KEY,
|
||||
}
|
||||
),
|
||||
RequestMocks.deviceRegister
|
||||
).then(function (device) {
|
||||
return respond(
|
||||
client.deviceUpdate(
|
||||
account.signIn.sessionToken,
|
||||
device.id,
|
||||
DEVICE_NAME_2,
|
||||
{
|
||||
deviceCallback: DEVICE_CALLBACK,
|
||||
deviceAuthKey: DEVICE_AUTH_KEY,
|
||||
devicePublicKey: DEVICE_PUBLIC_KEY,
|
||||
}
|
||||
),
|
||||
RequestMocks.deviceUpdate
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.id);
|
||||
assert.equal(res.name, DEVICE_NAME_2);
|
||||
assert.equal(res.pushCallback, DEVICE_CALLBACK);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#destroy', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.deviceRegister(
|
||||
account.signIn.sessionToken,
|
||||
DEVICE_NAME,
|
||||
DEVICE_TYPE,
|
||||
{
|
||||
deviceCallback: DEVICE_CALLBACK,
|
||||
deviceAuthKey: DEVICE_AUTH_KEY,
|
||||
devicePublicKey: DEVICE_PUBLIC_KEY,
|
||||
}
|
||||
),
|
||||
RequestMocks.deviceRegister
|
||||
).then(function (device) {
|
||||
return respond(
|
||||
client.deviceDestroy(account.signIn.sessionToken, device.id),
|
||||
RequestMocks.deviceDestroy
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(Object.keys(res), 0);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#list', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
return respond(
|
||||
client.deviceRegister(
|
||||
account.signIn.sessionToken,
|
||||
DEVICE_NAME,
|
||||
DEVICE_TYPE,
|
||||
{
|
||||
deviceCallback: DEVICE_CALLBACK,
|
||||
deviceAuthKey: DEVICE_AUTH_KEY,
|
||||
devicePublicKey: DEVICE_PUBLIC_KEY,
|
||||
}
|
||||
),
|
||||
RequestMocks.deviceRegister
|
||||
)
|
||||
.then(function (device) {
|
||||
return respond(
|
||||
client.deviceList(account.signIn.sessionToken),
|
||||
RequestMocks.deviceList
|
||||
);
|
||||
})
|
||||
|
||||
.then(function (devices) {
|
||||
assert.equal(devices.length, 1);
|
||||
|
||||
var device = devices[0];
|
||||
assert.ok(device.id);
|
||||
assert.equal(device.name, DEVICE_NAME);
|
||||
assert.equal(device.pushCallback, DEVICE_CALLBACK);
|
||||
assert.equal(device.type, DEVICE_TYPE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,316 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const sinon = require('sinon');
|
||||
|
||||
var user2;
|
||||
var user2Email;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('emails', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var mail;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var account;
|
||||
var env;
|
||||
var xhr;
|
||||
var xhrOpen;
|
||||
var xhrSend;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
mail = env.mail;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
|
||||
user2 = 'anotherEmail' + new Date().getTime();
|
||||
user2Email = user2 + '@restmail.net';
|
||||
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
function recoveryEmailCreate(options = {}) {
|
||||
return accountHelper.newVerifiedAccount().then(function (res) {
|
||||
account = res;
|
||||
return respond(
|
||||
client.recoveryEmailCreate(
|
||||
account.signIn.sessionToken,
|
||||
user2Email,
|
||||
options
|
||||
),
|
||||
RequestMocks.recoveryEmailCreate
|
||||
);
|
||||
}, handleError);
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
console.log(err);
|
||||
assert.fail();
|
||||
}
|
||||
|
||||
it('#recoveryEmailCreate', function () {
|
||||
return recoveryEmailCreate().then(function (res) {
|
||||
assert.ok(res);
|
||||
}, handleError);
|
||||
});
|
||||
|
||||
it('#recoveryEmails', function () {
|
||||
return recoveryEmailCreate()
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailsUnverified
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 2, 'returned two emails');
|
||||
assert.equal(res[1].verified, false, 'returned not verified');
|
||||
}, handleError);
|
||||
});
|
||||
|
||||
it('#verifyCode', function () {
|
||||
return recoveryEmailCreate()
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(mail.wait(user2, 1), RequestMocks.mailUnverifiedEmail);
|
||||
}, handleError)
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
return respond(
|
||||
client.verifyCode(account.signIn.uid, code, {
|
||||
type: 'secondary',
|
||||
}),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailsVerified
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 2, 'returned one email');
|
||||
assert.equal(res[1].verified, true, 'returned not verified');
|
||||
}, handleError);
|
||||
});
|
||||
|
||||
it('#recoveryEmailDestroy', function () {
|
||||
return recoveryEmailCreate()
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailsUnverified
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 2, 'returned two email');
|
||||
assert.equal(res[1].verified, false, 'returned not verified');
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailDestroy(account.signIn.sessionToken, user2Email),
|
||||
RequestMocks.recoveryEmailDestroy
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmails
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 1, 'returned one email');
|
||||
}, handleError);
|
||||
});
|
||||
|
||||
it('#recoveryEmailSetPrimaryEmail', function () {
|
||||
return recoveryEmailCreate()
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(mail.wait(user2, 1), RequestMocks.mailUnverifiedEmail);
|
||||
}, handleError)
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
return respond(
|
||||
client.verifyCode(account.signIn.uid, code, {
|
||||
type: 'secondary',
|
||||
}),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailSetPrimaryEmail(
|
||||
account.signIn.sessionToken,
|
||||
user2Email
|
||||
),
|
||||
RequestMocks.recoveryEmailSetPrimaryEmail
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailsSetPrimaryVerified
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 2, 'returned two emails');
|
||||
|
||||
assert.equal(
|
||||
true,
|
||||
res[0].email.indexOf('anotherEmail') > -1,
|
||||
'returned correct primary email'
|
||||
);
|
||||
assert.equal(res[0].verified, true, 'returned verified');
|
||||
assert.equal(res[0].isPrimary, true, 'returned isPrimary true');
|
||||
|
||||
assert.equal(res[1].verified, true, 'returned verified');
|
||||
assert.equal(res[1].isPrimary, false, 'returned isPrimary false');
|
||||
}, handleError);
|
||||
});
|
||||
|
||||
it('#recoveryEmailSecondaryVerifyCode', function () {
|
||||
var code;
|
||||
return recoveryEmailCreate({
|
||||
verificationMethod: 'email-otp',
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(mail.wait(user2, 1), RequestMocks.mailUnverifiedEmail);
|
||||
}, handleError)
|
||||
.then(function (emails) {
|
||||
code = emails[0].headers['x-verify-code'];
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailSecondaryVerifyCode(
|
||||
account.signIn.sessionToken,
|
||||
user2Email,
|
||||
code,
|
||||
{}
|
||||
),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
assert.equal(xhrOpen.args[6][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[6][1],
|
||||
'/recovery_email/secondary/verify_code',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[6][0]);
|
||||
assert.equal(Object.keys(sentData).length, 2);
|
||||
assert.equal(sentData.code, code, 'code is correct');
|
||||
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailsVerified
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 2, 'returned one email');
|
||||
assert.equal(res[1].verified, true, 'returned verified');
|
||||
}, handleError);
|
||||
});
|
||||
|
||||
it('#recoveryEmailSecondaryResendCode', function () {
|
||||
var code;
|
||||
return recoveryEmailCreate({
|
||||
verificationMethod: 'email-otp',
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
return respond(mail.wait(user2, 1), RequestMocks.mailUnverifiedEmail);
|
||||
}, handleError)
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.recoveryEmailSecondaryResendCode(
|
||||
account.signIn.sessionToken,
|
||||
user2Email
|
||||
),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
assert.equal(xhrOpen.args[6][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[6][1],
|
||||
'/recovery_email/secondary/resend_code',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[6][0]);
|
||||
assert.equal(Object.keys(sentData).length, 1);
|
||||
assert.equal(sentData.email, user2Email, 'email is correct');
|
||||
|
||||
return respond(
|
||||
mail.wait(user2, 2),
|
||||
RequestMocks.mailUnverifiedEmailResend
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (emails) {
|
||||
code = emails[1].headers['x-verify-code'];
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailSecondaryVerifyCode(
|
||||
account.signIn.sessionToken,
|
||||
user2Email,
|
||||
code,
|
||||
{}
|
||||
),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
client.recoveryEmails(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailsVerified
|
||||
);
|
||||
}, handleError)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(res.length, 2, 'returned one email');
|
||||
assert.equal(res[1].verified, true, 'returned verified');
|
||||
}, handleError);
|
||||
});
|
||||
});
|
|
@ -1,68 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('errors', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var ErrorMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
});
|
||||
|
||||
it('#accountUnverified', function () {
|
||||
return accountHelper
|
||||
.newUnverifiedAccount()
|
||||
.then(function (account) {
|
||||
var pk = { algorithm: 'RS', n: 'x', e: 'y' };
|
||||
var duration = 1000;
|
||||
|
||||
return respond(
|
||||
client.certificateSign(account.signIn.sessionToken, pk, duration),
|
||||
ErrorMocks.accountUnverified
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.equal(error.code, 400);
|
||||
assert.equal(error.errno, 104);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#invalidVerificationCode', function () {
|
||||
return accountHelper
|
||||
.newUnverifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.verifyCode(
|
||||
account.signUp.uid,
|
||||
'eb531a64deb628b2baeaceaa8762abf0'
|
||||
),
|
||||
ErrorMocks.invalidVerification
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.equal(error.code, 400);
|
||||
assert.equal(error.errno, 105);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const sjcl = require('sjcl');
|
||||
const hawkCredentials = require('../../client/lib/hawkCredentials');
|
||||
describe('hawkCredentials', function () {
|
||||
it('#client derive hawk credentials', function () {
|
||||
var context = 'sessionToken';
|
||||
var sessionToken =
|
||||
'a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf';
|
||||
|
||||
return hawkCredentials(sessionToken, context, 3 * 32).then(function (
|
||||
result
|
||||
) {
|
||||
var hmacKey = sjcl.codec.hex.fromBits(result.key);
|
||||
|
||||
assert.equal(
|
||||
hmacKey,
|
||||
'9d8f22998ee7f5798b887042466b72d53e56ab0c094388bf65831f702d2febc0',
|
||||
'== hmacKey is equal'
|
||||
);
|
||||
assert.equal(
|
||||
result.id,
|
||||
'c0a29dcf46174973da1378696e4c82ae10f723cf4f4d9f75e39f4ae3851595ab',
|
||||
'== id is equal'
|
||||
);
|
||||
},
|
||||
assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,103 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('headerLanguage', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var mail;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
RequestMocks = env.RequestMocks;
|
||||
client = env.client;
|
||||
mail = env.mail;
|
||||
});
|
||||
|
||||
it('#signUp', function () {
|
||||
var user = 'test' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
lang: 'zh-cn;',
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password, opts), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailSignUpLang);
|
||||
})
|
||||
.then(function (emails) {
|
||||
assert.property(emails[0], 'headers');
|
||||
assert.equal(emails[0].headers['content-language'], 'zh-CN');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#passwordForgotSendCode', function () {
|
||||
var account;
|
||||
var passwordForgotToken;
|
||||
var opts = {
|
||||
lang: 'zh-CN',
|
||||
service: 'sync',
|
||||
};
|
||||
|
||||
return accountHelper
|
||||
.newUnverifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
|
||||
return respond(
|
||||
client.passwordForgotSendCode(account.input.email, opts),
|
||||
RequestMocks.passwordForgotSendCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
passwordForgotToken = result.passwordForgotToken;
|
||||
assert.ok(passwordForgotToken, 'passwordForgotToken is returned');
|
||||
|
||||
return respond(
|
||||
mail.wait(account.input.user, 3),
|
||||
RequestMocks.resetMailLang
|
||||
);
|
||||
})
|
||||
.then(function (emails) {
|
||||
assert.property(emails[2], 'headers');
|
||||
assert.equal(emails[2].headers['content-language'], 'zh-CN');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#recoveryEmailResendCode', function () {
|
||||
var user;
|
||||
var opts = {
|
||||
lang: 'zh-CN',
|
||||
};
|
||||
|
||||
return accountHelper
|
||||
.newUnverifiedAccount()
|
||||
.then(function (account) {
|
||||
user = account.input.user;
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailResendCode(account.signIn.sessionToken, opts),
|
||||
RequestMocks.recoveryEmailResendCode
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(mail.wait(user, 3), RequestMocks.resetMailLang);
|
||||
})
|
||||
.then(function (emails) {
|
||||
assert.property(emails[2], 'headers');
|
||||
assert.equal(emails[2].headers['content-language'], 'zh-CN');
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const sjcl = require('sjcl');
|
||||
const hkdf = require('../../client/lib/hkdf');
|
||||
// test vectors from RFC5869
|
||||
describe('hkdf', function () {
|
||||
it('#vector 1', function () {
|
||||
var ikm = sjcl.codec.hex.toBits(
|
||||
'0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'
|
||||
);
|
||||
var salt = sjcl.codec.hex.toBits('000102030405060708090a0b0c');
|
||||
var info = sjcl.codec.hex.toBits('f0f1f2f3f4f5f6f7f8f9');
|
||||
|
||||
return hkdf(ikm, info, salt, 42).then(function (result) {
|
||||
assert.equal(sjcl.codec.hex.fromBits(result).length, 84);
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(result),
|
||||
'3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#vector 2', function () {
|
||||
var ikm = sjcl.codec.hex.toBits(
|
||||
'0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'
|
||||
);
|
||||
var salt = sjcl.codec.hex.toBits('');
|
||||
var info = sjcl.codec.hex.toBits('');
|
||||
|
||||
return hkdf(ikm, info, salt, 42).then(function (result) {
|
||||
assert.equal(sjcl.codec.hex.fromBits(result).length, 84);
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(result),
|
||||
'8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#vector 3', function () {
|
||||
var ikm = sjcl.codec.hex.toBits(
|
||||
'4a9cbe5ae7190a7bb7cc54d5d84f5e4ba743904f8a764933b72f10260067375a'
|
||||
);
|
||||
var salt = sjcl.codec.hex.toBits('');
|
||||
var info = sjcl.codec.utf8String.toBits(
|
||||
'identity.mozilla.com/picl/v1/keyFetchToken'
|
||||
);
|
||||
|
||||
return hkdf(ikm, info, salt, 3 * 32).then(function (result) {
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(result),
|
||||
'f4df04ffb79db35e94e4881719a6f145f9206e8efea17fc9f02a5ce09cbfac1e829a935f34111d75e0d16b7aa178e2766759eedb6f623c0babd2abcfea82bc12af75f6aa543a8ba7e0a029f87c785c4af0ad03889f7437f735b5256a88fc73fd'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#vector 4', function () {
|
||||
var ikm = sjcl.codec.hex.toBits(
|
||||
'ba0a107dab60f3b065ff7a642d14fe824fbd71bc5c99087e9e172a1abd1634f1'
|
||||
);
|
||||
var salt = sjcl.codec.hex.toBits('');
|
||||
var info = sjcl.codec.utf8String.toBits(
|
||||
'identity.mozilla.com/picl/v1/account/keys'
|
||||
);
|
||||
|
||||
return hkdf(ikm, info, salt, 3 * 32).then(function (result) {
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(result),
|
||||
'17ab463653a94c9a6419b48781930edefe500395e3b4e7879a2be1599975702285de16c3218a126404668bf9b7acfb6ce2b7e03c8889047ba48b8b854c6d8beb3ae100e145ca6d69cb519a872a83af788771954455716143bc08225ea8644d85'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const FxAccountClient = require('../../client/FxAccountClient');
|
||||
describe('init', function () {
|
||||
it('#should error if no options set', function () {
|
||||
try {
|
||||
void new FxAccountClient();
|
||||
} catch (e) {
|
||||
assert.isDefined(e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it('#should catch undefined parameters for the url', function () {
|
||||
try {
|
||||
void new FxAccountClient(undefined, {});
|
||||
} catch (e) {
|
||||
assert.isDefined(e.message);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const metricsContext = require('../../client/lib/metricsContext');
|
||||
|
||||
describe('metricsContext', function () {
|
||||
it('interface is correct', function () {
|
||||
assert.isObject(metricsContext);
|
||||
assert.lengthOf(Object.keys(metricsContext), 1);
|
||||
assert.isFunction(metricsContext.marshall);
|
||||
});
|
||||
|
||||
it('marshall returns correct data', function () {
|
||||
var input = {
|
||||
context: 'fx_desktop_v3',
|
||||
deviceId: '0123456789abcdef0123456789abcdef',
|
||||
entrypoint: 'menupanel',
|
||||
entrypointExperiment: 'wibble',
|
||||
entrypointVariation: 'blee',
|
||||
flowBeginTime: 1479815991573,
|
||||
flowId:
|
||||
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
migration: 'sync11',
|
||||
planId: 'planId',
|
||||
productId: 'productId',
|
||||
service: 'sync',
|
||||
utmCampaign: 'foo',
|
||||
utmContent: 'bar',
|
||||
utmMedium: 'baz',
|
||||
utmSource: 'qux',
|
||||
utmTerm: 'wibble',
|
||||
};
|
||||
|
||||
assert.deepEqual(metricsContext.marshall(input), {
|
||||
deviceId: input.deviceId,
|
||||
entrypoint: 'menupanel',
|
||||
entrypointExperiment: 'wibble',
|
||||
entrypointVariation: 'blee',
|
||||
flowBeginTime: input.flowBeginTime,
|
||||
flowId: input.flowId,
|
||||
planId: 'planId',
|
||||
productId: 'productId',
|
||||
utmCampaign: 'foo',
|
||||
utmContent: 'bar',
|
||||
utmMedium: 'baz',
|
||||
utmSource: 'qux',
|
||||
utmTerm: 'wibble',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,52 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('misc', function () {
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
it('#getRandomBytes', function () {
|
||||
return respond(client.getRandomBytes(), RequestMocks.getRandomBytes).then(
|
||||
function (res) {
|
||||
assert.property(res, 'data');
|
||||
},
|
||||
assert.fail
|
||||
);
|
||||
});
|
||||
|
||||
it('_required', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
client._required(true, 'true_boolean');
|
||||
client._required(false, 'false_boolean');
|
||||
client._required('string', 'string');
|
||||
client._required({ hasValue: true }, 'object_with_value');
|
||||
client._required(1, 'number');
|
||||
client._required(0, 'zero');
|
||||
});
|
||||
|
||||
assert.throws(function () {
|
||||
client._required('', 'empty_string');
|
||||
});
|
||||
|
||||
assert.throws(function () {
|
||||
client._required({}, 'empty_object');
|
||||
});
|
||||
|
||||
assert.throws(function () {
|
||||
client._required(null, 'null');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,242 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const CLIENT_ID = 'dcdb5ae7add825d2';
|
||||
const ID_TOKEN =
|
||||
'001122334455.66778899aabbccddeeff00112233445566778899.aabbccddeeff';
|
||||
const PUBLIC_CLIENT_ID = 'a2270f727f45f648';
|
||||
|
||||
describe('oauth', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
it('#createOAuthCode - missing sessionToken', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthCode(null, CLIENT_ID, 'state'),
|
||||
RequestMocks.createOAuthCode
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing sessionToken');
|
||||
});
|
||||
});
|
||||
|
||||
it('#createOAuthCode - missing clientId', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthCode(account.signIn.sessionToken, null, 'state'),
|
||||
RequestMocks.createOAuthCode
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing clientId');
|
||||
});
|
||||
});
|
||||
|
||||
it('#createOAuthCode - missing state', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthCode(account.signIn.sessionToken, CLIENT_ID, null),
|
||||
RequestMocks.createOAuthCode
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing state');
|
||||
});
|
||||
});
|
||||
|
||||
it('#createOAuthCode', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthCode(
|
||||
account.signIn.sessionToken,
|
||||
CLIENT_ID,
|
||||
'state'
|
||||
),
|
||||
RequestMocks.createOAuthCode
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#createOAuthToken - missing sessionToken', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthToken(null, CLIENT_ID),
|
||||
RequestMocks.createOAuthToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing sessionToken');
|
||||
});
|
||||
});
|
||||
|
||||
it('#createOAuthToken - missing clientId', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthToken(account.signIn.sessionToken, null),
|
||||
RequestMocks.createOAuthToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing clientId');
|
||||
});
|
||||
});
|
||||
|
||||
it('#createOAuthToken', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createOAuthToken(
|
||||
account.signIn.sessionToken,
|
||||
PUBLIC_CLIENT_ID
|
||||
),
|
||||
RequestMocks.createOAuthToken
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#getOAuthScopedKeyData - missing sessionToken', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getOAuthScopedKeyData(null, CLIENT_ID, 'profile'),
|
||||
RequestMocks.getOAuthScopedKeyData
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing sessionToken');
|
||||
});
|
||||
});
|
||||
|
||||
it('#getOAuthScopedKeyData - missing clientId', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getOAuthScopedKeyData(
|
||||
account.signIn.sessionToken,
|
||||
null,
|
||||
'profile'
|
||||
),
|
||||
RequestMocks.getOAuthScopedKeyData
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing clientId');
|
||||
});
|
||||
});
|
||||
|
||||
it('#getOAuthScopedKeyData - missing scope', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getOAuthScopedKeyData(
|
||||
account.signIn.sessionToken,
|
||||
CLIENT_ID,
|
||||
null
|
||||
),
|
||||
RequestMocks.getOAuthScopedKeyData
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing scope');
|
||||
});
|
||||
});
|
||||
|
||||
it('#getOAuthScopedKeyData', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getOAuthScopedKeyData(
|
||||
account.signIn.sessionToken,
|
||||
CLIENT_ID,
|
||||
'profile'
|
||||
),
|
||||
RequestMocks.getOAuthScopedKeyData
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#verifyIdToken - missing idToken', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.verifyIdToken(null, CLIENT_ID),
|
||||
RequestMocks.verifyIdToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing idToken');
|
||||
});
|
||||
});
|
||||
|
||||
it('#verifyIdToken - missing clientId', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.verifyIdToken(ID_TOKEN, null),
|
||||
RequestMocks.verifyIdToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing clientId');
|
||||
});
|
||||
});
|
||||
|
||||
it('#verifyIdToken', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.verifyIdToken(ID_TOKEN, CLIENT_ID),
|
||||
RequestMocks.verifyIdToken
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,396 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
const sjcl = require('sjcl');
|
||||
const credentials = require('../../client/lib/credentials');
|
||||
|
||||
describe('passwordChange', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var mail;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var ErrorMocks;
|
||||
var requests;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
mail = env.mail;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
requests = env.requests;
|
||||
});
|
||||
|
||||
it('#basic', function () {
|
||||
var user = 'test7' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var newPassword = 'ilikefoxes';
|
||||
var kB;
|
||||
var newUnwrapBKey;
|
||||
var oldCreds;
|
||||
var uid;
|
||||
var account;
|
||||
|
||||
// newUnwrapBKey from email+newpassword. The submitted newWrapKB
|
||||
// should equal (kB XOR newUnwrapBKey). This way we don't need to
|
||||
// know what the server will return for wrapKB: handy, since
|
||||
// sometimes we're using a mock (with a fixed response), but
|
||||
// sometimes we're using a real server (which randomly creates
|
||||
// wrapKB)
|
||||
|
||||
return credentials
|
||||
.setup(email, newPassword)
|
||||
.then(function (newCreds) {
|
||||
newUnwrapBKey = sjcl.codec.hex.fromBits(newCreds.unwrapBKey);
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp);
|
||||
})
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.signIn(email, password, { keys: true }),
|
||||
RequestMocks.signInWithKeys
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
account = result;
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.accountKeys(account.keyFetchToken, account.unwrapBKey),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
kB = keys.kB;
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client._passwordChangeStart(email, password),
|
||||
RequestMocks.passwordChangeStart
|
||||
);
|
||||
})
|
||||
.then(function (credentials) {
|
||||
oldCreds = credentials;
|
||||
assert.equal(credentials.emailToHashWith, email);
|
||||
|
||||
return respond(
|
||||
client._passwordChangeKeys(oldCreds),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
return respond(
|
||||
client._passwordChangeFinish(email, newPassword, oldCreds, keys, {
|
||||
keys: false,
|
||||
}),
|
||||
RequestMocks.passwordChangeFinish
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
// currently only available for mocked requests (issue #103)
|
||||
if (requests) {
|
||||
var req = requests[requests.length - 1];
|
||||
var args = JSON.parse(req.requestBody);
|
||||
var expectedNewWrapKB = sjcl.codec.hex.fromBits(
|
||||
credentials.xor(
|
||||
sjcl.codec.hex.toBits(kB),
|
||||
sjcl.codec.hex.toBits(newUnwrapBKey)
|
||||
)
|
||||
);
|
||||
assert.equal(args.wrapKb, expectedNewWrapKB);
|
||||
}
|
||||
assert.notProperty(result, 'keyFetchToken');
|
||||
|
||||
return respond(client.signIn(email, newPassword), RequestMocks.signIn);
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
assert.property(res, 'sessionToken');
|
||||
},
|
||||
function (err) {
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#keys', function () {
|
||||
var user = 'test7' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var newPassword = 'ilikefoxes';
|
||||
var kB;
|
||||
var newUnwrapBKey;
|
||||
var oldCreds;
|
||||
var sessionToken;
|
||||
var uid;
|
||||
var account;
|
||||
|
||||
// newUnwrapBKey from email+newpassword. The submitted newWrapKB
|
||||
// should equal (kB XOR newUnwrapBKey). This way we don't need to
|
||||
// know what the server will return for wrapKB: handy, since
|
||||
// sometimes we're using a mock (with a fixed response), but
|
||||
// sometimes we're using a real server (which randomly creates
|
||||
// wrapKB)
|
||||
|
||||
return credentials
|
||||
.setup(email, newPassword)
|
||||
.then(function (newCreds) {
|
||||
newUnwrapBKey = sjcl.codec.hex.fromBits(newCreds.unwrapBKey);
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp);
|
||||
})
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.signIn(email, password, { keys: true }),
|
||||
RequestMocks.signInWithKeys
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
sessionToken = result.sessionToken;
|
||||
account = result;
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.accountKeys(account.keyFetchToken, account.unwrapBKey),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
kB = keys.kB;
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client._passwordChangeStart(email, password),
|
||||
RequestMocks.passwordChangeStart
|
||||
);
|
||||
})
|
||||
.then(function (credentials) {
|
||||
oldCreds = credentials;
|
||||
assert.equal(credentials.emailToHashWith, email);
|
||||
|
||||
return respond(
|
||||
client._passwordChangeKeys(oldCreds),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
return respond(
|
||||
client._passwordChangeFinish(email, newPassword, oldCreds, keys, {
|
||||
keys: true,
|
||||
sessionToken: sessionToken,
|
||||
}),
|
||||
RequestMocks.passwordChangeFinishKeys
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
// currently only available for mocked requests (issue #103)
|
||||
if (requests) {
|
||||
var req = requests[requests.length - 1];
|
||||
var args = JSON.parse(req.requestBody);
|
||||
var expectedNewWrapKB = sjcl.codec.hex.fromBits(
|
||||
credentials.xor(
|
||||
sjcl.codec.hex.toBits(kB),
|
||||
sjcl.codec.hex.toBits(newUnwrapBKey)
|
||||
)
|
||||
);
|
||||
assert.equal(args.wrapKb, expectedNewWrapKB);
|
||||
}
|
||||
assert.property(result, 'sessionToken');
|
||||
assert.property(result, 'keyFetchToken');
|
||||
assert.property(result, 'unwrapBKey');
|
||||
assert.isTrue(result.verified);
|
||||
|
||||
return respond(client.signIn(email, newPassword), RequestMocks.signIn);
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
assert.property(res, 'sessionToken');
|
||||
},
|
||||
function (err) {
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#with incorrect case', function () {
|
||||
var newPassword = 'ilikefoxes';
|
||||
var account;
|
||||
var oldCreds;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client._passwordChangeStart(
|
||||
incorrectCaseEmail,
|
||||
account.input.password
|
||||
),
|
||||
RequestMocks.passwordChangeStart
|
||||
);
|
||||
})
|
||||
.then(function (credentials) {
|
||||
oldCreds = credentials;
|
||||
|
||||
return respond(
|
||||
client._passwordChangeKeys(oldCreds),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
return respond(
|
||||
client._passwordChangeFinish(
|
||||
account.input.email,
|
||||
newPassword,
|
||||
oldCreds,
|
||||
keys
|
||||
),
|
||||
RequestMocks.passwordChangeFinish
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result, '{}');
|
||||
|
||||
return respond(
|
||||
client.signIn(account.input.email, newPassword),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
assert.property(res, 'sessionToken');
|
||||
},
|
||||
function (err) {
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#with incorrect case with skipCaseError', function () {
|
||||
var account;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (acc) {
|
||||
account = acc;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client._passwordChangeStart(
|
||||
incorrectCaseEmail,
|
||||
account.input.password,
|
||||
{ skipCaseError: true }
|
||||
),
|
||||
ErrorMocks.incorrectEmailCase
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 120);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Changing the Password failure
|
||||
*/
|
||||
it('#changeFailure', function () {
|
||||
var user = 'test8' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var newPassword = 'ilikefoxes';
|
||||
var wrongPassword = '12345678';
|
||||
var uid;
|
||||
var oldCreds;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
.then(function () {
|
||||
return respond(
|
||||
client._passwordChangeStart(email, password),
|
||||
RequestMocks.passwordChangeStart
|
||||
);
|
||||
})
|
||||
.then(function (credentials) {
|
||||
oldCreds = credentials;
|
||||
assert.equal(credentials.emailToHashWith, email);
|
||||
return respond(
|
||||
client._passwordChangeKeys(oldCreds),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (keys) {
|
||||
return respond(
|
||||
client._passwordChangeFinish(email, newPassword, oldCreds, keys),
|
||||
RequestMocks.passwordChangeFinish
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result);
|
||||
|
||||
return respond(
|
||||
client.signIn(email, wrongPassword),
|
||||
ErrorMocks.accountIncorrectPassword
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (error) {
|
||||
assert.ok(error);
|
||||
assert.equal(
|
||||
error.message,
|
||||
'Incorrect password',
|
||||
'== Password is incorrect'
|
||||
);
|
||||
assert.equal(error.code, 400, '== Correct status code');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,129 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
const otplib = require('otplib');
|
||||
describe('recovery codes', function () {
|
||||
var account;
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var env;
|
||||
var xhr;
|
||||
var xhrOpen;
|
||||
var xhrSend;
|
||||
var recoveryCodes;
|
||||
var metricsContext;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
metricsContext = {
|
||||
flowBeginTime: Date.now(),
|
||||
flowId:
|
||||
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
};
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (newAccount) {
|
||||
account = newAccount;
|
||||
return respond(
|
||||
client.createTotpToken(account.signIn.sessionToken),
|
||||
RequestMocks.createTotpToken
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.qrCodeUrl, 'should return QR code data encoded url');
|
||||
assert.ok(res.secret, 'should return secret that is encoded in url');
|
||||
|
||||
var authenticator = new otplib.authenticator.Authenticator();
|
||||
authenticator.options = otplib.authenticator.options;
|
||||
|
||||
var code = authenticator.generate(res.secret);
|
||||
return respond(
|
||||
client.verifyTotpCode(account.signIn.sessionToken, code),
|
||||
RequestMocks.verifyTotpCodeTrueEnableToken
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(
|
||||
res.recoveryCodes.length,
|
||||
3,
|
||||
'should return recovery codes'
|
||||
);
|
||||
recoveryCodes = res.recoveryCodes;
|
||||
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
it('#consumeRecoveryCode - fails for invalid code', function () {
|
||||
return respond(
|
||||
client.consumeRecoveryCode(account.signIn.sessionToken, '00000000'),
|
||||
RequestMocks.consumeRecoveryCodeInvalidCode
|
||||
).then(assert.fail, function (err) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/session/verify/recoveryCode',
|
||||
'path is correct'
|
||||
);
|
||||
assert.equal(err.errno, 156, 'invalid recovery code errno');
|
||||
});
|
||||
});
|
||||
|
||||
it('#consumeRecoveryCode - consumes valid code', function () {
|
||||
var code = recoveryCodes[0];
|
||||
return respond(
|
||||
client.consumeRecoveryCode(account.signIn.sessionToken, code, {
|
||||
metricsContext: metricsContext,
|
||||
}),
|
||||
RequestMocks.consumeRecoveryCodeSuccess
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/session/verify/recoveryCode',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[0][0]);
|
||||
assert.lengthOf(Object.keys(sentData), 1);
|
||||
assert.equal(sentData.code, code, 'code is correct');
|
||||
|
||||
assert.equal(res.remaining, 2, 'correct remaining recovery codes');
|
||||
});
|
||||
});
|
||||
|
||||
it('#replaceRecoveryCodes - replaces current recovery codes', function () {
|
||||
return respond(
|
||||
client.replaceRecoveryCodes(account.signIn.sessionToken),
|
||||
RequestMocks.replaceRecoveryCodesSuccessNew
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'GET', 'method is correct');
|
||||
assert.include(xhrOpen.args[0][1], '/recoveryCodes', 'path is correct');
|
||||
|
||||
assert.equal(res.recoveryCodes.length, 3, 'should return recovery codes');
|
||||
assert.notDeepEqual(
|
||||
res.recoveryCodes,
|
||||
recoveryCodes,
|
||||
'should not be the same codes'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,97 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('recoveryEmail', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var mail;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
mail = env.mail;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
it('#recoveryEmail - recoveryEmailResendCode', function () {
|
||||
var user;
|
||||
|
||||
return accountHelper
|
||||
.newUnverifiedAccount()
|
||||
.then(function (account) {
|
||||
user = account.input.user;
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailResendCode(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryEmailResendCode
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
mail.wait(user, 3),
|
||||
RequestMocks.resetMailrecoveryEmailResendCode
|
||||
);
|
||||
})
|
||||
.then(function (emails) {
|
||||
// second email, the code is resent.
|
||||
var code = emails[2].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#recoveryEmailResendCode with service, redirectTo, type and resume', function () {
|
||||
var user;
|
||||
var opts = {
|
||||
service: 'sync',
|
||||
redirectTo: 'https://sync.localhost/after_reset',
|
||||
resume: 'resumejwt',
|
||||
type: 'upgradeSession',
|
||||
};
|
||||
|
||||
return accountHelper
|
||||
.newUnverifiedAccount()
|
||||
.then(function (account) {
|
||||
user = account.input.user;
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailResendCode(account.signIn.sessionToken, opts),
|
||||
RequestMocks.recoveryEmailResendCode
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
|
||||
return respond(
|
||||
mail.wait(user, 3),
|
||||
RequestMocks.resetMailWithServiceAndRedirectNoSignup
|
||||
);
|
||||
})
|
||||
.then(function (emails) {
|
||||
// second email, the code is resent.
|
||||
var code = emails[2].html.match(/code=([A-Za-z0-9]+)/);
|
||||
assert.ok(code, 'code found');
|
||||
var service = emails[2].html.match(/service=([A-Za-z0-9]+)/);
|
||||
assert.ok(service, 'service found');
|
||||
var redirectTo = emails[2].html.match(/redirectTo=([A-Za-z0-9]+)/);
|
||||
assert.ok(redirectTo, 'redirectTo found');
|
||||
var resume = emails[2].html.match(/resume=([A-Za-z0-9]+)/);
|
||||
assert.ok(resume, 'resume found');
|
||||
|
||||
assert.ok(code[1], 'code is returned');
|
||||
assert.equal(service[1], 'sync', 'service is returned');
|
||||
assert.equal(redirectTo[1], 'https', 'redirectTo is returned');
|
||||
assert.equal(resume[1], 'resumejwt', 'resume is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,290 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
describe('recovery key', function () {
|
||||
var account;
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var email;
|
||||
var RequestMocks;
|
||||
var env;
|
||||
var xhr;
|
||||
var xhrOpen;
|
||||
var xhrSend;
|
||||
var keys;
|
||||
var passwordForgotToken;
|
||||
var accountResetToken;
|
||||
var mail;
|
||||
var newPassword = '~(_8^(I)';
|
||||
var recoveryKeyId = 'edc243a821582ee9e979583be9989ee7';
|
||||
var bundle =
|
||||
'eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIiwia2lkIjoiODE4NDIwZjBkYTU4ZDIwZjZhZTR' +
|
||||
'kMmM5YmVhYjkyNTEifQ..D29EXHp8ubLvftaZ.xHJd2Nl2Uco2RyywYPLkUU7fHpgO2FztY12Zjpq1ffiyLRIUcQVfmiNC6aMiHB' +
|
||||
'l7Hp-lXEbb5mR1uXHrTH9iRXEBVaAfyf9KEAWOukWGVSH8EaOkr7cfu2Yr0K93Ec8glsssjiKp8NGB8VKTUJ-lmBv2cIrG68V4eTUVDo' +
|
||||
'DhMbXhrF-Mv4JNeh338pPeatTnyg.Ow2bhEYWxzxfSPMxVwKmSA';
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
mail = env.mail;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (newAccount) {
|
||||
account = newAccount;
|
||||
email = account.input.email;
|
||||
return respond(
|
||||
client.accountKeys(
|
||||
account.signIn.keyFetchToken,
|
||||
account.signIn.unwrapBKey
|
||||
),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
keys = result;
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
it('#can create and get a recovery key that can be used to reset an account', function () {
|
||||
return respond(
|
||||
client.createRecoveryKey(
|
||||
account.signIn.sessionToken,
|
||||
recoveryKeyId,
|
||||
bundle,
|
||||
true
|
||||
),
|
||||
RequestMocks.createRecoveryKey
|
||||
)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
return respond(
|
||||
client.passwordForgotSendCode(email),
|
||||
RequestMocks.passwordForgotSendCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
passwordForgotToken = result.passwordForgotToken;
|
||||
assert.ok(passwordForgotToken, 'passwordForgotToken is returned');
|
||||
|
||||
return respond(
|
||||
mail.wait(account.input.user, 4),
|
||||
RequestMocks.resetMailpasswordForgotRecoveryKey
|
||||
);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[3].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned: ' + code);
|
||||
|
||||
return respond(
|
||||
client.passwordForgotVerifyCode(code, passwordForgotToken, {
|
||||
accountResetWithRecoveryKey: true,
|
||||
}),
|
||||
RequestMocks.passwordForgotVerifyCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
accountResetToken = result.accountResetToken;
|
||||
assert.ok(accountResetToken, 'accountResetToken is returned');
|
||||
|
||||
assert.equal(xhrOpen.args[3][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[3][1],
|
||||
'/password/forgot/verify_code',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[3][0]);
|
||||
assert.equal(Object.keys(sentData).length, 2);
|
||||
assert.equal(sentData.accountResetWithRecoveryKey, true, 'param set');
|
||||
return respond(
|
||||
client.getRecoveryKey(accountResetToken, recoveryKeyId),
|
||||
RequestMocks.getRecoveryKey
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(xhrOpen.args[4][0], 'GET', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[4][1],
|
||||
'/recoveryKey/' + recoveryKeyId,
|
||||
'path is correct'
|
||||
);
|
||||
assert.ok(res.recoveryData, 'contains recovery data');
|
||||
|
||||
var options = {
|
||||
keys: true,
|
||||
metricsContext: {
|
||||
deviceId: '0123456789abcdef0123456789abcdef',
|
||||
flowBeginTime: 1480615985437,
|
||||
flowId:
|
||||
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
utmCampaign: 'mock-campaign',
|
||||
utmContent: 'mock-content',
|
||||
utmMedium: 'mock-medium',
|
||||
utmSource: 'mock-source',
|
||||
utmTerm: 'mock-term',
|
||||
},
|
||||
sessionToken: true,
|
||||
};
|
||||
return respond(
|
||||
client.resetPasswordWithRecoveryKey(
|
||||
accountResetToken,
|
||||
email,
|
||||
newPassword,
|
||||
recoveryKeyId,
|
||||
keys,
|
||||
options
|
||||
),
|
||||
RequestMocks.accountReset
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.keyFetchToken);
|
||||
assert.ok(res.sessionToken);
|
||||
assert.ok(res.unwrapBKey);
|
||||
assert.ok(res.uid);
|
||||
|
||||
// Attempt to login with new password and retrieve keys
|
||||
return respond(
|
||||
client.signIn(email, newPassword, { keys: true }),
|
||||
RequestMocks.signInWithKeys
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
return respond(
|
||||
client.accountKeys(res.keyFetchToken, res.unwrapBKey),
|
||||
RequestMocks.accountKeys
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!env.useRemoteServer) {
|
||||
assert.ok(res.kB, 'kB exists');
|
||||
} else {
|
||||
assert.equal(res.kB, keys.kB, 'kB is equal to original kB');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('#can create and delete recovery key', function () {
|
||||
return respond(
|
||||
client.createRecoveryKey(
|
||||
account.signIn.sessionToken,
|
||||
recoveryKeyId,
|
||||
bundle,
|
||||
true
|
||||
),
|
||||
RequestMocks.createRecoveryKey
|
||||
)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
return respond(
|
||||
client.deleteRecoveryKey(account.signIn.sessionToken),
|
||||
RequestMocks.deleteRecoveryKey
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
assert.equal(xhrOpen.args[1][0], 'DELETE', 'method is correct');
|
||||
assert.include(xhrOpen.args[1][1], '/recoveryKey', 'path is correct');
|
||||
});
|
||||
});
|
||||
|
||||
it('#can check if recovery exist using sessionToken', function () {
|
||||
return respond(
|
||||
client.recoveryKeyExists(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryKeyExistsFalse
|
||||
)
|
||||
.then(function (res) {
|
||||
assert.equal(res.exists, false, 'recovery key does not exist');
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/recoveryKey/exists',
|
||||
'path is correct'
|
||||
);
|
||||
return respond(
|
||||
client.createRecoveryKey(
|
||||
account.signIn.sessionToken,
|
||||
recoveryKeyId,
|
||||
bundle,
|
||||
true
|
||||
),
|
||||
RequestMocks.createRecoveryKey
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
return respond(
|
||||
client.recoveryKeyExists(account.signIn.sessionToken),
|
||||
RequestMocks.recoveryKeyExistsTrue
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.exists, true, 'recovery key exists');
|
||||
assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[2][1],
|
||||
'/recoveryKey/exists',
|
||||
'path is correct'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('#can check if recovery exist using email', function () {
|
||||
return respond(
|
||||
client.recoveryKeyExists(undefined, account.input.email),
|
||||
RequestMocks.recoveryKeyExistsFalse
|
||||
)
|
||||
.then(function (res) {
|
||||
assert.equal(res.exists, false, 'recovery key does not exist');
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/recoveryKey/exists',
|
||||
'path is correct'
|
||||
);
|
||||
|
||||
return respond(
|
||||
client.createRecoveryKey(
|
||||
account.signIn.sessionToken,
|
||||
recoveryKeyId,
|
||||
bundle,
|
||||
true
|
||||
),
|
||||
RequestMocks.createRecoveryKey
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
return respond(
|
||||
client.recoveryKeyExists(undefined, account.input.email),
|
||||
RequestMocks.recoveryKeyExistsTrue
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.exists, true, 'recovery key exists');
|
||||
assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[2][1],
|
||||
'/recoveryKey/exists',
|
||||
'path is correct'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
const sinon = require('sinon');
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const Request = require('../../client/lib/request');
|
||||
const ErrorMocks = require('../mocks/errors');
|
||||
describe('request module', function () {
|
||||
var RequestMocks;
|
||||
var request;
|
||||
var env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
RequestMocks = env.RequestMocks;
|
||||
request = new Request(env.authServerUrl, env.xhr);
|
||||
});
|
||||
|
||||
it('#heartbeat', function () {
|
||||
var heartbeatRequest = env
|
||||
.respond(request.send('/__heartbeat__', 'GET'), RequestMocks.heartbeat)
|
||||
.then(function (res) {
|
||||
assert.ok(res);
|
||||
}, assert.fail);
|
||||
|
||||
return heartbeatRequest;
|
||||
});
|
||||
|
||||
it('#error', function () {
|
||||
request = new Request('http://', env.xhr);
|
||||
|
||||
request.send('/', 'GET').then(assert.fail, function () {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('#timeout', function () {
|
||||
request = new Request('http://192.168.1.999', env.xhr, {
|
||||
timeout: 1,
|
||||
});
|
||||
|
||||
var timeoutRequest = env.respond(
|
||||
request.send('/', 'GET'),
|
||||
ErrorMocks.timeout
|
||||
);
|
||||
|
||||
return timeoutRequest.then(assert.fail, function (err) {
|
||||
assert.equal(err.error, 'Timeout error');
|
||||
});
|
||||
});
|
||||
|
||||
it('#bad response format error', function () {
|
||||
request = new Request('http://example.com/', env.xhr);
|
||||
|
||||
// Trigger an error response that's in HTML
|
||||
var response = env.respond(
|
||||
request.send('/nonexistent', 'GET'),
|
||||
ErrorMocks.badResponseFormat
|
||||
);
|
||||
|
||||
return response.then(assert.fail, function (err) {
|
||||
assert.equal(err.error, 'Unknown error');
|
||||
});
|
||||
});
|
||||
|
||||
it('#ensure is usable', function () {
|
||||
request = new Request('http://google.com:81', env.xhr, {
|
||||
timeout: 200,
|
||||
});
|
||||
sinon.stub(env.xhr.prototype, 'open').throws();
|
||||
|
||||
return env
|
||||
.respond(request.send('/__heartbeat__', 'GET'), RequestMocks.heartbeat)
|
||||
.then(null, function (err) {
|
||||
assert.ok(err);
|
||||
env.xhr.prototype.open.restore();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,85 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
describe('securityEvents', function () {
|
||||
let account;
|
||||
let accountHelper;
|
||||
let env;
|
||||
let respond;
|
||||
let client;
|
||||
let RequestMocks;
|
||||
let xhr;
|
||||
let xhrOpen;
|
||||
let xhrSend;
|
||||
|
||||
beforeEach(() => {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
|
||||
return accountHelper.newVerifiedAccount().then((newAccount) => {
|
||||
account = newAccount;
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
it('#securityEvents', function () {
|
||||
return respond(
|
||||
client.securityEvents(account.signIn.sessionToken),
|
||||
RequestMocks.securityEvents
|
||||
).then((res) => {
|
||||
assert.equal(xhrOpen.args[0][0], 'GET', 'method is correct');
|
||||
assert.include(xhrOpen.args[0][1], '/securityEvents', 'path is correct');
|
||||
assert.ok(res, 'got response');
|
||||
assert.equal(res.length, 2);
|
||||
|
||||
assert.equal(res[0].name, 'account.login');
|
||||
assert.equal(res[0].verified, true);
|
||||
assert.equal(res[0].createdAt < new Date().getTime(), true);
|
||||
|
||||
assert.equal(res[1].name, 'account.create');
|
||||
assert.equal(res[1].verified, true);
|
||||
assert.equal(res[1].createdAt < new Date().getTime(), true);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#deleteSecurityEvents', function () {
|
||||
return respond(
|
||||
client.deleteSecurityEvents(account.signIn.sessionToken),
|
||||
RequestMocks.deleteSecurityEvents
|
||||
).then((res) => {
|
||||
assert.equal(xhrOpen.args[0][0], 'DELETE', 'method is correct');
|
||||
assert.include(xhrOpen.args[0][1], '/securityEvents', 'path is correct');
|
||||
assert.ok(res, 'got response');
|
||||
assert.deepEqual(res, {});
|
||||
|
||||
return respond(
|
||||
client.securityEvents(account.signIn.sessionToken),
|
||||
RequestMocks.securityEventsEmptyResponse
|
||||
).then((res) => {
|
||||
assert.equal(xhrOpen.args[1][0], 'GET', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[1][1],
|
||||
'/securityEvents',
|
||||
'path is correct'
|
||||
);
|
||||
assert.ok(res, 'got response');
|
||||
assert.equal(res.length, 0);
|
||||
});
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,473 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
describe('session', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var requests;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var ErrorMocks;
|
||||
var xhr;
|
||||
let env;
|
||||
let mail;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
requests = env.requests;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
xhr = env.xhr;
|
||||
sinon.spy(xhr.prototype, 'open');
|
||||
sinon.spy(xhr.prototype, 'send');
|
||||
mail = env.mail;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhr.prototype.open.restore();
|
||||
xhr.prototype.send.restore();
|
||||
});
|
||||
|
||||
it('#destroy', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.sessionDestroy(account.signIn.sessionToken),
|
||||
RequestMocks.sessionDestroy
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res, 'got response');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#status', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.sessionStatus(account.signIn.sessionToken),
|
||||
RequestMocks.sessionStatus
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.isNotNull(res);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#status error with a false token', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function () {
|
||||
var fakeToken =
|
||||
'e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f10000';
|
||||
|
||||
return respond(
|
||||
client.passwordForgotStatus(fakeToken),
|
||||
ErrorMocks.invalidAuthToken
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (err) {
|
||||
assert.equal(err.code, 401);
|
||||
assert.equal(err.errno, 110);
|
||||
});
|
||||
});
|
||||
|
||||
it('#sessions', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.sessions(account.signIn.sessionToken),
|
||||
RequestMocks.sessions
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.length, 2);
|
||||
var s = res[0];
|
||||
assert.ok(s.id);
|
||||
assert.ok(s.deviceType);
|
||||
assert.equal(s.isDevice, false);
|
||||
assert.ok(s.lastAccessTime);
|
||||
assert.ok(s.lastAccessTimeFormatted);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#sessions error', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
var fakeToken =
|
||||
'e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f10000';
|
||||
|
||||
return respond(client.sessions(fakeToken), ErrorMocks.invalidAuthToken);
|
||||
})
|
||||
.then(assert.fail, function (err) {
|
||||
assert.equal(err.code, 401);
|
||||
assert.equal(err.errno, 110);
|
||||
});
|
||||
});
|
||||
|
||||
it('#reauth', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var email = account.input.email;
|
||||
var password = account.input.password;
|
||||
|
||||
return respond(
|
||||
client.sessionReauth(account.signIn.sessionToken, email, password),
|
||||
RequestMocks.sessionReauth
|
||||
).then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
assert.ok(res.verified);
|
||||
assert.ok(res.authAt);
|
||||
assert.notOk(res.keyFetchToken);
|
||||
assert.notOk(res.unwrapBKey);
|
||||
|
||||
var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/reauth');
|
||||
|
||||
var payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 2);
|
||||
assert.equal(payload.email, email);
|
||||
assert.equal(payload.authPW.length, 64);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('#reauth with keys', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var email = account.input.email;
|
||||
var password = account.input.password;
|
||||
|
||||
return respond(
|
||||
client.sessionReauth(account.signIn.sessionToken, email, password, {
|
||||
keys: true,
|
||||
}),
|
||||
RequestMocks.sessionReauthWithKeys
|
||||
).then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
assert.ok(res.verified);
|
||||
assert.ok(res.authAt);
|
||||
assert.ok(res.keyFetchToken);
|
||||
assert.ok(res.unwrapBKey);
|
||||
|
||||
var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/reauth?keys=true');
|
||||
|
||||
var payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 2);
|
||||
assert.equal(payload.email, email);
|
||||
assert.equal(payload.authPW.length, 64);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('#reauth with incorrect password', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var email = account.input.email;
|
||||
var password = 'incorrect password';
|
||||
|
||||
return respond(
|
||||
client.sessionReauth(account.signIn.sessionToken, email, password),
|
||||
ErrorMocks.accountIncorrectPassword
|
||||
).then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 103);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('#reauth with incorrect email case', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var numSetupRequests = requests ? requests.length : null;
|
||||
var sessionToken = account.signIn.sessionToken;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
var password = account.input.password;
|
||||
|
||||
respond(ErrorMocks.incorrectEmailCase);
|
||||
return respond(
|
||||
client.sessionReauth(sessionToken, incorrectCaseEmail, password),
|
||||
RequestMocks.sessionReauth
|
||||
).then(function (res) {
|
||||
assert.property(res, 'uid');
|
||||
assert.property(res, 'verified');
|
||||
assert.property(res, 'authAt');
|
||||
|
||||
if (requests) {
|
||||
assert.equal(requests.length - numSetupRequests, 2);
|
||||
}
|
||||
|
||||
var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 2];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/reauth');
|
||||
|
||||
var payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 2][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 2);
|
||||
assert.equal(payload.email, incorrectCaseEmail);
|
||||
assert.equal(payload.authPW.length, 64);
|
||||
|
||||
args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/reauth');
|
||||
|
||||
payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 3);
|
||||
assert.notEqual(payload.email, incorrectCaseEmail);
|
||||
assert.equal(payload.originalLoginEmail, incorrectCaseEmail);
|
||||
assert.equal(payload.authPW.length, 64);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('#reauth with incorrect email case with skipCaseError', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var numSetupRequests = requests ? requests.length : null;
|
||||
var sessionToken = account.signIn.sessionToken;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
var password = account.input.password;
|
||||
|
||||
return respond(
|
||||
client.sessionReauth(sessionToken, incorrectCaseEmail, password, {
|
||||
skipCaseError: true,
|
||||
}),
|
||||
ErrorMocks.incorrectEmailCase
|
||||
).then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 120);
|
||||
|
||||
if (requests) {
|
||||
assert.equal(requests.length - numSetupRequests, 1);
|
||||
}
|
||||
|
||||
var args =
|
||||
xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/reauth');
|
||||
|
||||
var payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 2);
|
||||
assert.equal(payload.email, incorrectCaseEmail);
|
||||
assert.equal(payload.authPW.length, 64);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('#reauth with all the options', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var sessionToken = account.signIn.sessionToken;
|
||||
var email = account.input.email;
|
||||
var password = account.input.password;
|
||||
var options = {
|
||||
keys: true,
|
||||
metricsContext: {
|
||||
entrypoint: 'mock-entrypoint',
|
||||
entrypointExperiment: 'mock-entrypoint-experiment',
|
||||
entrypointVariation: 'mock-entrypoint-variation',
|
||||
utmCampaign: 'mock-utm-campaign',
|
||||
utmContent: 'mock-utm-content',
|
||||
utmMedium: 'mock-utm-medium',
|
||||
utmSource: 'mock-utm-source',
|
||||
utmTerm: 'mock-utm-term',
|
||||
},
|
||||
originalLoginEmail: email.toUpperCase(),
|
||||
reason: 'password_change',
|
||||
redirectTo: 'http://localhost',
|
||||
resume: 'RESUME_TOKEN',
|
||||
service: 'sync',
|
||||
verificationMethod: 'email-2fa',
|
||||
};
|
||||
|
||||
return respond(
|
||||
client.sessionReauth(sessionToken, email, password, options),
|
||||
RequestMocks.sessionReauthWithKeys
|
||||
).then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
assert.ok(res.verified);
|
||||
assert.ok(res.authAt);
|
||||
assert.ok(res.keyFetchToken);
|
||||
assert.ok(res.unwrapBKey);
|
||||
|
||||
var args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/reauth?keys=true');
|
||||
|
||||
var payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 9);
|
||||
assert.equal(payload.email, email);
|
||||
assert.equal(payload.authPW.length, 64);
|
||||
assert.deepEqual(payload.metricsContext, options.metricsContext);
|
||||
assert.equal(payload.originalLoginEmail, options.originalLoginEmail);
|
||||
assert.equal(payload.reason, options.reason);
|
||||
assert.equal(payload.redirectTo, options.redirectTo);
|
||||
assert.equal(payload.resume, options.resume);
|
||||
assert.equal(payload.service, options.service);
|
||||
assert.equal(payload.verificationMethod, options.verificationMethod);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#verify_code', () => {
|
||||
it('with valid code', async () => {
|
||||
const account = await accountHelper.newUnconfirmedAccount({
|
||||
verificationMethod: 'email-otp',
|
||||
});
|
||||
const emails = await respond(
|
||||
mail.wait(account.input.user, 1),
|
||||
RequestMocks.signUpVerifyCodeEmailSent
|
||||
);
|
||||
const code = emails[0].headers['x-verify-short-code'];
|
||||
const response = await respond(
|
||||
client.sessionVerifyCode(account.signUp.sessionToken, code),
|
||||
RequestMocks.sessionVerifyCode
|
||||
);
|
||||
assert.deepEqual(response, {});
|
||||
|
||||
const args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/verify_code');
|
||||
|
||||
const payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 1);
|
||||
assert.equal(payload.code, code);
|
||||
});
|
||||
|
||||
it('with valid code and all options', async () => {
|
||||
const account = await accountHelper.newUnconfirmedAccount({
|
||||
verificationMethod: 'email-otp',
|
||||
});
|
||||
const emails = await respond(
|
||||
mail.wait(account.input.user, 1),
|
||||
RequestMocks.signUpVerifyCodeEmailSent
|
||||
);
|
||||
const code = emails[0].headers['x-verify-short-code'];
|
||||
|
||||
const allOptions = {
|
||||
service: 'sync',
|
||||
style: 'trailhead',
|
||||
newsletters: ['test-pilot'],
|
||||
};
|
||||
const response = await respond(
|
||||
client.sessionVerifyCode(account.signUp.sessionToken, code, allOptions),
|
||||
RequestMocks.sessionVerifyCode
|
||||
);
|
||||
assert.deepEqual(response, {});
|
||||
|
||||
const args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/verify_code');
|
||||
|
||||
const payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 4);
|
||||
assert.equal(payload.code, code);
|
||||
assert.equal(payload.service, allOptions.service);
|
||||
assert.equal(payload.style, allOptions.style);
|
||||
assert.deepEqual(payload.newsletters, allOptions.newsletters);
|
||||
});
|
||||
|
||||
it('with invalid code', async () => {
|
||||
const account = await accountHelper.newUnconfirmedAccount({
|
||||
verificationMethod: 'email-otp',
|
||||
});
|
||||
const emails = await respond(
|
||||
mail.wait(account.input.user, 1),
|
||||
RequestMocks.signUpVerifyCodeEmailSent
|
||||
);
|
||||
const invalidCode =
|
||||
'123123' === emails[0].headers['x-verify-short-code']
|
||||
? '123124'
|
||||
: '123123';
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await respond(
|
||||
client.sessionVerifyCode(account.signUp.sessionToken, invalidCode),
|
||||
RequestMocks.sessionVerifyCodeInvalid
|
||||
);
|
||||
assert.isNotOk(response);
|
||||
} catch (err) {
|
||||
assert.equal(err.errno, 183);
|
||||
}
|
||||
|
||||
const args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/verify_code');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resend_code', () => {
|
||||
it('resend code', async () => {
|
||||
const account = await accountHelper.newUnconfirmedAccount({
|
||||
verificationMethod: 'email-otp',
|
||||
});
|
||||
let emails = await respond(
|
||||
mail.wait(account.input.user, 1),
|
||||
RequestMocks.signUpVerifyCodeEmailSent
|
||||
);
|
||||
const originalCode = emails[0].headers['x-verify-short-code'];
|
||||
|
||||
const response = await respond(
|
||||
client.sessionResendVerifyCode(account.signUp.sessionToken),
|
||||
RequestMocks.sessionResendVerifyCode
|
||||
);
|
||||
assert.deepEqual(response, {});
|
||||
|
||||
const args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/session/resend_code');
|
||||
|
||||
const payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 0);
|
||||
|
||||
emails = await respond(
|
||||
mail.wait(account.input.user, 2),
|
||||
RequestMocks.sessionResendVerifyCodeEmail
|
||||
);
|
||||
const code = emails[1].headers['x-verify-short-code'];
|
||||
|
||||
assert.equal(originalCode, code, 'codes match');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,246 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('signIn', function () {
|
||||
var ErrorMocks;
|
||||
var RequestMocks;
|
||||
var accountHelper;
|
||||
var client;
|
||||
var mail;
|
||||
var respond;
|
||||
var requests;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
RequestMocks = env.RequestMocks;
|
||||
accountHelper = env.accountHelper;
|
||||
client = env.client;
|
||||
mail = env.mail;
|
||||
respond = env.respond;
|
||||
requests = env.requests;
|
||||
});
|
||||
|
||||
it('#basic', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function () {
|
||||
return respond(client.signIn(email, password), RequestMocks.signIn);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.sessionToken);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#with keys', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
return respond(
|
||||
client.signIn(email, password, { keys: true }),
|
||||
RequestMocks.signInWithKeys
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.sessionToken);
|
||||
assert.ok(res.keyFetchToken);
|
||||
assert.ok(res.unwrapBKey);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#with service', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp).then(
|
||||
function () {
|
||||
return respond(
|
||||
client.signIn(email, password, { service: 'sync' }),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#with reason', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp).then(
|
||||
function () {
|
||||
return respond(
|
||||
client.signIn(email, password, { reason: 'password_change' }),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#with Sync/redirectTo', function () {
|
||||
var user = 'sync.' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
keys: true,
|
||||
metricsContext: {
|
||||
context: 'fx_desktop_v2',
|
||||
},
|
||||
redirectTo: 'http://sync.localhost/after_reset',
|
||||
service: 'sync',
|
||||
};
|
||||
|
||||
return respond(
|
||||
client.signUp(email, password, { preVerified: true }),
|
||||
RequestMocks.signUp
|
||||
)
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.signIn(email, password, opts),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
var redirectTo = emails[0].html.match(/redirectTo=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
assert.ok(code, 'code is returned');
|
||||
assert.ok(redirectTo, 'redirectTo is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#with Sync/resume', function () {
|
||||
var user = 'sync.' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
keys: true,
|
||||
metricsContext: {
|
||||
context: 'fx_desktop_v2',
|
||||
},
|
||||
redirectTo: 'http://sync.localhost/after_reset',
|
||||
resume: 'resumejwt',
|
||||
service: 'sync',
|
||||
};
|
||||
|
||||
return respond(
|
||||
client.signUp(email, password, { preVerified: true }),
|
||||
RequestMocks.signUp
|
||||
)
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.signIn(email, password, opts),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
var resume = emails[0].html.match(/resume=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
assert.ok(code, 'code is returned');
|
||||
assert.ok(resume, 'resume is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#incorrect email case', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var numSetupRequests = requests ? requests.length : null;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
respond(ErrorMocks.incorrectEmailCase);
|
||||
return respond(
|
||||
client.signIn(incorrectCaseEmail, account.input.password),
|
||||
RequestMocks.signIn
|
||||
).then(function (res) {
|
||||
assert.property(res, 'sessionToken');
|
||||
if (requests) {
|
||||
assert.equal(requests.length - numSetupRequests, 2);
|
||||
}
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('#incorrect email case with skipCaseError', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (account) {
|
||||
var numSetupRequests = requests ? requests.length : null;
|
||||
var incorrectCaseEmail =
|
||||
account.input.email.charAt(0).toUpperCase() +
|
||||
account.input.email.slice(1);
|
||||
|
||||
return respond(
|
||||
client.signIn(incorrectCaseEmail, account.input.password, {
|
||||
skipCaseError: true,
|
||||
}),
|
||||
ErrorMocks.incorrectEmailCase
|
||||
).then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 120);
|
||||
if (requests) {
|
||||
assert.equal(requests.length - numSetupRequests, 1);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('#incorrectPassword', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.signIn(account.input.email, 'wrong password'),
|
||||
ErrorMocks.accountIncorrectPassword
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
assert.fail();
|
||||
},
|
||||
function (res) {
|
||||
assert.equal(res.code, 400);
|
||||
assert.equal(res.errno, 103);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#with metricsContext metadata', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function () {
|
||||
return respond(
|
||||
client.signIn(email, password, {
|
||||
metricsContext: {},
|
||||
reason: 'signin',
|
||||
}),
|
||||
RequestMocks.signIn
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,308 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
describe('signUp', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var mail;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var ErrorMocks;
|
||||
var xhr;
|
||||
var xhrOpen;
|
||||
var xhrSend;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
mail = env.mail;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
ErrorMocks = env.ErrorMocks;
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
it('#basic', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp).then(
|
||||
function (res) {
|
||||
assert.property(res, 'uid', 'uid should be returned on signUp');
|
||||
assert.property(
|
||||
res,
|
||||
'sessionToken',
|
||||
'sessionToken should be returned on signUp'
|
||||
);
|
||||
assert.notProperty(
|
||||
res,
|
||||
'keyFetchToken',
|
||||
'keyFetchToken should not be returned on signUp'
|
||||
);
|
||||
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/account/create',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[0][0]);
|
||||
assert.equal(Object.keys(sentData).length, 2);
|
||||
assert.equal(sentData.email, email, 'email is correct');
|
||||
assert.equal(sentData.authPW.length, 64, 'length of authPW');
|
||||
},
|
||||
assert.fail
|
||||
);
|
||||
});
|
||||
|
||||
it('#withKeys', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
keys: true,
|
||||
};
|
||||
|
||||
return respond(
|
||||
client.signUp(email, password, opts),
|
||||
RequestMocks.signUpKeys
|
||||
).then(function (res) {
|
||||
assert.property(res, 'uid', 'uid should be returned on signUp');
|
||||
assert.property(
|
||||
res,
|
||||
'sessionToken',
|
||||
'sessionToken should be returned on signUp'
|
||||
);
|
||||
assert.property(
|
||||
res,
|
||||
'keyFetchToken',
|
||||
'keyFetchToken should be returned on signUp'
|
||||
);
|
||||
assert.property(
|
||||
res,
|
||||
'unwrapBKey',
|
||||
'unwrapBKey should be returned on signUp'
|
||||
);
|
||||
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/account/create?keys=true',
|
||||
'path is correct'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#create account with service, redirectTo, and resume', function () {
|
||||
var user = 'test' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
service: 'sync',
|
||||
redirectTo: 'https://sync.localhost/after_reset',
|
||||
resume: 'resumejwt',
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password, opts), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
var service = emails[0].html.match(/service=([A-Za-z0-9]+)/)[1];
|
||||
var redirectTo = emails[0].html.match(/redirectTo=([A-Za-z0-9]+)/)[1];
|
||||
var resume = emails[0].html.match(/resume=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
assert.ok(code, 'code is returned');
|
||||
assert.ok(service, 'service is returned');
|
||||
assert.ok(redirectTo, 'redirectTo is returned');
|
||||
assert.ok(resume, 'resume is returned');
|
||||
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/account/create',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[0][0]);
|
||||
assert.equal(Object.keys(sentData).length, 5);
|
||||
assert.equal(sentData.email, email, 'email is correct');
|
||||
assert.equal(sentData.authPW.length, 64, 'length of authPW');
|
||||
assert.equal(sentData.service, opts.service);
|
||||
assert.equal(sentData.resume, opts.resume);
|
||||
assert.equal(sentData.redirectTo, opts.redirectTo);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#withService', function () {
|
||||
var user = 'test' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
service: 'sync',
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password, opts), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
var service = emails[0].html.match(/service=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
assert.ok(code, 'code is returned');
|
||||
assert.ok(service, 'service is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#withRedirectTo', function () {
|
||||
var user = 'test' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
redirectTo: 'http://sync.localhost/after_reset',
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password, opts), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
var redirectTo = emails[0].html.match(/redirectTo=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
assert.ok(code, 'code is returned');
|
||||
assert.ok(redirectTo, 'redirectTo is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#withResume', function () {
|
||||
var user = 'test' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
resume: 'resumejwt',
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password, opts), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
return respond(mail.wait(user), RequestMocks.mailServiceAndRedirect);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
var resume = emails[0].html.match(/resume=([A-Za-z0-9]+)/)[1];
|
||||
|
||||
assert.ok(code, 'code is returned');
|
||||
assert.ok(resume, 'resume is returned');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#preVerified', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var opts = {
|
||||
preVerified: true,
|
||||
};
|
||||
|
||||
return respond(client.signUp(email, password, opts), RequestMocks.signUp)
|
||||
.then(function (res) {
|
||||
assert.ok(res.uid);
|
||||
|
||||
return respond(client.signIn(email, password), RequestMocks.signIn);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(res.verified, true, '== account is verified');
|
||||
});
|
||||
});
|
||||
|
||||
it('#accountExists', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.signUp(account.input.email, 'somepass'),
|
||||
ErrorMocks.accountExists
|
||||
);
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
assert.fail();
|
||||
},
|
||||
function (err) {
|
||||
assert.equal(err.code, 400);
|
||||
assert.equal(err.errno, 101);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#with metricsContext metadata', function () {
|
||||
var email = 'test' + new Date().getTime() + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
|
||||
return respond(
|
||||
client.signUp(email, password, {
|
||||
metricsContext: {
|
||||
deviceId: '0123456789abcdef0123456789abcdef',
|
||||
flowId:
|
||||
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
flowBeginTime: Date.now(),
|
||||
utmCampaign: 'mock-campaign',
|
||||
utmContent: 'mock-content',
|
||||
utmMedium: 'mock-medium',
|
||||
utmSource: 'mock-source',
|
||||
utmTerm: 'mock-term',
|
||||
forbiddenProperty: 666,
|
||||
},
|
||||
}),
|
||||
RequestMocks.signUp
|
||||
).then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#with verificationMethod `email-otp`', async function () {
|
||||
const account = await accountHelper.newUnconfirmedAccount({
|
||||
verificationMethod: 'email-otp',
|
||||
});
|
||||
|
||||
const args = xhr.prototype.open.args[xhr.prototype.open.args.length - 1];
|
||||
assert.equal(args[0], 'POST');
|
||||
assert.include(args[1], '/account/create');
|
||||
|
||||
const payload = JSON.parse(
|
||||
xhr.prototype.send.args[xhr.prototype.send.args.length - 1][0]
|
||||
);
|
||||
assert.equal(Object.keys(payload).length, 3);
|
||||
assert.equal(payload.verificationMethod, 'email-otp');
|
||||
assert.equal(payload.email, account.input.email);
|
||||
assert.equal(payload.authPW.length, 64, 'length of authPW');
|
||||
|
||||
// Verify the account for good measure
|
||||
const emails = await respond(
|
||||
mail.wait(account.input.user, 1),
|
||||
RequestMocks.signUpVerifyCodeEmailSent
|
||||
);
|
||||
const code = emails[0].headers['x-verify-short-code'];
|
||||
const response = await respond(
|
||||
client.sessionVerifyCode(account.signUp.sessionToken, code),
|
||||
RequestMocks.sessionVerifyCode
|
||||
);
|
||||
assert.deepEqual(response, {});
|
||||
});
|
||||
});
|
|
@ -1,107 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var SIGNIN_CODE = '123456-_';
|
||||
var FLOW_ID =
|
||||
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
|
||||
var FLOW_BEGIN_TIME = Date.now();
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('signinCodes', function () {
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
let remoteServer;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
remoteServer = env.useRemoteServer;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
// This test is intended to run against a local auth-server. To test
|
||||
// against a mock auth-server would be pointless for this assertion.
|
||||
it('consumeSigninCode with invalid signinCode', function () {
|
||||
if (!remoteServer) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
return client.consumeSigninCode(SIGNIN_CODE, FLOW_ID, FLOW_BEGIN_TIME).then(
|
||||
function () {
|
||||
assert.fail(
|
||||
'client.consumeSigninCode should reject if signinCode is invalid'
|
||||
);
|
||||
},
|
||||
function (err) {
|
||||
assert.ok(err, 'client.consumeSigninCode should return an error');
|
||||
assert.equal(
|
||||
err.code,
|
||||
400,
|
||||
'client.consumeSigninCode should return a 400 response'
|
||||
);
|
||||
assert.equal(
|
||||
err.errno,
|
||||
146,
|
||||
'client.consumeSigninCode should return errno 146'
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// This test is intended to run against a mock auth-server. To test
|
||||
// against a local auth-server, we'd need to know a valid signinCode.
|
||||
it('consumeSigninCode', function () {
|
||||
if (remoteServer) {
|
||||
return this.skip();
|
||||
}
|
||||
return respond(
|
||||
client.consumeSigninCode(SIGNIN_CODE, FLOW_ID, FLOW_BEGIN_TIME),
|
||||
RequestMocks.consumeSigninCode
|
||||
).then(assert.ok, assert.fail);
|
||||
});
|
||||
|
||||
it('consumeSigninCode with missing code', function () {
|
||||
return client.consumeSigninCode(null, FLOW_ID, FLOW_BEGIN_TIME).then(
|
||||
function () {
|
||||
assert.fail(
|
||||
'client.consumeSigninCode should reject if code is missing'
|
||||
);
|
||||
},
|
||||
function (err) {
|
||||
assert.equal(err.message, 'Missing code');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('consumeSigninCode with missing flowId', function () {
|
||||
return client.consumeSigninCode(SIGNIN_CODE, null, FLOW_BEGIN_TIME).then(
|
||||
function () {
|
||||
assert.fail(
|
||||
'client.consumeSigninCode should reject if flowId is missing'
|
||||
);
|
||||
},
|
||||
function (err) {
|
||||
assert.equal(err.message, 'Missing flowId');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('consumeSigninCode with missing flowBeginTime', function () {
|
||||
return client.consumeSigninCode(SIGNIN_CODE, FLOW_ID, null).then(
|
||||
function () {
|
||||
assert.fail(
|
||||
'client.consumeSigninCode should reject if flowBeginTime is missing'
|
||||
);
|
||||
},
|
||||
function (err) {
|
||||
assert.equal(err.message, 'Missing flowBeginTime');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,70 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
var PHONE_NUMBER = '+14168483114';
|
||||
var MESSAGE_ID = 1;
|
||||
|
||||
describe('sms', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
});
|
||||
|
||||
it('#send connect device', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.sendSms(account.signIn.sessionToken, PHONE_NUMBER, MESSAGE_ID),
|
||||
RequestMocks.sendSmsConnectDevice
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('status', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.smsStatus(account.signIn.sessionToken),
|
||||
RequestMocks.smsStatus
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
assert.ok(resp.ok);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('status with country', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.smsStatus(account.signIn.sessionToken, { country: 'US' }),
|
||||
RequestMocks.smsStatus
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
assert.ok(resp.ok);
|
||||
assert.ok(resp.country, 'US');
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,141 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
describe('subscriptions', function () {
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
let env;
|
||||
let remoteServer;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
remoteServer = env.useRemoteServer;
|
||||
});
|
||||
|
||||
it('#getSubscriptionPlans - missing token', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getSubscriptionPlans(),
|
||||
RequestMocks.getSubscriptionPlans
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing token');
|
||||
});
|
||||
});
|
||||
|
||||
it('#getSubscriptionPlans', function () {
|
||||
if (remoteServer) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getSubscriptionPlans('saynomore'),
|
||||
RequestMocks.getSubscriptionPlans
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#getActiveSubscriptions - missing token', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getActiveSubscriptions(),
|
||||
RequestMocks.getActiveSubscriptions
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing token');
|
||||
});
|
||||
});
|
||||
|
||||
// This test is intended to run against a mock auth-server. To test
|
||||
// against a local auth-server, we'd need to know a valid subscription.
|
||||
it('#getActiveSubscriptions', function () {
|
||||
if (remoteServer) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.getActiveSubscriptions('saynomore'),
|
||||
RequestMocks.getActiveSubscriptions
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#createSupportTicket - missing token', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createSupportTicket(),
|
||||
RequestMocks.createSupportTicket
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing token');
|
||||
});
|
||||
});
|
||||
it('#createSupportTicket - missing supportTicket', function () {
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createSupportTicket('redpandas'),
|
||||
RequestMocks.createSupportTicket
|
||||
);
|
||||
})
|
||||
.then(assert.fail, function (error) {
|
||||
assert.include(error.message, 'Missing supportTicket');
|
||||
});
|
||||
});
|
||||
|
||||
// This test is intended to run against a mock auth-server. To test
|
||||
// against a local auth-server, we'd need to know a valid subscription.
|
||||
it('#createSupportTicket', function () {
|
||||
if (remoteServer) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (account) {
|
||||
return respond(
|
||||
client.createSupportTicket('redpandas', {
|
||||
topic: 'Species',
|
||||
subject: 'Cute & Rare',
|
||||
message: 'Need moar',
|
||||
}),
|
||||
RequestMocks.createSupportTicket
|
||||
);
|
||||
})
|
||||
.then(function (resp) {
|
||||
assert.ok(resp);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,183 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
const otplib = require('otplib');
|
||||
describe('totp', function () {
|
||||
var authenticator;
|
||||
var account;
|
||||
var accountHelper;
|
||||
var respond;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var env;
|
||||
var xhr;
|
||||
var xhrOpen;
|
||||
var xhrSend;
|
||||
var secret;
|
||||
var opts = {
|
||||
metricsContext: {
|
||||
flowBeginTime: Date.now(),
|
||||
flowId:
|
||||
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
},
|
||||
service: 'sync',
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
accountHelper = env.accountHelper;
|
||||
respond = env.respond;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
|
||||
return accountHelper
|
||||
.newVerifiedAccount()
|
||||
.then(function (newAccount) {
|
||||
account = newAccount;
|
||||
return respond(
|
||||
client.createTotpToken(account.signIn.sessionToken),
|
||||
RequestMocks.createTotpToken
|
||||
);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.ok(res.qrCodeUrl, 'should return QR code data encoded url');
|
||||
assert.ok(res.secret, 'should return secret that is encoded in url');
|
||||
|
||||
// Create a new authenticator instance with shared options
|
||||
authenticator = new otplib.authenticator.Authenticator();
|
||||
authenticator.options = otplib.authenticator.options;
|
||||
secret = res.secret;
|
||||
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
it('#createTotpToken - fails if already exists', function () {
|
||||
return respond(
|
||||
client.createTotpToken(account.signIn.sessionToken),
|
||||
RequestMocks.createTotpTokenDuplicate
|
||||
).then(assert.fail, function (err) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(xhrOpen.args[0][1], '/totp/create', 'path is correct');
|
||||
assert.equal(err.errno, 154, 'token already exists for account errno');
|
||||
});
|
||||
});
|
||||
|
||||
it('#deleteTotpToken', function () {
|
||||
return respond(
|
||||
client.deleteTotpToken(account.signIn.sessionToken),
|
||||
RequestMocks.deleteTotpToken
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(xhrOpen.args[0][1], '/totp/destroy', 'path is correct');
|
||||
assert.ok(res, 'should return empty response');
|
||||
});
|
||||
});
|
||||
|
||||
it('#checkTotpTokenExists - does not exist returns false', function () {
|
||||
return accountHelper.newVerifiedAccount().then(function (newAccount) {
|
||||
return respond(
|
||||
client.checkTotpTokenExists(newAccount.signIn.sessionToken),
|
||||
RequestMocks.checkTotpTokenExistsFalse
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[4][0], 'GET', 'method is correct');
|
||||
assert.include(xhrOpen.args[4][1], '/totp/exists', 'path is correct');
|
||||
assert.equal(res.exists, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('#checkTotpTokenExists - created token but not verified returns false', function () {
|
||||
return respond(
|
||||
client.checkTotpTokenExists(account.signIn.sessionToken),
|
||||
RequestMocks.checkTotpTokenExistsFalse
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'GET', 'method is correct');
|
||||
assert.include(xhrOpen.args[0][1], '/totp/exists', 'path is correct');
|
||||
assert.equal(res.exists, false);
|
||||
});
|
||||
});
|
||||
|
||||
it('#checkTotpTokenExists - verified token returns true', function () {
|
||||
var code = authenticator.generate(secret);
|
||||
return respond(
|
||||
client.verifyTotpCode(account.signIn.sessionToken, code),
|
||||
RequestMocks.verifyTotpCodeTrue
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/session/verify/totp',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[0][0]);
|
||||
assert.equal(Object.keys(sentData).length, 1);
|
||||
assert.equal(sentData.code, code, 'code is correct');
|
||||
|
||||
assert.equal(res.success, true);
|
||||
return respond(
|
||||
client.checkTotpTokenExists(account.signIn.sessionToken),
|
||||
RequestMocks.checkTotpTokenExistsTrue
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[1][0], 'GET', 'method is correct');
|
||||
assert.include(xhrOpen.args[1][1], '/totp/exists', 'path is correct');
|
||||
assert.equal(res.exists, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('#verifyTotpCode - succeeds for valid code', function () {
|
||||
var code = authenticator.generate(secret);
|
||||
return respond(
|
||||
client.verifyTotpCode(account.signIn.sessionToken, code, opts),
|
||||
RequestMocks.verifyTotpCodeTrue
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/session/verify/totp',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[0][0]);
|
||||
assert.lengthOf(Object.keys(sentData), 2);
|
||||
assert.equal(sentData.code, code, 'code is correct');
|
||||
assert.equal(sentData.service, opts.service, 'service is correct');
|
||||
|
||||
assert.equal(res.success, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('#verifyTotpCode - fails for invalid code', function () {
|
||||
var code =
|
||||
authenticator.generate(secret) === '000000' ? '000001' : '000000';
|
||||
return respond(
|
||||
client.verifyTotpCode(account.signIn.sessionToken, code, opts),
|
||||
RequestMocks.verifyTotpCodeFalse
|
||||
).then(function (res) {
|
||||
assert.equal(xhrOpen.args[0][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[0][1],
|
||||
'/session/verify/totp',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[0][0]);
|
||||
assert.lengthOf(Object.keys(sentData), 2);
|
||||
assert.equal(sentData.code, code, 'code is correct');
|
||||
assert.equal(sentData.service, opts.service, 'service is correct');
|
||||
|
||||
assert.equal(res.success, false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,88 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const sjcl = require('sjcl');
|
||||
const credentials = require('../../client/lib/credentials');
|
||||
|
||||
describe('unbundle', function () {
|
||||
it('#vector 1', function () {
|
||||
// credentials.unbundleKeyFetchResponse(bundleKey, 'account/keys', payload.bundle);
|
||||
// Vectors generated from fxa-auth-server
|
||||
var bundleKey =
|
||||
'ba0a107dab60f3b065ff7a642d14fe824fbd71bc5c99087e9e172a1abd1634f1';
|
||||
var keyInfo = 'account/keys';
|
||||
var bundle =
|
||||
'e47eb17e487eb4495e79846d5e0c16ea51ef51ff5ef59cd8f626f95f572ec64dcc7b97fcbc0d0ece0cc93dbe6ac84974066830280ccacf5de13a8460524238cf543edfc5027aabeddc107e9fd429a25ce6f5d94917f2a6435380ee5f11353814';
|
||||
var bitBundle = sjcl.codec.hex.toBits(bundle);
|
||||
|
||||
return credentials
|
||||
.deriveBundleKeys(bundleKey, keyInfo)
|
||||
.then(function (keys) {
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(keys.hmacKey),
|
||||
'17ab463653a94c9a6419b48781930edefe500395e3b4e7879a2be15999757022',
|
||||
'== hmacKey equal'
|
||||
);
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(keys.xorKey),
|
||||
'85de16c3218a126404668bf9b7acfb6ce2b7e03c8889047ba48b8b854c6d8beb3ae100e145ca6d69cb519a872a83af788771954455716143bc08225ea8644d85',
|
||||
'== xorKey equal'
|
||||
);
|
||||
|
||||
var keyAWrapB = credentials.xor(
|
||||
sjcl.bitArray.bitSlice(bitBundle, 0, 8 * 64),
|
||||
keys.xorKey
|
||||
);
|
||||
assert.equal(
|
||||
sjcl.codec.hex.fromBits(keyAWrapB),
|
||||
'61a0a7bd69f4a62d5a1f0f94e9a0ed86b358b1c3d67c98a352ad72da1b434da6f69a971df9c763a7c798a739404be60c8119a56c59bbae1e5d32a63efa26754a',
|
||||
'== xorBuffers equal'
|
||||
);
|
||||
var keyObj = {
|
||||
kA: sjcl.codec.hex.fromBits(
|
||||
sjcl.bitArray.bitSlice(keyAWrapB, 0, 8 * 32)
|
||||
),
|
||||
wrapKB: sjcl.codec.hex.fromBits(
|
||||
sjcl.bitArray.bitSlice(keyAWrapB, 8 * 32, 8 * 64)
|
||||
),
|
||||
};
|
||||
|
||||
return keyObj;
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.equal(
|
||||
result.kA,
|
||||
'61a0a7bd69f4a62d5a1f0f94e9a0ed86b358b1c3d67c98a352ad72da1b434da6',
|
||||
'== kA equal'
|
||||
);
|
||||
assert.equal(
|
||||
result.wrapKB,
|
||||
'f69a971df9c763a7c798a739404be60c8119a56c59bbae1e5d32a63efa26754a',
|
||||
'== wrapKB equal'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('#vector 2', function () {
|
||||
var bundleKey =
|
||||
'dedd009a8275a4f672bb4b41e14a117812c0b2f400c85fa058e0293f3f45726a';
|
||||
var bundle =
|
||||
'df4717238a738501bd2ad8f7114ef193ea69751a40108149bfb88a5643a8d683a1e75b705d4db135130f0896dbac0819ab7d54334e0cd4f9c945e0a7ada91899756cedf4384be404844050270310bc2b396f100eeda0c7b428cfe77c40a873ae';
|
||||
return credentials
|
||||
.unbundleKeyFetchResponse(bundleKey, bundle)
|
||||
.then(function (result) {
|
||||
assert.equal(
|
||||
result.kA,
|
||||
'939282904b808c6003ea31aeb14bc766d2ab70ba7dcaa54f820efcf4762b9619',
|
||||
'== kA equal'
|
||||
);
|
||||
assert.equal(
|
||||
result.wrapKB,
|
||||
'849ac9f71643ace46dcdd384633ec1bffe565852806ee2f859c3eba7fafeafec',
|
||||
'== wrapKB equal'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const FxAccountClient = require('../../client/FxAccountClient');
|
||||
var xhr = function () {};
|
||||
var serverUri = 'https://mock.server';
|
||||
var VERSION = FxAccountClient.VERSION;
|
||||
|
||||
describe('fxa client', function () {
|
||||
it('#version appended to uri when not present', function () {
|
||||
var client = new FxAccountClient(serverUri, { xhr: xhr });
|
||||
assert.equal(serverUri + '/' + VERSION, client.request.baseUri);
|
||||
});
|
||||
|
||||
it('#version not appended to uri when already present', function () {
|
||||
var uri = serverUri + '/' + VERSION;
|
||||
var client = new FxAccountClient(uri, { xhr: xhr });
|
||||
assert.equal(uri, client.request.baseUri);
|
||||
});
|
||||
});
|
|
@ -1,222 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const assert = require('chai').assert;
|
||||
const Environment = require('../addons/environment');
|
||||
|
||||
const sinon = require('sinon');
|
||||
describe('verifyCode', function () {
|
||||
var respond;
|
||||
let env;
|
||||
var mail;
|
||||
var client;
|
||||
var RequestMocks;
|
||||
var xhr;
|
||||
var xhrOpen;
|
||||
var xhrSend;
|
||||
|
||||
beforeEach(function () {
|
||||
env = new Environment();
|
||||
respond = env.respond;
|
||||
mail = env.mail;
|
||||
client = env.client;
|
||||
RequestMocks = env.RequestMocks;
|
||||
xhr = env.xhr;
|
||||
xhrOpen = sinon.spy(xhr.prototype, 'open');
|
||||
xhrSend = sinon.spy(xhr.prototype, 'send');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
xhrOpen.restore();
|
||||
xhrSend.restore();
|
||||
});
|
||||
|
||||
it('#verifyEmail', function () {
|
||||
var user = 'test3' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned');
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#verifyEmailCheckStatus', function () {
|
||||
var user = 'test4' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
var sessionToken;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(client.signIn(email, password), RequestMocks.signIn);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result.sessionToken, 'sessionToken is returned');
|
||||
sessionToken = result.sessionToken;
|
||||
|
||||
return respond(
|
||||
client.recoveryEmailStatus(sessionToken),
|
||||
RequestMocks.recoveryEmailUnverified
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.equal(result.verified, false, 'Email should not be verified.');
|
||||
|
||||
return respond(mail.wait(user, 2), RequestMocks.mailUnverifiedSignin);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned: ' + code);
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
.then(function (result) {
|
||||
return respond(
|
||||
client.recoveryEmailStatus(sessionToken),
|
||||
RequestMocks.recoveryEmailVerified
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.equal(result.verified, true, 'Email should be verified.');
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#verifyEmail with service param', function () {
|
||||
var user = 'test5' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned');
|
||||
|
||||
return respond(
|
||||
client.verifyCode(uid, code, { service: 'sync' }),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#verifyEmail with reminder param', function () {
|
||||
var user = 'test6' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned');
|
||||
|
||||
return respond(
|
||||
client.verifyCode(uid, code, { reminder: 'first' }),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#verifyEmail with style param', function () {
|
||||
var user = 'test7' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned');
|
||||
|
||||
return respond(client.verifyCode(uid, code), RequestMocks.verifyCode);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result);
|
||||
assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[2][1],
|
||||
'/recovery_email/verify_code',
|
||||
'path is correct'
|
||||
);
|
||||
}, assert.fail);
|
||||
});
|
||||
|
||||
it('#verifyEmail with newsletters param', function () {
|
||||
var user = 'test7' + new Date().getTime();
|
||||
var email = user + '@restmail.net';
|
||||
var password = 'iliketurtles';
|
||||
var uid;
|
||||
|
||||
return respond(client.signUp(email, password), RequestMocks.signUp)
|
||||
.then(function (result) {
|
||||
uid = result.uid;
|
||||
assert.ok(uid, 'uid is returned');
|
||||
|
||||
return respond(mail.wait(user), RequestMocks.mail);
|
||||
})
|
||||
.then(function (emails) {
|
||||
var code = emails[0].html.match(/code=([A-Za-z0-9]+)/)[1];
|
||||
assert.ok(code, 'code is returned');
|
||||
|
||||
return respond(
|
||||
client.verifyCode(uid, code, { newsletters: ['test-pilot'] }),
|
||||
RequestMocks.verifyCode
|
||||
);
|
||||
})
|
||||
.then(function (result) {
|
||||
assert.ok(result);
|
||||
assert.equal(xhrOpen.args[2][0], 'POST', 'method is correct');
|
||||
assert.include(
|
||||
xhrOpen.args[2][1],
|
||||
'/recovery_email/verify_code',
|
||||
'path is correct'
|
||||
);
|
||||
var sentData = JSON.parse(xhrSend.args[2][0]);
|
||||
assert.deepEqual(sentData.newsletters, ['test-pilot']);
|
||||
}, assert.fail);
|
||||
});
|
||||
});
|
|
@ -1,158 +0,0 @@
|
|||
module.exports = {
|
||||
// status code 400, errno 101: attempt to create an account that already exists
|
||||
accountExists: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 101}',
|
||||
},
|
||||
// status code 400, errno 102: attempt to access an account that does not exist
|
||||
accountDoesNotExist: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 102}',
|
||||
},
|
||||
// status code 400, errno 103: incorrect password
|
||||
accountIncorrectPassword: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 103, "message":"Incorrect password"}',
|
||||
},
|
||||
// status code 400, errno 104: attempt to operate on an unverified account
|
||||
accountUnverified: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 104}',
|
||||
},
|
||||
// status code 400, errno 105: invalid verification code
|
||||
invalidVerification: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 105}',
|
||||
},
|
||||
// status code 400, errno 106: request body was not valid json
|
||||
invalidJson: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 106}',
|
||||
},
|
||||
// status code 400, errno 107: request body contains invalid parameters
|
||||
requestInvalidParams: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 107}',
|
||||
},
|
||||
// status code 400, errno 107: request body contains invalid parameters
|
||||
requestMissingParams: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 108}',
|
||||
},
|
||||
// status code 401, errno 109: invalid request signature
|
||||
invalidRequestSignature: {
|
||||
status: 401,
|
||||
headers: {},
|
||||
body: '{"code":401, "errno": 109}',
|
||||
},
|
||||
// status code 401, errno 110: invalid authentication token
|
||||
invalidAuthToken: {
|
||||
status: 401,
|
||||
headers: {},
|
||||
body: '{"code":401, "errno": 110}',
|
||||
},
|
||||
// status code 401, errno 111: invalid authentication timestamp
|
||||
invalidAuthTimestamp: {
|
||||
status: 401,
|
||||
headers: {},
|
||||
body: '{"code":401, "errno": 111}',
|
||||
},
|
||||
// status code 411, errno 112: content-length header was not provided
|
||||
missingContentLength: {
|
||||
status: 411,
|
||||
headers: {},
|
||||
body: '{"code":411, "errno": 112}',
|
||||
},
|
||||
// status code 413, errno 113: request body too large
|
||||
requestTooLarge: {
|
||||
status: 413,
|
||||
headers: {},
|
||||
body: '{"code":413, "errno": 113}',
|
||||
},
|
||||
// status code 429, errno 114: client has sent too many requests (see backoff protocol)
|
||||
sentTooManyRequests: {
|
||||
status: 429,
|
||||
headers: {},
|
||||
body: '{"code":429, "errno": 114}',
|
||||
},
|
||||
// status code 429, errno 115: invalid authentication nonce
|
||||
invalidAuthNonce: {
|
||||
status: 401,
|
||||
headers: {},
|
||||
body: '{"code":401, "errno": 115}',
|
||||
},
|
||||
// status code 410, errno 116: endpoint is no longer supported
|
||||
endpointNotSupported: {
|
||||
status: 410,
|
||||
headers: {},
|
||||
body: '{"code":410, "errno": 116}',
|
||||
},
|
||||
// status code 400, errno 117: incorrect login method for this account
|
||||
incorrectLoginMethod: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 117}',
|
||||
},
|
||||
// status code 400, errno 118: incorrect key retrieval method for this account
|
||||
incorrectKeyMethod: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 118}',
|
||||
},
|
||||
// status code 400, errno 119: incorrect API version for this account
|
||||
incorrectAPIVersion: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 119}',
|
||||
},
|
||||
// status code 400, errno 120: incorrect email case
|
||||
incorrectEmailCase: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 120, "email": "a@b.com"}',
|
||||
},
|
||||
// status code 503, errno 201: service temporarily unavailable to due high load (see backoff protocol)
|
||||
temporarilyUnavailable: {
|
||||
status: 503,
|
||||
headers: {},
|
||||
body: '{"code":503, "errno": 201}',
|
||||
},
|
||||
// any status code, errno 999: unknown error
|
||||
unknownError: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400, "errno": 999}',
|
||||
},
|
||||
timeout: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '',
|
||||
},
|
||||
badResponseFormat: {
|
||||
status: 404,
|
||||
headers: {},
|
||||
body: '<html><body>Something is wrong.</body></html>',
|
||||
},
|
||||
signInBlocked: {
|
||||
status: 429,
|
||||
headers: {},
|
||||
body: JSON.stringify({
|
||||
code: 429,
|
||||
errno: 125,
|
||||
verificationMethod: 'email-captcha',
|
||||
verificationReason: 'login',
|
||||
}),
|
||||
},
|
||||
signInInvalidUnblockCode: {
|
||||
status: 400,
|
||||
body: '{"code":400, "errno": 127}',
|
||||
},
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
DEVICE_CALLBACK:
|
||||
'https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef',
|
||||
DEVICE_ID: '0f7aa00356e5416e82b3bef7bc409eef',
|
||||
DEVICE_NAME: 'My Phone',
|
||||
DEVICE_NAME_2: 'My Android Phone',
|
||||
DEVICE_PUBLIC_KEY:
|
||||
'BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc',
|
||||
DEVICE_AUTH_KEY: 'GSsIiaD2Mr83iPqwFNK4rw',
|
||||
DEVICE_TYPE: 'mobile',
|
||||
};
|
|
@ -1,583 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const ERRORS = require('../../client/lib/errors');
|
||||
const PushTestConstants = require('./pushConstants');
|
||||
var DEVICE_CALLBACK = PushTestConstants.DEVICE_CALLBACK;
|
||||
var DEVICE_ID = PushTestConstants.DEVICE_ID;
|
||||
var DEVICE_NAME = PushTestConstants.DEVICE_NAME;
|
||||
var DEVICE_NAME_2 = PushTestConstants.DEVICE_NAME_2;
|
||||
var DEVICE_PUBLIC_KEY = PushTestConstants.DEVICE_PUBLIC_KEY;
|
||||
var DEVICE_AUTH_KEY = PushTestConstants.DEVICE_AUTH_KEY;
|
||||
var DEVICE_TYPE = PushTestConstants.DEVICE_TYPE;
|
||||
|
||||
module.exports = {
|
||||
createOAuthCode: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body: '{}',
|
||||
},
|
||||
createOAuthToken: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body: '{}',
|
||||
},
|
||||
deleteSecurityEvents: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
getOAuthScopedKeyData: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body: '{}',
|
||||
},
|
||||
signUp: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{ "uid": "0577e7a5fbf448e3bc60dacbff5dcd5c", "sessionToken": "27cd4f4a4aa03d7d186a2ec81cbf19d5c8a604713362df9ee15c4f4a4aa03d7d"}',
|
||||
},
|
||||
signUpExistingDevice: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body: JSON.stringify({
|
||||
device: {
|
||||
id: DEVICE_ID,
|
||||
name: DEVICE_NAME,
|
||||
type: DEVICE_TYPE,
|
||||
pushCallback: DEVICE_CALLBACK,
|
||||
pushPublicKey: DEVICE_PUBLIC_KEY,
|
||||
pushAuthKey: DEVICE_AUTH_KEY,
|
||||
},
|
||||
sessionToken:
|
||||
'6544062365c5ebee16e3c5e15448139851583b5f5f7b6bd6d4a37bac41665e8a',
|
||||
uid: '9c8e5cf6915949c1b063b88fa0c53d05',
|
||||
verified: true,
|
||||
}),
|
||||
},
|
||||
signUpKeys: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{ "uid": "0577e7a5fbf448e3bc60dacbff5dcd5c", "sessionToken": "27cd4f4a4aa03d7d186a2ec81cbf19d5c8a604713362df9ee15c4f4a4aa03d7d","keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130"}',
|
||||
},
|
||||
signUpVerifyCodeEmailSent: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001","headers": {"x-verify-short-code": "123123" }}]',
|
||||
},
|
||||
signIn: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{"uid":"9c8e5cf6915949c1b063b88fa0c53d05","verified":true,"sessionToken":"6544062365c5ebee16e3c5e15448139851583b5f5f7b6bd6d4a37bac41665e8a", "emailSent": false}',
|
||||
},
|
||||
signInEmailSent: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{"uid":"9c8e5cf6915949c1b063b88fa0c53d05","verified":true,"sessionToken":"6544062365c5ebee16e3c5e15448139851583b5f5f7b6bd6d4a37bac41665e8a","emailSent":true}',
|
||||
},
|
||||
signInFailurePassword: {
|
||||
status: 400,
|
||||
headers: {},
|
||||
body: '{"code":400,"message":"Incorrect password"}',
|
||||
},
|
||||
signInWithKeys: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{"uid": "5d576e2cd3604981a8c05f6ea67fce5b", "sessionToken": "9c1fe2a0643ce23aa1b44afbe30e28d33e5726558cab215314980fc85875684f","keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130","verified": true, "emailSent": false}',
|
||||
},
|
||||
signInForceTokenVerification: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{"uid": "5d576e2cd3604981a8c05f6ea67fce5b", "sessionToken": "9c1fe2a0643ce23aa1b44afbe30e28d33e5726558cab215314980fc85875684f","keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130","verified": true, "emailSent": false}',
|
||||
},
|
||||
heartbeat: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
verifyCode: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
mail: {
|
||||
status: 200,
|
||||
body: '[{"html":"Mocked code=9001"}]',
|
||||
},
|
||||
mailUnverifiedSignin: {
|
||||
status: 200,
|
||||
body: '[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}]',
|
||||
},
|
||||
mailUnverifiedEmail: {
|
||||
status: 200,
|
||||
body: '[{"html":"Mocked code=9001", "headers":{"x-verify-code":"123123"}}]',
|
||||
},
|
||||
mailUnverifiedEmailResend: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001", "headers":{"x-verify-code":"123123"}}, {"html":"Mocked code=9001", "headers":{"x-verify-code":"123123"}}]',
|
||||
},
|
||||
mailSignUpLang: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001","headers": {"content-language": "zh-CN" }}]',
|
||||
},
|
||||
mailServiceAndRedirect: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001 service=sync redirectTo=https resume=resumejwt"}]',
|
||||
},
|
||||
resetMail: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}]',
|
||||
},
|
||||
resetMailrecoveryEmailResendCode: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}]',
|
||||
},
|
||||
resetMailpasswordForgotresetMail: {
|
||||
status: 200,
|
||||
body: '[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}]',
|
||||
},
|
||||
resetMailpasswordForgotRecoveryKey: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}]',
|
||||
},
|
||||
resetMailUnlock: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}]',
|
||||
},
|
||||
resetMailWithServiceAndRedirectNoSignup: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001 service=sync redirectTo=https resume=resumejwt"}]',
|
||||
},
|
||||
resetMailWithServiceAndRedirect: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001 service=sync redirectTo=https resume=resumejwt"}]',
|
||||
},
|
||||
resetMailResendWithServiceAndRedirect: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001 service=sync redirectTo=https"}, {"html":"Mocked code=9001 service=sync redirectTo=https resume=resumejwt"}]',
|
||||
},
|
||||
resetMailLang: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001","headers": {"content-language": "zh-CN" }}]',
|
||||
},
|
||||
recoveryEmailUnverified: {
|
||||
status: 200,
|
||||
body: '{"verified": false}',
|
||||
},
|
||||
recoveryEmailVerified: {
|
||||
status: 200,
|
||||
body: '{"verified": true}',
|
||||
},
|
||||
recoveryEmailResendCode: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
passwordForgotSendCode: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"passwordForgotToken":"e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f19103","ttl":900,"tries":3}',
|
||||
},
|
||||
passwordForgotResendCode: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"passwordForgotToken":"e838790265a45f6ee1130070d57d67d9bb20953706f73af0e34b0d4d92f19103","ttl":900,"tries":3}',
|
||||
},
|
||||
passwordForgotStatus: {
|
||||
status: 200,
|
||||
body: '{ "tries": 3, "ttl": 420 }',
|
||||
},
|
||||
passwordForgotVerifyCode: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"accountResetToken":"50a2052498d538a5d3918847751c8d5077294fd637dbf20d27f2f5f854cbcf4f"}',
|
||||
},
|
||||
passwordChangeStart: {
|
||||
status: 200,
|
||||
body:
|
||||
'{ "keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130", "passwordChangeToken": "0208a48ca4f777688a1017e98cedcc1c36ba9c4595088d28dcde5af04ae2215b", "verified": true }',
|
||||
},
|
||||
passwordChangeFinish: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
passwordChangeFinishKeys: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"uid": "5d576e2cd3604981a8c05f6ea67fce5b", "sessionToken": "9c1fe2a0643ce23aa1b44afbe30e28d33e5726558cab215314980fc85875684f","keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130","verified": true}',
|
||||
},
|
||||
accountReset: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"uid": "5d576e2cd3604981a8c05f6ea67fce5b", "sessionToken": "9c1fe2a0643ce23aa1b44afbe30e28d33e5726558cab215314980fc85875684f","keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130","verified": true}',
|
||||
},
|
||||
accountProfile: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"email": "a@a.com", "locale": "en", "authenticationMethods": ["pwd", "email"], "authenticatorAssuranceLevel": 2, "profileChangedAt": 1539002077704}',
|
||||
},
|
||||
account: {
|
||||
status: 200,
|
||||
body: '{"subscriptions":[{"foo":"bar"}]}',
|
||||
},
|
||||
securityEvents: {
|
||||
status: 200,
|
||||
body: JSON.stringify([
|
||||
{
|
||||
name: 'account.login',
|
||||
verified: true,
|
||||
createdAt: new Date().getTime() + 1,
|
||||
},
|
||||
{
|
||||
name: 'account.create',
|
||||
verified: true,
|
||||
createdAt: new Date().getTime(),
|
||||
},
|
||||
]),
|
||||
},
|
||||
securityEventsEmptyResponse: {
|
||||
status: 200,
|
||||
body: '[]',
|
||||
},
|
||||
sessionDestroy: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
sessionStatus: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
sessionVerifyCode: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
sessionVerifyCodeInvalid: {
|
||||
status: 400,
|
||||
body: '{"errno": 183}',
|
||||
},
|
||||
sessionResendVerifyCode: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
sessionResendVerifyCodeEmail: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001","headers": {"x-verify-short-code": "123123" }}]',
|
||||
},
|
||||
sessions: {
|
||||
status: 200,
|
||||
body: JSON.stringify([
|
||||
{
|
||||
id: 'device1',
|
||||
userAgent: 'agent1',
|
||||
deviceName: 'name1',
|
||||
deviceType: 'desktop',
|
||||
isDevice: false,
|
||||
lastAccessTime: 100,
|
||||
lastAccessTimeFormatted: 'a few seconds ago',
|
||||
},
|
||||
{
|
||||
id: 'device2',
|
||||
userAgent: 'agent2',
|
||||
deviceName: 'name2',
|
||||
deviceType: 'desktop',
|
||||
isDevice: false,
|
||||
lastAccessTime: 101,
|
||||
lastAccessTimeFormatted: 'a few seconds ago',
|
||||
},
|
||||
]),
|
||||
},
|
||||
attachedClients: {
|
||||
status: 200,
|
||||
body: JSON.stringify([
|
||||
{
|
||||
clientId: null,
|
||||
deviceId: 'device1',
|
||||
deviceType: 'desktop',
|
||||
isDevice: false,
|
||||
lastAccessTime: 100,
|
||||
lastAccessTimeFormatted: 'a few seconds ago',
|
||||
name: 'name1',
|
||||
sessionTokenId: 'session1',
|
||||
refreshTokenId: null,
|
||||
userAgent: 'agent1',
|
||||
},
|
||||
{
|
||||
clientId: null,
|
||||
deviceId: 'device2',
|
||||
deviceType: 'desktop',
|
||||
isDevice: false,
|
||||
lastAccessTime: 101,
|
||||
lastAccessTimeFormatted: 'a few seconds ago',
|
||||
name: 'name2',
|
||||
sessionTokenId: 'session2',
|
||||
refreshTokenId: null,
|
||||
userAgent: 'agent2',
|
||||
},
|
||||
]),
|
||||
},
|
||||
attachedClientDestroy: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
accountDestroy: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
accountKeys: {
|
||||
status: 200,
|
||||
body:
|
||||
'{ "bundle": "7f1a9633560774251a2d317b4539e04bcb14a767ec92e3b3f4d438fdad984831f6d1e1b0d93c23d312bf0859270dc8c0e6ebcae4c499f3a604881fc57683459b01cdfd04757835b0334a80728ce40cf50dce32bb365d8a0ac868bb747bf8aca4"}',
|
||||
},
|
||||
accountStatus: {
|
||||
status: 200,
|
||||
body: '{ "exists": true }',
|
||||
},
|
||||
accountStatusFalse: {
|
||||
status: 200,
|
||||
body: '{ "exists": false }',
|
||||
},
|
||||
certificateSign: {
|
||||
status: 200,
|
||||
body:
|
||||
'{ "cert": "eyJhbGciOiJEUzI1NiJ9.eyJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU3NjE1NTUwOTM3NjU1NDk2MDk4MjAyMjM2MDYyOTA3Mzg5ODMyMzI0MjUyMDY2Mzc4OTA0ODUyNDgyMjUzODg1MTA3MzQzMTY5MzI2OTEyNDkxNjY5NjQxNTQ3NzQ1OTM3NzAxNzYzMTk1NzQ3NDI1NTEyNjU5NjM2MDgwMzYzNjE3MTc1MzMzNjY5MzEyNTA2OTk1MzMyNDMiLCJlIjoiNjU1MzcifSwicHJpbmNpcGFsIjp7ImVtYWlsIjoiZm9vQGV4YW1wbGUuY29tIn0sImlhdCI6MTM3MzM5MjE4OTA5MywiZXhwIjoxMzczMzkyMjM5MDkzLCJpc3MiOiIxMjcuMC4wLjE6OTAwMCJ9.l5I6WSjsDIwCKIz_9d3juwHGlzVcvI90T2lv2maDlr8bvtMglUKFFWlN_JEzNyPBcMDrvNmu5hnhyN7vtwLu3Q" }',
|
||||
},
|
||||
getRandomBytes: {
|
||||
status: 200,
|
||||
body:
|
||||
'{ "data": "ac55c0520f2edfb026761443da0ab27b1fa18c98912af6291714e9600aa34991" }',
|
||||
},
|
||||
invalidTimestamp: {
|
||||
status: 401,
|
||||
body:
|
||||
'{ "errno": ' +
|
||||
ERRORS.INVALID_TIMESTAMP +
|
||||
', "error": "Invalid authentication timestamp", "serverTime": ' +
|
||||
new Date().getTime() +
|
||||
' }',
|
||||
},
|
||||
deviceDestroy: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
deviceList: {
|
||||
status: 200,
|
||||
body: JSON.stringify([
|
||||
{
|
||||
id: DEVICE_ID,
|
||||
name: DEVICE_NAME,
|
||||
type: DEVICE_TYPE,
|
||||
pushCallback: DEVICE_CALLBACK,
|
||||
pushPublicKey: DEVICE_PUBLIC_KEY,
|
||||
pushAuthKey: DEVICE_AUTH_KEY,
|
||||
},
|
||||
]),
|
||||
},
|
||||
deviceRegister: {
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
id: DEVICE_ID,
|
||||
name: DEVICE_NAME,
|
||||
type: DEVICE_TYPE,
|
||||
pushCallback: DEVICE_CALLBACK,
|
||||
pushPublicKey: DEVICE_PUBLIC_KEY,
|
||||
pushAuthKey: DEVICE_AUTH_KEY,
|
||||
}),
|
||||
},
|
||||
deviceUpdate: {
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
id: DEVICE_ID,
|
||||
name: DEVICE_NAME_2,
|
||||
type: DEVICE_TYPE,
|
||||
pushCallback: DEVICE_CALLBACK,
|
||||
pushPublicKey: DEVICE_PUBLIC_KEY,
|
||||
pushAuthKey: DEVICE_AUTH_KEY,
|
||||
}),
|
||||
},
|
||||
sendUnblockCode: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
unblockEmail: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001", "headers": {"x-unblock-code": "ASDF1234"}}]',
|
||||
},
|
||||
rejectUnblockCode: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
sendSmsConnectDevice: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
smsStatus: {
|
||||
status: 200,
|
||||
body: '{"country":"US","ok":true}',
|
||||
},
|
||||
consumeSigninCode: {
|
||||
status: 200,
|
||||
body: '{"email":"foo@example.org"}',
|
||||
},
|
||||
recoveryEmails: {
|
||||
status: 200,
|
||||
body: '[{"email": "a@b.com", "verified": true, "isPrimary": true}]',
|
||||
},
|
||||
recoveryEmailsUnverified: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"email": "a@b.com", "verified": true, "isPrimary": true}, {"email": "another@email.com", "verified": false, "isPrimary": false}]',
|
||||
},
|
||||
recoveryEmailsVerified: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"email": "a@b.com", "verified": true, "isPrimary": true}, {"email": "another@email.com", "verified": true, "isPrimary": false}]',
|
||||
},
|
||||
recoveryEmailsSetPrimaryVerified: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"email": "anotherEmail@email.com", "verified": true, "isPrimary": true}, {"email": "a@a.com", "verified": true, "isPrimary": false}]',
|
||||
},
|
||||
recoveryEmailCreate: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
recoveryEmailDestroy: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
recoveryEmailSetPrimaryEmail: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
signInWithVerificationMethodEmail2faResponse: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"uid": "5d576e2cd3604981a8c05f6ea67fce5b", "sessionToken": "9c1fe2a0643ce23aa1b44afbe30e28d33e5726558cab215314980fc85875684f","keyFetchToken": "b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130","verified": true, "emailSent": false, "verificationMethod": "email-2fa", "verificationReason": "login"}',
|
||||
},
|
||||
signInWithVerificationMethodEmail2faCode: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{"html":"Mocked code=9001"}, {"html":"Mocked code=9001"}, {"html":"Mocked code=9001","headers": {"x-signin-verify-code": "000111" }}]',
|
||||
},
|
||||
sessionVerifyTokenCodeSuccess: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
sessionReauth: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{"uid":"9c8e5cf6915949c1b063b88fa0c53d05","verified":true,"authAt":123456}',
|
||||
},
|
||||
sessionReauthWithKeys: {
|
||||
status: 200,
|
||||
headers: {},
|
||||
body:
|
||||
'{"uid": "5d576e2cd3604981a8c05f6ea67fce5b","keyFetchToken":"b1f4182d7e072567a1dbe682043a16932a84b7f4ca3b95e471a34806c87e4130","verified":true,"authAt":123456}',
|
||||
},
|
||||
createTotpToken: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"qrCodeUrl": "data:image/png;base64,iVBOR", "secret": "MZEE4ODKPI2UCU3DIJ3UGYSIOVWDKV3P"}',
|
||||
},
|
||||
createTotpTokenDuplicate: {
|
||||
status: 400,
|
||||
body: '{"errno": 154}',
|
||||
},
|
||||
deleteTotpToken: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
checkTotpTokenExistsFalse: {
|
||||
status: 200,
|
||||
body: '{"exists": false}',
|
||||
},
|
||||
checkTotpTokenExistsTrue: {
|
||||
status: 200,
|
||||
body: '{"exists": true}',
|
||||
},
|
||||
verifyTotpCodeTrueEnableToken: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"success": true, "recoveryCodes": ["01001112", "01001113", "01001114"]}',
|
||||
},
|
||||
verifyTotpCodeTrue: {
|
||||
status: 200,
|
||||
body: '{"success": true}',
|
||||
},
|
||||
verifyTotpCodeFalse: {
|
||||
status: 200,
|
||||
body: '{"success": false}',
|
||||
},
|
||||
consumeRecoveryCodeInvalidCode: {
|
||||
status: 400,
|
||||
body: '{"errno": 156}',
|
||||
},
|
||||
consumeRecoveryCodeSuccess: {
|
||||
status: 200,
|
||||
body: '{"remaining": 2}',
|
||||
},
|
||||
replaceRecoveryCodesSuccess: {
|
||||
status: 200,
|
||||
body: '{"recoveryCodes": ["01001112", "01001113", "01001114"]}',
|
||||
},
|
||||
replaceRecoveryCodesSuccessNew: {
|
||||
status: 200,
|
||||
body: '{"recoveryCodes": ["99999999", "01001113", "01001114"]}',
|
||||
},
|
||||
createRecoveryKey: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
getRecoveryKey: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"recoveryData": "eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIiwia2lkIjoiODE4NDIwZjBkYTU4ZDIwZjZhZTRkMmM5YmVhYjkyNTEifQ..D29EXHp8ubLvftaZ.xHJd2Nl2Uco2RyywYPLkUU7fHpgO2FztY12Zjpq1ffiyLRIUcQVfmiNC6aMiHBl7Hp-lXEbb5mR1uXHrTH9iRXEBVaAfyf9KEAWOukWGVSH8EaOkr7cfu2Yr0K93Ec8glsssjiKp8NGB8VKTUJ-lmBv2cIrG68V4eTUVDoDhMbXhrF-Mv4JNeh338pPeatTnyg.Ow2bhEYWxzxfSPMxVwKmSA"}',
|
||||
},
|
||||
deleteRecoveryKey: {
|
||||
status: 200,
|
||||
body: '{}',
|
||||
},
|
||||
recoveryKeyExistsFalse: {
|
||||
status: 200,
|
||||
body: '{"exists": false}',
|
||||
},
|
||||
recoveryKeyExistsTrue: {
|
||||
status: 200,
|
||||
body: '{"exists": true}',
|
||||
},
|
||||
getSubscriptionPlans: {
|
||||
status: 200,
|
||||
body:
|
||||
'[{ "plan_id": "123doneProMonthly", "product_id": "123doneProProduct", "product_name": "123done Pro", "interval": "month", "amount": 50, "currency": "usd" }]',
|
||||
},
|
||||
getActiveSubscriptions: {
|
||||
status: 200,
|
||||
body: '[{"subscriptionId": 9},{"subscriptionId": 12}]',
|
||||
},
|
||||
createSupportTicket: {
|
||||
status: 200,
|
||||
body: '{"success": true, "ticket": "abc123xyz"}',
|
||||
},
|
||||
verifyIdToken: {
|
||||
status: 200,
|
||||
body:
|
||||
'{"aud": "59cceb6f8c32317c", "iss": "http://127.0.0.1:3030", "alg": "RS256", "exp": 1587024184, "iat": 1587023184, "sub": "0577e7a5fbf448e3bc60dacbff5dcd5c"}',
|
||||
},
|
||||
};
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче