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:
Danny Coates 2020-07-20 16:30:44 -07:00
Родитель 96161f3775
Коммит 7358a3cae5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4C442633C62E00CB
103 изменённых файлов: 303 добавлений и 11235 удалений

Просмотреть файл

@ -116,7 +116,7 @@ jobs:
'123done' \
'browserid-verifier' \
'fortress' \
'fxa-js-client' \
'fxa-auth-client' \
'fxa-geodb' \
'fxa-email-event-proxy' \
'fxa-customs-server' \

5
.gitignore поставляемый
Просмотреть файл

@ -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 Contributors 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 partys
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
partys 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 partys 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"}',
},
};

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше