From 7358a3cae5ffd4b2df2068a9ed8d82259b5ab720 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Mon, 20 Jul 2020 16:30:44 -0700 Subject: [PATCH] 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 --- .circleci/config.yml | 2 +- .gitignore | 5 - README.md | 2 +- _scripts/base-docker.sh | 2 +- package.json | 4 +- packages/fxa-auth-client/browser.ts | 2 + .../auth => fxa-auth-client/lib}/client.ts | 7 +- .../auth => fxa-auth-client/lib}/crypto.ts | 4 +- .../lib/auth => fxa-auth-client/lib}/hawk.ts | 6 +- .../lib/auth => fxa-auth-client/lib}/utils.ts | 0 packages/fxa-auth-client/package.json | 35 + packages/fxa-auth-client/server.ts | 23 + .../test/crypto.ts} | 7 +- .../hawk.js => fxa-auth-client/test/hawk.ts} | 9 +- .../fxa-auth-client/tsconfig.browser.json | 10 + packages/fxa-auth-client/tsconfig.json | 10 + packages/fxa-auth-server/README.md | 2 +- packages/fxa-auth-server/docs/api.md | 2 +- .../app/scripts/lib/fxa-client.js | 12 +- .../app/tests/spec/lib/fxa-client.js | 26 +- .../app/tests/test_start.js | 2 - .../fxa-content-server/docs/architecture.md | 6 +- packages/fxa-content-server/package.json | 1 + .../tests/functional/lib/helpers.js | 9 +- .../fxa-content-server/tests/teamcity/run.sh | 2 +- packages/fxa-content-server/webpack.config.js | 3 +- packages/fxa-graphql-api/package.json | 2 +- packages/fxa-graphql-api/src/bin/main.ts | 4 +- packages/fxa-graphql-api/src/lib/auth.ts | 4 +- packages/fxa-graphql-api/src/lib/constants.ts | 4 +- .../src/lib/datasources/authServer.ts | 10 +- .../src/lib/datasources/profileServer.ts | 4 +- .../resolvers/types/input/metrics-context.ts | 10 +- packages/fxa-graphql-api/tsconfig.json | 2 +- packages/fxa-js-client/.eslintrc | 8 - packages/fxa-js-client/.jscsrc | 20 - packages/fxa-js-client/.nsprc | 8 - packages/fxa-js-client/.prettierignore | 4 - packages/fxa-js-client/AUTHORS | 6 - packages/fxa-js-client/CHANGELOG.md | 87 - packages/fxa-js-client/CONTRIBUTING.md | 101 - packages/fxa-js-client/Gruntfile.js | 28 - packages/fxa-js-client/LICENSE | 354 -- packages/fxa-js-client/README.md | 20 - .../fxa-js-client/client/FxAccountClient.js | 2850 ----------------- .../fxa-js-client/client/lib/credentials.js | 162 - packages/fxa-js-client/client/lib/errors.js | 8 - packages/fxa-js-client/client/lib/hawk.js | 586 ---- .../client/lib/hawkCredentials.js | 39 - packages/fxa-js-client/client/lib/hkdf.js | 56 - .../client/lib/metricsContext.js | 25 - packages/fxa-js-client/client/lib/pbkdf2.js | 26 - packages/fxa-js-client/client/lib/request.js | 140 - packages/fxa-js-client/node/index.js | 28 - packages/fxa-js-client/package.json | 59 - packages/fxa-js-client/tasks/clean.js | 11 - packages/fxa-js-client/tasks/copyright.js | 22 - packages/fxa-js-client/tasks/eslint.js | 22 - packages/fxa-js-client/tasks/open.js | 13 - packages/fxa-js-client/tasks/watch.js | 24 - packages/fxa-js-client/tasks/webpack.js | 15 - packages/fxa-js-client/tests/.eslintrc | 7 - .../tests/addons/accountHelper.js | 135 - .../fxa-js-client/tests/addons/environment.js | 80 - .../fxa-js-client/tests/addons/restmail.js | 39 - .../fxa-js-client/tests/examples/example.html | 163 - .../fxa-js-client/tests/examples/proxy.js | 38 - packages/fxa-js-client/tests/lib/account.js | 608 ---- .../tests/lib/attachedClients.js | 97 - .../tests/lib/certificateSign.js | 77 - .../fxa-js-client/tests/lib/credentials.js | 55 - packages/fxa-js-client/tests/lib/device.js | 165 - packages/fxa-js-client/tests/lib/emails.js | 316 -- packages/fxa-js-client/tests/lib/errors.js | 68 - .../tests/lib/hawkCredentials.js | 32 - .../fxa-js-client/tests/lib/headerLang.js | 103 - packages/fxa-js-client/tests/lib/hkdf.js | 75 - packages/fxa-js-client/tests/lib/init.js | 23 - .../fxa-js-client/tests/lib/metricsContext.js | 54 - packages/fxa-js-client/tests/lib/misc.js | 52 - packages/fxa-js-client/tests/lib/oauth.js | 242 -- .../fxa-js-client/tests/lib/passwordChange.js | 396 --- .../fxa-js-client/tests/lib/recoveryCodes.js | 129 - .../fxa-js-client/tests/lib/recoveryEmail.js | 97 - .../fxa-js-client/tests/lib/recoveryKeys.js | 290 -- packages/fxa-js-client/tests/lib/request.js | 82 - .../fxa-js-client/tests/lib/securityEvents.js | 85 - packages/fxa-js-client/tests/lib/session.js | 473 --- packages/fxa-js-client/tests/lib/signIn.js | 246 -- packages/fxa-js-client/tests/lib/signUp.js | 308 -- .../fxa-js-client/tests/lib/signinCodes.js | 107 - packages/fxa-js-client/tests/lib/sms.js | 70 - .../fxa-js-client/tests/lib/subscriptions.js | 141 - packages/fxa-js-client/tests/lib/totp.js | 183 -- packages/fxa-js-client/tests/lib/unbundle.js | 88 - .../fxa-js-client/tests/lib/uriVersion.js | 22 - .../fxa-js-client/tests/lib/verifyCode.js | 222 -- packages/fxa-js-client/tests/mocks/errors.js | 158 - .../tests/mocks/pushConstants.js | 17 - packages/fxa-js-client/tests/mocks/request.js | 583 ---- packages/fxa-js-client/webpack.config.js | 43 - types/fxa-js-client/index.d.ts | 79 - yarn.lock | 335 +- 103 files changed, 303 insertions(+), 11235 deletions(-) create mode 100644 packages/fxa-auth-client/browser.ts rename packages/{fxa-content-server/app/scripts/lib/auth => fxa-auth-client/lib}/client.ts (99%) rename packages/{fxa-content-server/app/scripts/lib/auth => fxa-auth-client/lib}/crypto.ts (97%) rename packages/{fxa-content-server/app/scripts/lib/auth => fxa-auth-client/lib}/hawk.ts (96%) rename packages/{fxa-content-server/app/scripts/lib/auth => fxa-auth-client/lib}/utils.ts (100%) create mode 100644 packages/fxa-auth-client/package.json create mode 100644 packages/fxa-auth-client/server.ts rename packages/{fxa-content-server/app/tests/spec/lib/auth/crypto.js => fxa-auth-client/test/crypto.ts} (86%) rename packages/{fxa-content-server/app/tests/spec/lib/auth/hawk.js => fxa-auth-client/test/hawk.ts} (88%) create mode 100644 packages/fxa-auth-client/tsconfig.browser.json create mode 100644 packages/fxa-auth-client/tsconfig.json delete mode 100644 packages/fxa-js-client/.eslintrc delete mode 100644 packages/fxa-js-client/.jscsrc delete mode 100644 packages/fxa-js-client/.nsprc delete mode 100644 packages/fxa-js-client/.prettierignore delete mode 100644 packages/fxa-js-client/AUTHORS delete mode 100644 packages/fxa-js-client/CHANGELOG.md delete mode 100644 packages/fxa-js-client/CONTRIBUTING.md delete mode 100644 packages/fxa-js-client/Gruntfile.js delete mode 100644 packages/fxa-js-client/LICENSE delete mode 100644 packages/fxa-js-client/README.md delete mode 100644 packages/fxa-js-client/client/FxAccountClient.js delete mode 100644 packages/fxa-js-client/client/lib/credentials.js delete mode 100644 packages/fxa-js-client/client/lib/errors.js delete mode 100644 packages/fxa-js-client/client/lib/hawk.js delete mode 100644 packages/fxa-js-client/client/lib/hawkCredentials.js delete mode 100644 packages/fxa-js-client/client/lib/hkdf.js delete mode 100644 packages/fxa-js-client/client/lib/metricsContext.js delete mode 100644 packages/fxa-js-client/client/lib/pbkdf2.js delete mode 100644 packages/fxa-js-client/client/lib/request.js delete mode 100644 packages/fxa-js-client/node/index.js delete mode 100644 packages/fxa-js-client/package.json delete mode 100644 packages/fxa-js-client/tasks/clean.js delete mode 100644 packages/fxa-js-client/tasks/copyright.js delete mode 100644 packages/fxa-js-client/tasks/eslint.js delete mode 100644 packages/fxa-js-client/tasks/open.js delete mode 100644 packages/fxa-js-client/tasks/watch.js delete mode 100644 packages/fxa-js-client/tasks/webpack.js delete mode 100644 packages/fxa-js-client/tests/.eslintrc delete mode 100644 packages/fxa-js-client/tests/addons/accountHelper.js delete mode 100644 packages/fxa-js-client/tests/addons/environment.js delete mode 100644 packages/fxa-js-client/tests/addons/restmail.js delete mode 100644 packages/fxa-js-client/tests/examples/example.html delete mode 100644 packages/fxa-js-client/tests/examples/proxy.js delete mode 100644 packages/fxa-js-client/tests/lib/account.js delete mode 100644 packages/fxa-js-client/tests/lib/attachedClients.js delete mode 100644 packages/fxa-js-client/tests/lib/certificateSign.js delete mode 100644 packages/fxa-js-client/tests/lib/credentials.js delete mode 100644 packages/fxa-js-client/tests/lib/device.js delete mode 100644 packages/fxa-js-client/tests/lib/emails.js delete mode 100644 packages/fxa-js-client/tests/lib/errors.js delete mode 100644 packages/fxa-js-client/tests/lib/hawkCredentials.js delete mode 100644 packages/fxa-js-client/tests/lib/headerLang.js delete mode 100644 packages/fxa-js-client/tests/lib/hkdf.js delete mode 100644 packages/fxa-js-client/tests/lib/init.js delete mode 100644 packages/fxa-js-client/tests/lib/metricsContext.js delete mode 100644 packages/fxa-js-client/tests/lib/misc.js delete mode 100644 packages/fxa-js-client/tests/lib/oauth.js delete mode 100644 packages/fxa-js-client/tests/lib/passwordChange.js delete mode 100644 packages/fxa-js-client/tests/lib/recoveryCodes.js delete mode 100644 packages/fxa-js-client/tests/lib/recoveryEmail.js delete mode 100644 packages/fxa-js-client/tests/lib/recoveryKeys.js delete mode 100644 packages/fxa-js-client/tests/lib/request.js delete mode 100644 packages/fxa-js-client/tests/lib/securityEvents.js delete mode 100644 packages/fxa-js-client/tests/lib/session.js delete mode 100644 packages/fxa-js-client/tests/lib/signIn.js delete mode 100644 packages/fxa-js-client/tests/lib/signUp.js delete mode 100644 packages/fxa-js-client/tests/lib/signinCodes.js delete mode 100644 packages/fxa-js-client/tests/lib/sms.js delete mode 100644 packages/fxa-js-client/tests/lib/subscriptions.js delete mode 100644 packages/fxa-js-client/tests/lib/totp.js delete mode 100644 packages/fxa-js-client/tests/lib/unbundle.js delete mode 100644 packages/fxa-js-client/tests/lib/uriVersion.js delete mode 100644 packages/fxa-js-client/tests/lib/verifyCode.js delete mode 100644 packages/fxa-js-client/tests/mocks/errors.js delete mode 100644 packages/fxa-js-client/tests/mocks/pushConstants.js delete mode 100644 packages/fxa-js-client/tests/mocks/request.js delete mode 100644 packages/fxa-js-client/webpack.config.js delete mode 100644 types/fxa-js-client/index.d.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d16538127..911d737a38 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,7 +116,7 @@ jobs: '123done' \ 'browserid-verifier' \ 'fortress' \ - 'fxa-js-client' \ + 'fxa-auth-client' \ 'fxa-geodb' \ 'fxa-email-event-proxy' \ 'fxa-customs-server' \ diff --git a/.gitignore b/.gitignore index 971ba61e4b..83fab058b2 100644 --- a/.gitignore +++ b/.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 diff --git a/README.md b/README.md index 147cbd4d3b..2875c5ba42 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/_scripts/base-docker.sh b/_scripts/base-docker.sh index cbefbf7e51..91897d1e2b 100755 --- a/_scripts/base-docker.sh +++ b/_scripts/base-docker.sh @@ -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 \ diff --git a/package.json b/package.json index a7855dfe19..102aafc5ad 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/packages/fxa-auth-client/browser.ts b/packages/fxa-auth-client/browser.ts new file mode 100644 index 0000000000..49689a8a07 --- /dev/null +++ b/packages/fxa-auth-client/browser.ts @@ -0,0 +1,2 @@ +import AuthClient from './lib/client'; +export default AuthClient; diff --git a/packages/fxa-content-server/app/scripts/lib/auth/client.ts b/packages/fxa-auth-client/lib/client.ts similarity index 99% rename from packages/fxa-content-server/app/scripts/lib/auth/client.ts rename to packages/fxa-auth-client/lib/client.ts index c3e07f9f4a..12e2e29a6a 100644 --- a/packages/fxa-content-server/app/scripts/lib/auth/client.ts +++ b/packages/fxa-auth-client/lib/client.ts @@ -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 }); } diff --git a/packages/fxa-content-server/app/scripts/lib/auth/crypto.ts b/packages/fxa-auth-client/lib/crypto.ts similarity index 97% rename from packages/fxa-content-server/app/scripts/lib/auth/crypto.ts rename to packages/fxa-auth-client/lib/crypto.ts index 1f607018ec..5bfa262de3 100644 --- a/packages/fxa-content-server/app/scripts/lib/auth/crypto.ts +++ b/packages/fxa-auth-client/lib/crypto.ts @@ -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, diff --git a/packages/fxa-content-server/app/scripts/lib/auth/hawk.ts b/packages/fxa-auth-client/lib/hawk.ts similarity index 96% rename from packages/fxa-content-server/app/scripts/lib/auth/hawk.ts rename to packages/fxa-auth-client/lib/hawk.ts index 55c08841ce..8b41b64e41 100644 --- a/packages/fxa-content-server/app/scripts/lib/auth/hawk.ts +++ b/packages/fxa-auth-client/lib/hawk.ts @@ -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), diff --git a/packages/fxa-content-server/app/scripts/lib/auth/utils.ts b/packages/fxa-auth-client/lib/utils.ts similarity index 100% rename from packages/fxa-content-server/app/scripts/lib/auth/utils.ts rename to packages/fxa-auth-client/lib/utils.ts diff --git a/packages/fxa-auth-client/package.json b/packages/fxa-auth-client/package.json new file mode 100644 index 0000000000..f66aaff0ca --- /dev/null +++ b/packages/fxa-auth-client/package.json @@ -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" + } +} diff --git a/packages/fxa-auth-client/server.ts b/packages/fxa-auth-client/server.ts new file mode 100644 index 0000000000..fb4b112a8e --- /dev/null +++ b/packages/fxa-auth-client/server.ts @@ -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'; diff --git a/packages/fxa-content-server/app/tests/spec/lib/auth/crypto.js b/packages/fxa-auth-client/test/crypto.ts similarity index 86% rename from packages/fxa-content-server/app/tests/spec/lib/auth/crypto.js rename to packages/fxa-auth-client/test/crypto.ts index 765c9e24a2..ebf4ad2079 100644 --- a/packages/fxa-content-server/app/tests/spec/lib/auth/crypto.js +++ b/packages/fxa-auth-client/test/crypto.ts @@ -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'; diff --git a/packages/fxa-content-server/app/tests/spec/lib/auth/hawk.js b/packages/fxa-auth-client/test/hawk.ts similarity index 88% rename from packages/fxa-content-server/app/tests/spec/lib/auth/hawk.js rename to packages/fxa-auth-client/test/hawk.ts index 7b12928f22..19545e2156 100644 --- a/packages/fxa-content-server/app/tests/spec/lib/auth/hawk.js +++ b/packages/fxa-auth-client/test/hawk.ts @@ -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 = { diff --git a/packages/fxa-auth-client/tsconfig.browser.json b/packages/fxa-auth-client/tsconfig.browser.json new file mode 100644 index 0000000000..55e10abfae --- /dev/null +++ b/packages/fxa-auth-client/tsconfig.browser.json @@ -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"] +} diff --git a/packages/fxa-auth-client/tsconfig.json b/packages/fxa-auth-client/tsconfig.json new file mode 100644 index 0000000000..59a96b44bf --- /dev/null +++ b/packages/fxa-auth-client/tsconfig.json @@ -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"] +} diff --git a/packages/fxa-auth-server/README.md b/packages/fxa-auth-server/README.md index a58d61ccfb..392f4ffefc 100644 --- a/packages/fxa-auth-server/README.md +++ b/packages/fxa-auth-server/README.md @@ -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 diff --git a/packages/fxa-auth-server/docs/api.md b/packages/fxa-auth-server/docs/api.md index c5c9bc3f86..56120701b3 100644 --- a/packages/fxa-auth-server/docs/api.md +++ b/packages/fxa-auth-server/docs/api.md @@ -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). diff --git a/packages/fxa-content-server/app/scripts/lib/fxa-client.js b/packages/fxa-content-server/app/scripts/lib/fxa-client.js index 57dbb2c40d..e1bcb59e4d 100644 --- a/packages/fxa-content-server/app/scripts/lib/fxa-client.js +++ b/packages/fxa-content-server/app/scripts/lib/fxa-client.js @@ -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. diff --git a/packages/fxa-content-server/app/tests/spec/lib/fxa-client.js b/packages/fxa-content-server/app/tests/spec/lib/fxa-client.js index ac38f1c345..70ba7d21d9 100644 --- a/packages/fxa-content-server/app/tests/spec/lib/fxa-client.js +++ b/packages/fxa-content-server/app/tests/spec/lib/fxa-client.js @@ -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(); diff --git a/packages/fxa-content-server/app/tests/test_start.js b/packages/fxa-content-server/app/tests/test_start.js index 497abe96f5..5f9f8b556a 100644 --- a/packages/fxa-content-server/app/tests/test_start.js +++ b/packages/fxa-content-server/app/tests/test_start.js @@ -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'); diff --git a/packages/fxa-content-server/docs/architecture.md b/packages/fxa-content-server/docs/architecture.md index 3b1c1743be..8757250b3b 100644 --- a/packages/fxa-content-server/docs/architecture.md +++ b/packages/fxa-content-server/docs/architecture.md @@ -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) diff --git a/packages/fxa-content-server/package.json b/packages/fxa-content-server/package.json index 44dfaad50d..b7760857f0 100644 --- a/packages/fxa-content-server/package.json +++ b/packages/fxa-content-server/package.json @@ -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:*", diff --git a/packages/fxa-content-server/tests/functional/lib/helpers.js b/packages/fxa-content-server/tests/functional/lib/helpers.js index 2577b5c91a..445667cdf1 100644 --- a/packages/fxa-content-server/tests/functional/lib/helpers.js +++ b/packages/fxa-content-server/tests/functional/lib/helpers.js @@ -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); } /** diff --git a/packages/fxa-content-server/tests/teamcity/run.sh b/packages/fxa-content-server/tests/teamcity/run.sh index d779eea4c7..4210fa0865 100755 --- a/packages/fxa-content-server/tests/teamcity/run.sh +++ b/packages/fxa-content-server/tests/teamcity/run.sh @@ -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 diff --git a/packages/fxa-content-server/webpack.config.js b/packages/fxa-content-server/webpack.config.js index b0bbb33082..d988df883d 100644 --- a/packages/fxa-content-server/webpack.config.js +++ b/packages/fxa-content-server/webpack.config.js @@ -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') }, }, diff --git a/packages/fxa-graphql-api/package.json b/packages/fxa-graphql-api/package.json index f2f4a183cf..16357c9631 100644 --- a/packages/fxa-graphql-api/package.json +++ b/packages/fxa-graphql-api/package.json @@ -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", diff --git a/packages/fxa-graphql-api/src/bin/main.ts b/packages/fxa-graphql-api/src/bin/main.ts index fbec9c5517..9d756b28b6 100644 --- a/packages/fxa-graphql-api/src/bin/main.ts +++ b/packages/fxa-graphql-api/src/bin/main.ts @@ -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 diff --git a/packages/fxa-graphql-api/src/lib/auth.ts b/packages/fxa-graphql-api/src/lib/auth.ts index 63b8a326c0..261901e8a2 100644 --- a/packages/fxa-graphql-api/src/lib/auth.ts +++ b/packages/fxa-graphql-api/src/lib/auth.ts @@ -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 { try { diff --git a/packages/fxa-graphql-api/src/lib/constants.ts b/packages/fxa-graphql-api/src/lib/constants.ts index 22b0a5930a..404ef08e84 100644 --- a/packages/fxa-graphql-api/src/lib/constants.ts +++ b/packages/fxa-graphql-api/src/lib/constants.ts @@ -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(); export const loggerContainerToken = new Token(); -export const fxAccountClientToken = new Token(); +export const fxAccountClientToken = new Token(); export const authRedisConfig = new Token(); diff --git a/packages/fxa-graphql-api/src/lib/datasources/authServer.ts b/packages/fxa-graphql-api/src/lib/datasources/authServer.ts index 8aae13331d..fbff0b41a4 100644 --- a/packages/fxa-graphql-api/src/lib/datasources/authServer.ts +++ b/packages/fxa-graphql-api/src/lib/datasources/authServer.ts @@ -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 { - return this.authClient.createTotpToken(this.token, options); + options: MetricsContext + ): ReturnType { + return this.authClient.createTotpToken(this.token, { metricsContext: options }); } public destroyTotpToken(): Promise { diff --git a/packages/fxa-graphql-api/src/lib/datasources/profileServer.ts b/packages/fxa-graphql-api/src/lib/datasources/profileServer.ts index 02b5729eac..1b729ba091 100644 --- a/packages/fxa-graphql-api/src/lib/datasources/profileServer.ts +++ b/packages/fxa-graphql-api/src/lib/datasources/profileServer.ts @@ -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; diff --git a/packages/fxa-graphql-api/src/lib/resolvers/types/input/metrics-context.ts b/packages/fxa-graphql-api/src/lib/resolvers/types/input/metrics-context.ts index 1bf2d40517..8103f27a15 100644 --- a/packages/fxa-graphql-api/src/lib/resolvers/types/input/metrics-context.ts +++ b/packages/fxa-graphql-api/src/lib/resolvers/types/input/metrics-context.ts @@ -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; } diff --git a/packages/fxa-graphql-api/tsconfig.json b/packages/fxa-graphql-api/tsconfig.json index c1c548b970..0a1ecd928e 100644 --- a/packages/fxa-graphql-api/tsconfig.json +++ b/packages/fxa-graphql-api/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "noEmitHelpers": true, "importHelpers": true, - "types": ["fxa-js-client", "mocha", "mozlog", "node"] + "types": ["mocha", "mozlog", "node"] }, "include": ["./src"] } diff --git a/packages/fxa-js-client/.eslintrc b/packages/fxa-js-client/.eslintrc deleted file mode 100644 index 1059147963..0000000000 --- a/packages/fxa-js-client/.eslintrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["plugin:fxa/client"], - "plugins": ["fxa"], - "rules": { - "strict": "off", - "space-unary-ops": [2, {"words": false}] - } -} diff --git a/packages/fxa-js-client/.jscsrc b/packages/fxa-js-client/.jscsrc deleted file mode 100644 index f47dff4c1c..0000000000 --- a/packages/fxa-js-client/.jscsrc +++ /dev/null @@ -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 - } -} diff --git a/packages/fxa-js-client/.nsprc b/packages/fxa-js-client/.nsprc deleted file mode 100644 index 7ec61dbc11..0000000000 --- a/packages/fxa-js-client/.nsprc +++ /dev/null @@ -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" - ] -} diff --git a/packages/fxa-js-client/.prettierignore b/packages/fxa-js-client/.prettierignore deleted file mode 100644 index ee864e0569..0000000000 --- a/packages/fxa-js-client/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -AUTHORS -LICENSE -.* -*.sh \ No newline at end of file diff --git a/packages/fxa-js-client/AUTHORS b/packages/fxa-js-client/AUTHORS deleted file mode 100644 index a8a84cd992..0000000000 --- a/packages/fxa-js-client/AUTHORS +++ /dev/null @@ -1,6 +0,0 @@ -Vlad Filippov -Zach Carter -Peter deHaan -Glen Mailer -Brian Warner -Shane Tomlinson diff --git a/packages/fxa-js-client/CHANGELOG.md b/packages/fxa-js-client/CHANGELOG.md deleted file mode 100644 index bedcd616e7..0000000000 --- a/packages/fxa-js-client/CHANGELOG.md +++ /dev/null @@ -1,87 +0,0 @@ - - -### 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)) - - - -### 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)) - - - -### 0.1.28 (2015-02-12) - -#### Features - -- **client:** Add account unlock functionality. ([2f8e642c](https://github.com/mozilla/fxa-js-client/commit/2f8e642c3600e29fedd3913b60e417f376593754)) - - - -### 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)) - - - -### 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)) - - - -### 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)) - - - -### 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)) - - - -### 0.1.23 (2014-06-12) - - - -### 0.1.22 (2014-06-11) - - - -### 0.1.20 (2014-05-16) - -#### Bug Fixes - -- **xhr:** make the default payload null ([83666223](https://github.com/mozilla/fxa-js-client/commit/83666223b6fdf4c6993bb4fefce9f0d63c6b38d4)) diff --git a/packages/fxa-js-client/CONTRIBUTING.md b/packages/fxa-js-client/CONTRIBUTING.md deleted file mode 100644 index 8c785bb42a..0000000000 --- a/packages/fxa-js-client/CONTRIBUTING.md +++ /dev/null @@ -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: -- 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: . - -## 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 `(): ` 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/). diff --git a/packages/fxa-js-client/Gruntfile.js b/packages/fxa-js-client/Gruntfile.js deleted file mode 100644 index a4c835f256..0000000000 --- a/packages/fxa-js-client/Gruntfile.js +++ /dev/null @@ -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']); -}; diff --git a/packages/fxa-js-client/LICENSE b/packages/fxa-js-client/LICENSE deleted file mode 100644 index c33dcc7c92..0000000000 --- a/packages/fxa-js-client/LICENSE +++ /dev/null @@ -1,354 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. “Contributor” - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. “Contributor Version” - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor’s Contribution. - -1.3. “Contribution” - - means Covered Software of a particular Contributor. - -1.4. “Covered Software” - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. “Incompatible With Secondary Licenses” - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of version - 1.1 or earlier of the License, but not also under the terms of a - Secondary License. - -1.6. “Executable Form” - - means any form of the work other than Source Code Form. - -1.7. “Larger Work” - - means a work that combines Covered Software with other material, in a separate - file or files, that is not Covered Software. - -1.8. “License” - - means this document. - -1.9. “Licensable” - - means having the right to grant, to the maximum extent possible, whether at the - time of the initial grant or subsequently, any and all of the rights conveyed by - this License. - -1.10. “Modifications” - - means any of the following: - - a. any file in Source Code Form that results from an addition to, deletion - from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. “Patent Claims” of a Contributor - - means any patent claim(s), including without limitation, method, process, - and apparatus claims, in any patent Licensable by such Contributor that - would be infringed, but for the grant of the License, by the making, - using, selling, offering for sale, having made, import, or transfer of - either its Contributions or its Contributor Version. - -1.12. “Secondary License” - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. “Source Code Form” - - means the form of the work preferred for making modifications. - -1.14. “You” (or “Your”) - - means an individual or a legal entity exercising rights under this - License. For legal entities, “You” includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, “control” means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or as - part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its Contributions - or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution become - effective for each Contribution on the date the Contributor first distributes - such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under this - License. No additional rights or licenses will be implied from the distribution - or licensing of Covered Software under this License. Notwithstanding Section - 2.1(b) above, no patent license is granted by a Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party’s - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of its - Contributions. - - This License does not grant any rights in the trademarks, service marks, or - logos of any Contributor (except as may be necessary to comply with the - notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this License - (see Section 10.2) or under the terms of a Secondary License (if permitted - under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its Contributions - are its original creation(s) or it has sufficient rights to grant the - rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under applicable - copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under the - terms of this License. You must inform recipients that the Source Code Form - of the Covered Software is governed by the terms of this License, and how - they can obtain a copy of this License. You may not attempt to alter or - restrict the recipients’ rights in the Source Code Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this License, - or sublicense it under different terms, provided that the license for - the Executable Form does not attempt to limit or alter the recipients’ - rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for the - Covered Software. If the Larger Work is a combination of Covered Software - with a work governed by one or more Secondary Licenses, and the Covered - Software is not Incompatible With Secondary Licenses, this License permits - You to additionally distribute such Covered Software under the terms of - such Secondary License(s), so that the recipient of the Larger Work may, at - their option, further distribute the Covered Software under the terms of - either this License or such Secondary License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices (including - copyright notices, patent notices, disclaimers of warranty, or limitations - of liability) contained within the Source Code Form of the Covered - Software, except that You may alter any license notices to the extent - required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on behalf - of any Contributor. You must make it absolutely clear that any such - warranty, support, indemnity, or liability obligation is offered by You - alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, judicial - order, or regulation then You must: (a) comply with the terms of this License - to the maximum extent possible; and (b) describe the limitations and the code - they affect. Such description must be placed in a text file included with all - distributions of the Covered Software under this License. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing basis, - if such Contributor fails to notify You of the non-compliance by some - reasonable means prior to 60 days after You have come back into compliance. - Moreover, Your grants from a particular Contributor are reinstated on an - ongoing basis if such Contributor notifies You of the non-compliance by - some reasonable means, this is the first time You have received notice of - non-compliance with this License from such Contributor, and You become - compliant prior to 30 days after Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, counter-claims, - and cross-claims) alleging that a Contributor Version directly or - indirectly infringes any patent, then the rights granted to You by any and - all Contributors for the Covered Software under Section 2.1 of this License - shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an “as is” basis, without - warranty of any kind, either expressed, implied, or statutory, including, - without limitation, warranties that the Covered Software is free of defects, - merchantable, fit for a particular purpose or non-infringing. The entire - risk as to the quality and performance of the Covered Software is with You. - Should any Covered Software prove defective in any respect, You (not any - Contributor) assume the cost of any necessary servicing, repair, or - correction. This disclaimer of warranty constitutes an essential part of this - License. No use of any Covered Software is authorized under this License - except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from such - party’s negligence to the extent applicable law prohibits such limitation. - Some jurisdictions do not allow the exclusion or limitation of incidental or - consequential damages, so this exclusion and limitation may not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts of - a jurisdiction where the defendant maintains its principal place of business - and such litigation shall be governed by laws of that jurisdiction, without - reference to its conflict-of-law provisions. Nothing in this Section shall - prevent a party’s ability to bring cross-claims or counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. Any law or regulation which provides that the language of a - contract shall be construed against the drafter shall not be used to construe - this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version of - the License under which You originally received the Covered Software, or - under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a modified - version of this License if you rename the license and remove any - references to the name of the license steward (except to note that such - modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, then -You may include the notice in a location (such as a LICENSE file in a relevant -directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - “Incompatible With Secondary Licenses” Notice - - This Source Code Form is “Incompatible - With Secondary Licenses”, as defined by - the Mozilla Public License, v. 2.0. - diff --git a/packages/fxa-js-client/README.md b/packages/fxa-js-client/README.md deleted file mode 100644 index 64e53417ec..0000000000 --- a/packages/fxa-js-client/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# fxa-js-client - -> Web client that talks to the Firefox Accounts API server - -## Usage - -``` - -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. diff --git a/packages/fxa-js-client/client/FxAccountClient.js b/packages/fxa-js-client/client/FxAccountClient.js deleted file mode 100644 index 10556c82ae..0000000000 --- a/packages/fxa-js-client/client/FxAccountClient.js +++ /dev/null @@ -1,2850 +0,0 @@ -/* eslint-disable camelcase,id-blacklist,strict */ -/* 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 credentials = require('./lib/credentials'); -const ERRORS = require('./lib/errors'); -const hawkCredentials = require('./lib/hawkCredentials'); -const metricsContext = require('./lib/metricsContext'); -const Request = require('./lib/request'); - -var VERSION = 'v1'; -var uriVersionRegExp = new RegExp('/' + VERSION + '$'); -var HKDF_SIZE = 2 * 32; - -function isUndefined(val) { - return typeof val === 'undefined'; -} - -function isNull(val) { - return val === null; -} - -function isEmptyObject(val) { - return ( - Object.prototype.toString.call(val) === '[object Object]' && - !Object.keys(val).length - ); -} - -function isEmptyString(val) { - return val === ''; -} - -function required(val, name) { - if ( - isUndefined(val) || - isNull(val) || - isEmptyObject(val) || - isEmptyString(val) - ) { - throw new Error('Missing ' + name); - } -} - -/** - * @class FxAccountClient - * @constructor - * @param {String} uri Auth Server URI - * @param {Object} config Configuration - */ -function FxAccountClient(uri, config) { - if (!uri && !config) { - throw new Error( - 'Firefox Accounts auth server endpoint or configuration object required.' - ); - } - - if (typeof uri !== 'string') { - config = uri || {}; - uri = config.uri; - } - - if (typeof config === 'undefined') { - config = {}; - } - - if (!uri) { - throw new Error('FxA auth server uri not set.'); - } - - if (!uriVersionRegExp.test(uri)) { - uri = uri + '/' + VERSION; - } - - this.request = new Request(uri, config.xhr, { - localtimeOffsetMsec: config.localtimeOffsetMsec, - }); -} - -FxAccountClient.VERSION = VERSION; - -/** - * @method signUp - * @param {String} email Email input - * @param {String} password Password input - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, calls the API with `?keys=true` to get the keyFetchToken - * @param {String} [options.service] - * Opaque alphanumeric token to be included in verification links - * @param {String} [options.redirectTo] - * a URL that the client should be redirected to after handling the request - * @param {String} [options.preVerified] - * set email to be verified if possible - * @param {String} [options.resume] - * Opaque url-encoded string that will be included in the verification link - * as a querystring parameter, useful for continuing an OAuth flow for - * example. - * @param {String} [options.lang] - * set the language for the 'Accept-Language' header - * @param {String} [options.style] - * Specify the style of confirmation emails - * @param {String} [options.verificationMethod] - * Specify the verification method to confirm the account with - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.signUp = function (email, password, options) { - var self = this; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(password, 'password'); - - return credentials.setup(email, password); - }) - .then(function (result) { - /*eslint complexity: [2, 13] */ - var endpoint = '/account/create'; - var data = { - email: result.emailUTF8, - authPW: sjcl.codec.hex.fromBits(result.authPW), - }; - var requestOpts = {}; - - if (options) { - if (options.service) { - data.service = options.service; - } - - if (options.redirectTo) { - data.redirectTo = options.redirectTo; - } - - // preVerified is used for unit/functional testing - if (options.preVerified) { - data.preVerified = options.preVerified; - } - - if (options.resume) { - data.resume = options.resume; - } - - if (options.keys) { - endpoint += '?keys=true'; - } - - if (options.lang) { - requestOpts.headers = { - 'Accept-Language': options.lang, - }; - } - - if (options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - - if (options.style) { - data.style = options.style; - } - - if (options.verificationMethod) { - data.verificationMethod = options.verificationMethod; - } - } - - return self.request - .send(endpoint, 'POST', null, data, requestOpts) - .then(function (accountData) { - if (options && options.keys) { - accountData.unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey); - } - return accountData; - }); - }); -}; - -/** - * @method signIn - * @param {String} email Email input - * @param {String} password Password input - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, calls the API with `?keys=true` to get the keyFetchToken - * @param {Boolean} [options.skipCaseError] - * If `true`, the request will skip the incorrect case error - * @param {String} [options.service] - * Service being signed into - * @param {String} [options.reason] - * Reason for sign in. Can be one of: `signin`, `password_check`, - * `password_change`, `password_reset` - * @param {String} [options.redirectTo] - * a URL that the client should be redirected to after handling the request - * @param {String} [options.resume] - * Opaque url-encoded string that will be included in the verification link - * as a querystring parameter, useful for continuing an OAuth flow for - * example. - * @param {String} [options.originalLoginEmail] - * If retrying after an "incorrect email case" error, this specifies - * the email address as originally entered by the user. - * @param {String} [options.verificationMethod] - * Request a specific verification method be used for verifying the session, - * e.g. 'email-2fa' or 'totp-2fa'. - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - * @param {String} [options.unblockCode] - * Login unblock code. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.signIn = function (email, password, options) { - var self = this; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(password, 'password'); - - return credentials.setup(email, password); - }) - .then(function (result) { - var endpoint = '/account/login'; - - if (options.keys) { - endpoint += '?keys=true'; - } - - var data = { - email: result.emailUTF8, - authPW: sjcl.codec.hex.fromBits(result.authPW), - }; - - if (options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - - if (options.reason) { - data.reason = options.reason; - } - - if (options.redirectTo) { - data.redirectTo = options.redirectTo; - } - - if (options.resume) { - data.resume = options.resume; - } - - if (options.service) { - data.service = options.service; - } - - if (options.unblockCode) { - data.unblockCode = options.unblockCode; - } - - if (options.originalLoginEmail) { - data.originalLoginEmail = options.originalLoginEmail; - } - - if (options.verificationMethod) { - data.verificationMethod = options.verificationMethod; - } - - return self.request.send(endpoint, 'POST', null, data).then( - function (accountData) { - if (options.keys) { - accountData.unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey); - } - return accountData; - }, - function (error) { - if ( - error && - error.email && - error.errno === ERRORS.INCORRECT_EMAIL_CASE && - !options.skipCaseError - ) { - options.skipCaseError = true; - options.originalLoginEmail = email; - - return self.signIn(error.email, password, options); - } else { - throw error; - } - } - ); - }); -}; - -/** - * @method verifyCode - * @param {String} uid Account ID - * @param {String} code Verification code - * @param {Object} [options={}] Options - * @param {String} [options.service] - * Service being signed into - * @param {String} [options.reminder] - * Reminder that was used to verify the account - * @param {String} [options.type] - * Type of code being verified, only supports `secondary` otherwise will verify account/sign-in - * @param {Array} [options.newsletters] - * Array of newsletters to opt into. - * @param {String} [options.style] - * Specify the style of email to send. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.verifyCode = function (uid, code, options) { - var self = this; - - return Promise.resolve().then(function () { - required(uid, 'uid'); - required(code, 'verify code'); - - var data = { - uid: uid, - code: code, - }; - - if (options) { - if (options.service) { - data.service = options.service; - } - - if (options.reminder) { - data.reminder = options.reminder; - } - - if (options.type) { - data.type = options.type; - } - - if (options.newsletters) { - data.newsletters = options.newsletters; - } - - if (options.style) { - data.style = options.style; - } - } - - return self.request.send('/recovery_email/verify_code', 'POST', null, data); - }); -}; - -/** - * @method recoveryEmailStatus - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.recoveryEmailStatus = function (sessionToken) { - var self = this; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/recovery_email/status', 'GET', creds); - }); -}; - -/** - * Re-sends a verification code to the account's recovery email address. - * - * @method recoveryEmailResendCode - * @param {String} sessionToken sessionToken obtained from signIn - * @param {Object} [options={}] Options - * @param {String} [options.email] - * Code will be resent to this email, only used for secondary email codes - * @param {String} [options.service] - * Opaque alphanumeric token to be included in verification links - * @param {String} [options.redirectTo] - * a URL that the client should be redirected to after handling the request - * @param {String} [options.resume] - * Opaque url-encoded string that will be included in the verification link - * as a querystring parameter, useful for continuing an OAuth flow for - * example. - * @param {String} [options.type] - * Specifies the type of code to send, currently only supported type is - * `upgradeSession`. - * @param {String} [options.lang] - * set the language for the 'Accept-Language' header - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.recoveryEmailResendCode = function ( - sessionToken, - options -) { - var self = this; - var data = {}; - var requestOpts = {}; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - if (options) { - if (options.email) { - data.email = options.email; - } - - if (options.service) { - data.service = options.service; - } - - if (options.redirectTo) { - data.redirectTo = options.redirectTo; - } - - if (options.resume) { - data.resume = options.resume; - } - - if (options.type) { - data.type = options.type; - } - - if (options.lang) { - requestOpts.headers = { - 'Accept-Language': options.lang, - }; - } - - if (options.style) { - data.style = options.style; - } - } - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send( - '/recovery_email/resend_code', - 'POST', - creds, - data, - requestOpts - ); - }); -}; - -/** - * Used to ask the server to send a recovery code. - * The API returns passwordForgotToken to the client. - * - * @method passwordForgotSendCode - * @param {String} email - * @param {Object} [options={}] Options - * @param {String} [options.service] - * Opaque alphanumeric token to be included in verification links - * @param {String} [options.redirectTo] - * a URL that the client should be redirected to after handling the request - * @param {String} [options.resume] - * Opaque url-encoded string that will be included in the verification link - * as a querystring parameter, useful for continuing an OAuth flow for - * example. - * @param {String} [options.lang] - * set the language for the 'Accept-Language' header - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.passwordForgotSendCode = function (email, options) { - var self = this; - var data = { - email: email, - }; - var requestOpts = {}; - - return Promise.resolve().then(function () { - required(email, 'email'); - - if (options) { - if (options.service) { - data.service = options.service; - } - - if (options.redirectTo) { - data.redirectTo = options.redirectTo; - } - - if (options.resume) { - data.resume = options.resume; - } - - if (options.lang) { - requestOpts.headers = { - 'Accept-Language': options.lang, - }; - } - - if (options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - } - - return self.request.send( - '/password/forgot/send_code', - 'POST', - null, - data, - requestOpts - ); - }); -}; - -/** - * Re-sends a verification code to the account's recovery email address. - * HAWK-authenticated with the passwordForgotToken. - * - * @method passwordForgotResendCode - * @param {String} email - * @param {String} passwordForgotToken - * @param {Object} [options={}] Options - * @param {String} [options.service] - * Opaque alphanumeric token to be included in verification links - * @param {String} [options.redirectTo] - * a URL that the client should be redirected to after handling the request - * @param {String} [options.resume] - * Opaque url-encoded string that will be included in the verification link - * as a querystring parameter, useful for continuing an OAuth flow for - * example. - * @param {String} [options.lang] - * set the language for the 'Accept-Language' header - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.passwordForgotResendCode = function ( - email, - passwordForgotToken, - options -) { - var self = this; - var data = { - email: email, - }; - var requestOpts = {}; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(passwordForgotToken, 'passwordForgotToken'); - - if (options) { - if (options.service) { - data.service = options.service; - } - - if (options.redirectTo) { - data.redirectTo = options.redirectTo; - } - - if (options.resume) { - data.resume = options.resume; - } - - if (options.lang) { - requestOpts.headers = { - 'Accept-Language': options.lang, - }; - } - } - - return hawkCredentials( - passwordForgotToken, - 'passwordForgotToken', - HKDF_SIZE - ); - }) - .then(function (creds) { - return self.request.send( - '/password/forgot/resend_code', - 'POST', - creds, - data, - requestOpts - ); - }); -}; - -/** - * Submits the verification token to the server. - * The API returns accountResetToken to the client. - * HAWK-authenticated with the passwordForgotToken. - * - * @method passwordForgotVerifyCode - * @param {String} code - * @param {String} passwordForgotToken - * @param {Object} [options={}] Options - * @param {Boolean} [options.accountResetWithRecoveryKey] verifying code to be use in account recovery - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.passwordForgotVerifyCode = function ( - code, - passwordForgotToken, - options -) { - var self = this; - - return Promise.resolve() - .then(function () { - required(code, 'reset code'); - required(passwordForgotToken, 'passwordForgotToken'); - - return hawkCredentials( - passwordForgotToken, - 'passwordForgotToken', - HKDF_SIZE - ); - }) - .then(function (creds) { - var data = { - code: code, - }; - - if (options && options.accountResetWithRecoveryKey) { - data.accountResetWithRecoveryKey = options.accountResetWithRecoveryKey; - } - - return self.request.send( - '/password/forgot/verify_code', - 'POST', - creds, - data - ); - }); -}; - -/** - * Returns the status for the passwordForgotToken. - * If the request returns a success response, the token has not yet been consumed. - - * @method passwordForgotStatus - * @param {String} passwordForgotToken - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.passwordForgotStatus = function ( - passwordForgotToken -) { - var self = this; - - return Promise.resolve() - .then(function () { - required(passwordForgotToken, 'passwordForgotToken'); - - return hawkCredentials( - passwordForgotToken, - 'passwordForgotToken', - HKDF_SIZE - ); - }) - .then(function (creds) { - return self.request.send('/password/forgot/status', 'GET', creds); - }); -}; - -/** - * The API returns reset result to the client. - * HAWK-authenticated with accountResetToken - * - * @method accountReset - * @param {String} email - * @param {String} newPassword - * @param {String} accountResetToken - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, a new `keyFetchToken` is provisioned. `options.sessionToken` - * is required if `options.keys` is true. - * @param {Boolean} [options.sessionToken] - * If `true`, a new `sessionToken` is provisioned. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.accountReset = function ( - email, - newPassword, - accountResetToken, - options -) { - var self = this; - var data = {}; - var unwrapBKey; - - options = options || {}; - - if (options.sessionToken) { - data.sessionToken = options.sessionToken; - } - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(newPassword, 'new password'); - required(accountResetToken, 'accountResetToken'); - - if (options.keys) { - required(options.sessionToken, 'sessionToken'); - } - - return credentials.setup(email, newPassword); - }) - .then(function (result) { - if (options.keys) { - unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey); - } - - data.authPW = sjcl.codec.hex.fromBits(result.authPW); - - return hawkCredentials(accountResetToken, 'accountResetToken', HKDF_SIZE); - }) - .then(function (creds) { - var queryParams = ''; - if (options.keys) { - queryParams = '?keys=true'; - } - - var endpoint = '/account/reset' + queryParams; - return self.request - .send(endpoint, 'POST', creds, data) - .then(function (accountData) { - if (options.keys && accountData.keyFetchToken) { - accountData.unwrapBKey = unwrapBKey; - } - - return accountData; - }); - }); -}; - -/** - * Get the base16 bundle of encrypted kA|wrapKb. - * - * @method accountKeys - * @param {String} keyFetchToken - * @param {String} oldUnwrapBKey - * @return {Promise} A promise that will be fulfilled with JSON of {kA, kB} of the key bundle - */ -FxAccountClient.prototype.accountKeys = function ( - keyFetchToken, - oldUnwrapBKey -) { - var self = this; - - return Promise.resolve() - .then(function () { - required(keyFetchToken, 'keyFetchToken'); - required(oldUnwrapBKey, 'oldUnwrapBKey'); - - return hawkCredentials(keyFetchToken, 'keyFetchToken', 3 * 32); - }) - .then(function (creds) { - var bundleKey = sjcl.codec.hex.fromBits(creds.bundleKey); - - return self.request - .send('/account/keys', 'GET', creds) - .then(function (payload) { - return credentials.unbundleKeyFetchResponse( - bundleKey, - payload.bundle - ); - }); - }) - .then(function (keys) { - return { - kB: sjcl.codec.hex.fromBits( - credentials.xor( - sjcl.codec.hex.toBits(keys.wrapKB), - sjcl.codec.hex.toBits(oldUnwrapBKey) - ) - ), - kA: keys.kA, - }; - }); -}; - -/** - * This deletes the account completely. All stored data is erased. - * - * @method accountDestroy - * @param {String} email Email input - * @param {String} password Password input - * @param {Object} [options={}] Options - * @param {Boolean} [options.skipCaseError] - * If `true`, the request will skip the incorrect case error - * @param {String} sessionToken User session token - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.accountDestroy = function ( - email, - password, - options, - sessionToken -) { - var self = this; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(password, 'password'); - - var defers = [credentials.setup(email, password)]; - if (sessionToken) { - defers.push(hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE)); - } - - return Promise.all(defers); - }) - .then(function (results) { - var auth = results[0]; - var creds = results[1]; - var data = { - email: auth.emailUTF8, - authPW: sjcl.codec.hex.fromBits(auth.authPW), - }; - - return self.request.send('/account/destroy', 'POST', creds, data).then( - function (response) { - return response; - }, - function (error) { - // if incorrect email case error - if ( - error && - error.email && - error.errno === ERRORS.INCORRECT_EMAIL_CASE && - !options.skipCaseError - ) { - options.skipCaseError = true; - - return self.accountDestroy( - error.email, - password, - options, - sessionToken - ); - } else { - throw error; - } - } - ); - }); -}; - -/** - * Gets the status of an account by uid. - * - * @method accountStatus - * @param {String} uid User account id - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.accountStatus = function (uid) { - var self = this; - - return Promise.resolve().then(function () { - required(uid, 'uid'); - - return self.request.send('/account/status?uid=' + uid, 'GET'); - }); -}; - -/** - * Gets the status of an account by email. - * - * @method accountStatusByEmail - * @param {String} email User account email - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.accountStatusByEmail = function (email) { - var self = this; - - return Promise.resolve().then(function () { - required(email, 'email'); - - return self.request.send('/account/status', 'POST', null, { - email: email, - }); - }); -}; - -/** - * Gets the account profile - * - * @method accountProfile - * @param {String} sessionToken User session token - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.accountProfile = function (sessionToken) { - var self = this; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/account/profile', 'GET', creds); - }); -}; - -/** - * Gets aggregated account data, to be used instead of making - * multiple calls to disparate `/status` endpoints. - * - * @method account - * @param {String} sessionToken User session token - * @return {Promise} A promise that will be fulfilled with JSON - */ -FxAccountClient.prototype.account = function (sessionToken) { - var self = this; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/account', 'GET', creds); - }); -}; - -/** - * Destroys this session, by invalidating the sessionToken. - * - * @method sessionDestroy - * @param {String} sessionToken User session token - * @param {Object} [options={}] Options - * @param {String} [options.customSessionToken] Override which session token to destroy for this same user - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sessionDestroy = function (sessionToken, options) { - var self = this; - var data = {}; - options = options || {}; - - if (options.customSessionToken) { - data.customSessionToken = options.customSessionToken; - } - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/session/destroy', 'POST', creds, data); - }); -}; - -/** - * Responds successfully if the session status is valid, requires the sessionToken. - * - * @method sessionStatus - * @param {String} sessionToken User session token - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sessionStatus = function (sessionToken) { - var self = this; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/session/status', 'GET', creds); - }); -}; - -/** - * Verifies an account and a session using a short code that is based on otp. - * - * @method sessionVerifyCode - * @param {String} sessionToken User session token - * @param {String} code Code to be verified - * @param {Object} [options={}] Options - * @param {String} [options.service] - * Service being used - * @param {Array} [options.newsletters] - * Array of newsletters to opt into. - * @param {String} [options.style] - * Specify the style of email to send. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sessionVerifyCode = function ( - sessionToken, - code, - options -) { - var self = this; - - required(sessionToken, 'sessionToken'); - required(code, 'code'); - - var data = { - code: code, - }; - options = options || {}; - - if (options.service) { - data.service = options.service; - } - - if (options.newsletters) { - data.newsletters = options.newsletters; - } - - if (options.style) { - data.style = options.style; - } - - return Promise.resolve() - .then(function () { - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/session/verify_code', 'POST', creds, data); - }); -}; - -/** - * Resends the short code that can verify the account and session. - * - * @method sessionResendVerifyCode - * @param {String} sessionToken User session token - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sessionResendVerifyCode = function (sessionToken) { - var self = this; - required(sessionToken, 'sessionToken'); - - return Promise.resolve() - .then(function () { - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send('/session/resend_code', 'POST', creds, {}); - }); -}; - -/** - * @method sessionReauth - * @param {String} sessionToken sessionToken obtained from signIn - * @param {String} email Email input - * @param {String} password Password input - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, calls the API with `?keys=true` to get the keyFetchToken - * @param {Boolean} [options.skipCaseError] - * If `true`, the request will skip the incorrect case error - * @param {String} [options.service] - * Service being accessed that needs reauthentication - * @param {String} [options.reason] - * Reason for reauthentication. Can be one of: `signin`, `password_check`, - * `password_change`, `password_reset` - * @param {String} [options.redirectTo] - * a URL that the client should be redirected to after handling the request - * @param {String} [options.resume] - * Opaque url-encoded string that will be included in the verification link - * as a querystring parameter, useful for continuing an OAuth flow for - * example. - * @param {String} [options.originalLoginEmail] - * If retrying after an "incorrect email case" error, this specifies - * the email address as originally entered by the user. - * @param {String} [options.verificationMethod] - * Request a specific verification method be used for verifying the session, - * e.g. 'email-2fa' or 'totp-2fa'. - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - * @param {String} [options.unblockCode] - * Login unblock code. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sessionReauth = function ( - sessionToken, - email, - password, - options -) { - var self = this; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(email, 'email'); - required(password, 'password'); - - return credentials.setup(email, password); - }) - .then(function (result) { - var endpoint = '/session/reauth'; - - if (options.keys) { - endpoint += '?keys=true'; - } - - var data = { - email: result.emailUTF8, - authPW: sjcl.codec.hex.fromBits(result.authPW), - }; - - if (options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - - if (options.reason) { - data.reason = options.reason; - } - - if (options.redirectTo) { - data.redirectTo = options.redirectTo; - } - - if (options.resume) { - data.resume = options.resume; - } - - if (options.service) { - data.service = options.service; - } - - if (options.unblockCode) { - data.unblockCode = options.unblockCode; - } - - if (options.originalLoginEmail) { - data.originalLoginEmail = options.originalLoginEmail; - } - - if (options.verificationMethod) { - data.verificationMethod = options.verificationMethod; - } - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE) - .then(function (creds) { - return self.request.send(endpoint, 'POST', creds, data); - }) - .then( - function (accountData) { - if (options.keys) { - accountData.unwrapBKey = sjcl.codec.hex.fromBits( - result.unwrapBKey - ); - } - return accountData; - }, - function (error) { - if ( - error && - error.email && - error.errno === ERRORS.INCORRECT_EMAIL_CASE && - !options.skipCaseError - ) { - options.skipCaseError = true; - options.originalLoginEmail = email; - - return self.sessionReauth( - sessionToken, - error.email, - password, - options - ); - } else { - throw error; - } - } - ); - }); -}; - -/** - * Sign a BrowserID public key - * - * @method certificateSign - * @param {String} sessionToken User session token - * @param {Object} publicKey The key to sign - * @param {int} duration Time interval from now when the certificate will expire in milliseconds - * @param {Object} [options={}] Options - * @param {String} [service=''] The requesting service, sent via the query string - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.certificateSign = function ( - sessionToken, - publicKey, - duration, - options -) { - var self = this; - var data = { - publicKey: publicKey, - duration: duration, - }; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(publicKey, 'publicKey'); - required(duration, 'duration'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - options = options || {}; - - var queryString = ''; - if (options.service) { - queryString = '?service=' + encodeURIComponent(options.service); - } - - return self.request.send( - '/certificate/sign' + queryString, - 'POST', - creds, - data - ); - }); -}; - -/** - * Change the password from one known value to another. - * - * @method passwordChange - * @param {String} email - * @param {String} oldPassword - * @param {String} newPassword - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, calls the API with `?keys=true` to get a new keyFetchToken - * @param {String} [options.sessionToken] - * If a `sessionToken` is passed, a new sessionToken will be returned - * with the same `verified` status as the existing sessionToken. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.passwordChange = function ( - email, - oldPassword, - newPassword, - options -) { - var self = this; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(oldPassword, 'old password'); - required(newPassword, 'new password'); - - return self._passwordChangeStart(email, oldPassword); - }) - .then(function (credentials) { - var oldCreds = credentials; - var emailToHashWith = credentials.emailToHashWith || email; - - return self._passwordChangeKeys(oldCreds).then(function (keys) { - return self._passwordChangeFinish( - emailToHashWith, - newPassword, - oldCreds, - keys, - options - ); - }); - }); -}; - -/** - * First step to change the password. - * - * @method passwordChangeStart - * @private - * @param {String} email - * @param {String} oldPassword - * @param {Object} [options={}] Options - * @param {Boolean} [options.skipCaseError] - * If `true`, the request will skip the incorrect case error - * @return {Promise} A promise that will be fulfilled with JSON of `xhr.responseText` and `oldUnwrapBKey` - */ -FxAccountClient.prototype._passwordChangeStart = function ( - email, - oldPassword, - options -) { - var self = this; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(oldPassword, 'old password'); - - return credentials.setup(email, oldPassword); - }) - .then(function (oldCreds) { - var data = { - email: oldCreds.emailUTF8, - oldAuthPW: sjcl.codec.hex.fromBits(oldCreds.authPW), - }; - - return self.request - .send('/password/change/start', 'POST', null, data) - .then( - function (passwordData) { - passwordData.oldUnwrapBKey = sjcl.codec.hex.fromBits( - oldCreds.unwrapBKey - ); - - // Similar to password reset, this keeps the contract that we always - // hash passwords with the original account email. - passwordData.emailToHashWith = email; - return passwordData; - }, - function (error) { - // if incorrect email case error - if ( - error && - error.email && - error.errno === ERRORS.INCORRECT_EMAIL_CASE && - !options.skipCaseError - ) { - options.skipCaseError = true; - - return self._passwordChangeStart( - error.email, - oldPassword, - options - ); - } else { - throw error; - } - } - ); - }); -}; - -function checkCreds(creds) { - required(creds, 'credentials'); - required(creds.oldUnwrapBKey, 'credentials.oldUnwrapBKey'); - required(creds.keyFetchToken, 'credentials.keyFetchToken'); - required(creds.passwordChangeToken, 'credentials.passwordChangeToken'); -} - -/** - * Second step to change the password. - * - * @method _passwordChangeKeys - * @private - * @param {Object} oldCreds This object should consists of `oldUnwrapBKey`, `keyFetchToken` and `passwordChangeToken`. - * @return {Promise} A promise that will be fulfilled with JSON of `xhr.responseText` - */ -FxAccountClient.prototype._passwordChangeKeys = function (oldCreds) { - var self = this; - - return Promise.resolve() - .then(function () { - checkCreds(oldCreds); - }) - .then(function () { - return self.accountKeys(oldCreds.keyFetchToken, oldCreds.oldUnwrapBKey); - }); -}; - -/** - * Third step to change the password. - * - * @method _passwordChangeFinish - * @private - * @param {String} email - * @param {String} newPassword - * @param {Object} oldCreds This object should consists of `oldUnwrapBKey`, `keyFetchToken` and `passwordChangeToken`. - * @param {Object} keys This object should contain the unbundled keys - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, calls the API with `?keys=true` to get the keyFetchToken - * @param {String} [options.sessionToken] - * If a `sessionToken` is passed, a new sessionToken will be returned - * with the same `verified` status as the existing sessionToken. - * @return {Promise} A promise that will be fulfilled with JSON of `xhr.responseText` - */ -FxAccountClient.prototype._passwordChangeFinish = function ( - email, - newPassword, - oldCreds, - keys, - options -) { - options = options || {}; - var self = this; - - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(newPassword, 'new password'); - checkCreds(oldCreds); - required(keys, 'keys'); - required(keys.kB, 'keys.kB'); - - var defers = []; - defers.push(credentials.setup(email, newPassword)); - defers.push( - hawkCredentials( - oldCreds.passwordChangeToken, - 'passwordChangeToken', - HKDF_SIZE - ) - ); - - if (options.sessionToken) { - // Unbundle session data to get session id - defers.push( - hawkCredentials(options.sessionToken, 'sessionToken', HKDF_SIZE) - ); - } - - return Promise.all(defers); - }) - .then(function (results) { - var newCreds = results[0]; - var hawkCreds = results[1]; - var sessionData = results[2]; - var newWrapKb = sjcl.codec.hex.fromBits( - credentials.xor(sjcl.codec.hex.toBits(keys.kB), newCreds.unwrapBKey) - ); - - var queryParams = ''; - if (options.keys) { - queryParams = '?keys=true'; - } - - var sessionTokenId; - if (sessionData && sessionData.id) { - sessionTokenId = sessionData.id; - } - - return self.request - .send('/password/change/finish' + queryParams, 'POST', hawkCreds, { - wrapKb: newWrapKb, - authPW: sjcl.codec.hex.fromBits(newCreds.authPW), - sessionToken: sessionTokenId, - }) - .then(function (accountData) { - if (options.keys && accountData.keyFetchToken) { - accountData.unwrapBKey = sjcl.codec.hex.fromBits( - newCreds.unwrapBKey - ); - } - return accountData; - }); - }); -}; - -/** - * Get 32 bytes of random data. This should be combined with locally-sourced entropy when creating salts, etc. - * - * @method getRandomBytes - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.getRandomBytes = function () { - return this.request.send('/get_random_bytes', 'POST'); -}; - -/** - * Add a new device - * - * @method deviceRegister - * @param {String} sessionToken User session token - * @param {String} deviceName Name of device - * @param {String} deviceType Type of device (mobile|desktop) - * @param {Object} [options={}] Options - * @param {string} [options.deviceCallback] Device's push endpoint. - * @param {string} [options.devicePublicKey] Public key used to encrypt push messages. - * @param {string} [options.deviceAuthKey] Authentication secret used to encrypt push messages. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.deviceRegister = function ( - sessionToken, - deviceName, - deviceType, - options -) { - var request = this.request; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(deviceName, 'deviceName'); - required(deviceType, 'deviceType'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - name: deviceName, - type: deviceType, - }; - - if (options.deviceCallback) { - data.pushCallback = options.deviceCallback; - } - - if (options.devicePublicKey && options.deviceAuthKey) { - data.pushPublicKey = options.devicePublicKey; - data.pushAuthKey = options.deviceAuthKey; - } - - return request.send('/account/device', 'POST', creds, data); - }); -}; - -/** - * Update the name of an existing device - * - * @method deviceUpdate - * @param {String} sessionToken User session token - * @param {String} deviceId User-unique identifier of device - * @param {String} deviceName Name of device - * @param {Object} [options={}] Options - * @param {string} [options.deviceCallback] Device's push endpoint. - * @param {string} [options.devicePublicKey] Public key used to encrypt push messages. - * @param {string} [options.deviceAuthKey] Authentication secret used to encrypt push messages. - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.deviceUpdate = function ( - sessionToken, - deviceId, - deviceName, - options -) { - var request = this.request; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(deviceId, 'deviceId'); - required(deviceName, 'deviceName'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - id: deviceId, - name: deviceName, - }; - - if (options.deviceCallback) { - data.pushCallback = options.deviceCallback; - } - - if (options.devicePublicKey && options.deviceAuthKey) { - data.pushPublicKey = options.devicePublicKey; - data.pushAuthKey = options.deviceAuthKey; - } - - return request.send('/account/device', 'POST', creds, data); - }); -}; - -/** - * Unregister an existing device - * - * @method deviceDestroy - * @param {String} sessionToken Session token obtained from signIn - * @param {String} deviceId User-unique identifier of device - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.deviceDestroy = function (sessionToken, deviceId) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(deviceId, 'deviceId'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - id: deviceId, - }; - - return request.send('/account/device/destroy', 'POST', creds, data); - }); -}; - -/** - * Get a list of all devices for a user - * - * @method deviceList - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.deviceList = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/account/devices', 'GET', creds); - }); -}; - -/** - * Get a list of user's sessions - * - * @method sessions - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sessions = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/account/sessions', 'GET', creds); - }); -}; - -/** - * Get a list of user's security events - * - * @method securityEvents - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.securityEvents = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/securityEvents', 'GET', creds); - }); -}; - -/** - * Delete user's security events - * - * @method deleteSecurityEvents - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.deleteSecurityEvents = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/securityEvents', 'DELETE', creds, {}); - }); -}; - -/** - * Get a list of user's attached clients - * - * @method attachedClients - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.attachedClients = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/account/attached_clients', 'GET', creds); - }); -}; - -/** - * Destroys all tokens held by an attached client. - * - * @method attachedClientDestroy - * @param {String} sessionToken User session token - * @param {Object} clientInfo Attached client info, as returned by `attachedClients` method - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.attachedClientDestroy = function ( - sessionToken, - clientInfo -) { - var self = this; - var data = { - clientId: clientInfo.clientId, - deviceId: clientInfo.deviceId, - refreshTokenId: clientInfo.refreshTokenId, - sessionTokenId: clientInfo.sessionTokenId, - }; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send( - '/account/attached_client/destroy', - 'POST', - creds, - data - ); - }); -}; - -/** - * Get a list of user's attached clients - * - * @method attachedClients - * @param {String} sessionToken sessionToken obtained from signIn - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.attachedClients = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/account/attached_clients', 'GET', creds); - }); -}; - -/** - * Destroys all tokens held by an attached client. - * - * @method attachedClientDestroy - * @param {String} sessionToken User session token - * @param {Object} clientInfo Attached client info, as returned by `attachedClients` method - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.attachedClientDestroy = function ( - sessionToken, - clientInfo -) { - var self = this; - var data = { - clientId: clientInfo.clientId, - deviceId: clientInfo.deviceId, - refreshTokenId: clientInfo.refreshTokenId, - sessionTokenId: clientInfo.sessionTokenId, - }; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return self.request.send( - '/account/attached_client/destroy', - 'POST', - creds, - data - ); - }); -}; - -/** - * Send an unblock code - * - * @method sendUnblockCode - * @param {String} email email where to send the login authorization code - * @param {Object} [options={}] Options - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.sendUnblockCode = function (email, options) { - var self = this; - - return Promise.resolve().then(function () { - required(email, 'email'); - - var data = { - email: email, - }; - - if (options && options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - - return self.request.send( - '/account/login/send_unblock_code', - 'POST', - null, - data - ); - }); -}; - -/** - * Reject a login unblock code. Code will be deleted from the server - * and will not be able to be used again. - * - * @method rejectLoginAuthorizationCode - * @param {String} uid Account ID - * @param {String} unblockCode unblock code - * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request - */ -FxAccountClient.prototype.rejectUnblockCode = function (uid, unblockCode) { - var self = this; - - return Promise.resolve().then(function () { - required(uid, 'uid'); - required(unblockCode, 'unblockCode'); - - var data = { - uid: uid, - unblockCode: unblockCode, - }; - - return self.request.send( - '/account/login/reject_unblock_code', - 'POST', - null, - data - ); - }); -}; - -/** - * Send an sms. - * - * @method sendSms - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} phoneNumber Phone number sms will be sent to - * @param {String} messageId Corresponding message id that will be sent - * @param {Object} [options={}] Options - * @param {String} [options.lang] Language that sms will be sent in - * @param {Array} [options.features] Array of features to be enabled for the request - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - */ -FxAccountClient.prototype.sendSms = function ( - sessionToken, - phoneNumber, - messageId, - options -) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(phoneNumber, 'phoneNumber'); - required(messageId, 'messageId'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - phoneNumber: phoneNumber, - messageId: messageId, - }; - var requestOpts = {}; - - if (options) { - if (options.lang) { - requestOpts.headers = { - 'Accept-Language': options.lang, - }; - } - - if (options.features) { - data.features = options.features; - } - - if (options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - } - - return request.send('/sms', 'POST', creds, data, requestOpts); - }); -}; - -/** - * Get SMS status for the current user. - * - * @method smsStatus - * @param {String} sessionToken SessionToken obtained from signIn - * @param {Object} [options={}] Options - * @param {String} [options.country] country Country to force for testing. - */ -FxAccountClient.prototype.smsStatus = function (sessionToken, options) { - var request = this.request; - options = options || {}; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var url = '/sms/status'; - if (options.country) { - url += '?country=' + encodeURIComponent(options.country); - } - return request.send(url, 'GET', creds); - }); -}; - -/** - * Consume a signinCode. - * - * @method consumeSigninCode - * @param {String} code The signinCode entered by the user - * @param {String} flowId Identifier for the current event flow - * @param {Number} flowBeginTime Timestamp for the flow.begin event - * @param {String} [deviceId] Identifier for the current device - */ -FxAccountClient.prototype.consumeSigninCode = function ( - code, - flowId, - flowBeginTime, - deviceId -) { - var self = this; - - return Promise.resolve().then(function () { - required(code, 'code'); - required(flowId, 'flowId'); - required(flowBeginTime, 'flowBeginTime'); - - return self.request.send('/signinCodes/consume', 'POST', null, { - code: code, - metricsContext: { - deviceId: deviceId, - flowId: flowId, - flowBeginTime: flowBeginTime, - }, - }); - }); -}; - -/** - * Get the recovery emails associated with the signed in account. - * - * @method recoveryEmails - * @param {String} sessionToken SessionToken obtained from signIn - */ -FxAccountClient.prototype.recoveryEmails = function (sessionToken) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/recovery_emails', 'GET', creds); - }); -}; - -/** - * Create a new recovery email for the signed in account. - * - * @method recoveryEmailCreate - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} email new email to be added - */ -FxAccountClient.prototype.recoveryEmailCreate = function ( - sessionToken, - email, - options -) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(sessionToken, 'email'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - email: email, - }; - - if (options && options.verificationMethod) { - data.verificationMethod = options.verificationMethod; - } - - return request.send('/recovery_email', 'POST', creds, data); - }); -}; - -/** - * Remove the recovery email for the signed in account. - * - * @method recoveryEmailDestroy - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} email email to be removed - */ -FxAccountClient.prototype.recoveryEmailDestroy = function ( - sessionToken, - email -) { - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(sessionToken, 'email'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - email: email, - }; - - return request.send('/recovery_email/destroy', 'POST', creds, data); - }); -}; - -/** - * Changes user's primary email address. - * - * @method recoveryEmailSetPrimaryEmail - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} email Email that will be the new primary email for user - */ -FxAccountClient.prototype.recoveryEmailSetPrimaryEmail = function ( - sessionToken, - email -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - email: email, - }; - return request.send('/recovery_email/set_primary', 'POST', creds, data); - }); -}; - -FxAccountClient.prototype.recoveryEmailSecondaryVerifyCode = function ( - sessionToken, - email, - code -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(email, 'email'); - required(code, 'code'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - email: email, - code: code, - }; - return request.send( - '/recovery_email/secondary/verify_code', - 'POST', - creds, - data - ); - }); -}; - -/** - * Verify a secondary email via a short code. - * - * @method recoveryEmailSecondaryVerifyCode - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} email Email to verify - * @param {String} code Code to verify with - */ -FxAccountClient.prototype.recoveryEmailSecondaryVerifyCode = function ( - sessionToken, - email, - code -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(email, 'email'); - required(code, 'code'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - email: email, - code: code, - }; - return request.send( - '/recovery_email/secondary/verify_code', - 'POST', - creds, - data - ); - }); -}; - -/** - * Resend secondary email verification code. - * - * @method recoveryEmailSecondaryVerifyCode - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} email Email to resend verification code - */ -FxAccountClient.prototype.recoveryEmailSecondaryResendCode = function ( - sessionToken, - email -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(email, 'email'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - email: email, - }; - return request.send( - '/recovery_email/secondary/resend_code', - 'POST', - creds, - data - ); - }); -}; - -/** - * Creates a new TOTP token for the user associated with this session. - * - * @method createTotpToken - * @param {String} sessionToken SessionToken obtained from signIn - * @param {Object} [options.metricsContext={}] Metrics context metadata - * @param {String} options.metricsContext.deviceId identifier for the current device - * @param {String} options.metricsContext.flowId identifier for the current event flow - * @param {Number} options.metricsContext.flowBeginTime flow.begin event time - * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier - * @param {Number} options.metricsContext.utmContent content identifier - * @param {Number} options.metricsContext.utmMedium acquisition medium - * @param {Number} options.metricsContext.utmSource traffic source - * @param {Number} options.metricsContext.utmTerm search terms - */ -FxAccountClient.prototype.createTotpToken = function (sessionToken, options) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = {}; - - if (options && options.metricsContext) { - data.metricsContext = metricsContext.marshall(options.metricsContext); - } - - return request.send('/totp/create', 'POST', creds, data); - }); -}; - -/** - * Deletes this user's TOTP token. - * - * @method deleteTotpToken - * @param {String} sessionToken SessionToken obtained from signIn - */ -FxAccountClient.prototype.deleteTotpToken = function (sessionToken) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/totp/destroy', 'POST', creds, {}); - }); -}; - -/** - * Check to see if the current user has a TOTP token associated with - * their account. - * - * @method checkTotpTokenExists - * @param {String} sessionToken SessionToken obtained from signIn - */ -FxAccountClient.prototype.checkTotpTokenExists = function (sessionToken) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/totp/exists', 'GET', creds); - }); -}; - -/** - * Verify tokens if using a valid TOTP code. - * - * @method verifyTotpCode - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} code TOTP code to verif - * @param {String} [options.service] Service being used - */ -FxAccountClient.prototype.verifyTotpCode = function ( - sessionToken, - code, - options -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(code, 'code'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - code: code, - }; - - if (options && options.service) { - data.service = options.service; - } - - return request.send('/session/verify/totp', 'POST', creds, data); - }); -}; - -/** - * Replace user's recovery codes. - * - * @method replaceRecoveryCodes - * @param {String} sessionToken SessionToken obtained from signIn - */ -FxAccountClient.prototype.replaceRecoveryCodes = function (sessionToken) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/recoveryCodes', 'GET', creds); - }); -}; - -/** - * Consume recovery code. - * - * @method consumeRecoveryCode - * @param {String} sessionToken SessionToken obtained from signIn - * @param {String} code recovery code - */ -FxAccountClient.prototype.consumeRecoveryCode = function (sessionToken, code) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(code, 'code'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - code: code, - }; - - return request.send('/session/verify/recoveryCode', 'POST', creds, data); - }); -}; - -/** - * Creates a new recovery key for the account. The recovery key contains encrypted - * data the corresponds the the accounts current `kB`. This data can be used during - * the password reset process to avoid regenerating the `kB`. - * - * @param sessionToken - * @param recoveryKeyId The recoveryKeyId that can be used to retrieve saved bundle - * @param bundle The encrypted recovery bundle to store - * @returns {Promise} A promise that will be fulfilled with decoded recovery data (`kB`) - */ -FxAccountClient.prototype.createRecoveryKey = function ( - sessionToken, - recoveryKeyId, - bundle, - enabled -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(recoveryKeyId, 'recoveryKeyId'); - required(bundle, 'bundle'); - required(enabled, 'enabled'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - var data = { - recoveryKeyId: recoveryKeyId, - recoveryData: bundle, - enabled: enabled, - }; - - return request.send('/recoveryKey', 'POST', creds, data); - }); -}; - -/** - * Retrieves the encrypted recovery data that corresponds to the recovery key which - * then gets decoded into the stored `kB`. - * - * @param accountResetToken - * @param recoveryKeyId The recovery key id to retrieve encrypted bundle - * @returns {Promise} A promise that will be fulfilled with decoded recovery data (`kB`) - */ -FxAccountClient.prototype.getRecoveryKey = function ( - accountResetToken, - recoveryKeyId -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(accountResetToken, 'accountResetToken'); - required(recoveryKeyId, 'recoveryKeyId'); - - return hawkCredentials(accountResetToken, 'accountResetToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/recoveryKey/' + recoveryKeyId, 'GET', creds); - }); -}; - -/** - * Reset a user's account using keys (kB) derived from a recovery key. This - * process can be used to maintain the account's original kB. - * - * @param accountResetToken The account reset token - * @param email The current email of the account - * @param newPassword The new password of the account - * @param recoveryKeyId The recovery key id used for account recovery - * @param keys Keys used to create the new wrapKb - * @param {Object} [options={}] Options - * @param {Boolean} [options.keys] - * If `true`, a new `keyFetchToken` is provisioned. `options.sessionToken` - * is required if `options.keys` is true. - * @param {Boolean} [options.sessionToken] - * If `true`, a new `sessionToken` is provisioned. - * @returns {Promise} A promise that will be fulfilled with updated account data - */ -FxAccountClient.prototype.resetPasswordWithRecoveryKey = function ( - accountResetToken, - email, - newPassword, - recoveryKeyId, - keys, - options -) { - options = options || {}; - var request = this.request; - return Promise.resolve() - .then(function () { - required(email, 'email'); - required(newPassword, 'new password'); - required(keys, 'keys'); - required(keys.kB, 'keys.kB'); - required(accountResetToken, 'accountResetToken'); - required(recoveryKeyId, 'recoveryKeyId'); - - var defers = []; - defers.push(credentials.setup(email, newPassword)); - defers.push( - hawkCredentials(accountResetToken, 'accountResetToken', HKDF_SIZE) - ); - - return Promise.all(defers); - }) - .then(function (results) { - var newCreds = results[0]; - var hawkCreds = results[1]; - var newWrapKb = sjcl.codec.hex.fromBits( - credentials.xor(sjcl.codec.hex.toBits(keys.kB), newCreds.unwrapBKey) - ); - - var data = { - wrapKb: newWrapKb, - authPW: sjcl.codec.hex.fromBits(newCreds.authPW), - recoveryKeyId: recoveryKeyId, - }; - - if (options.sessionToken) { - data.sessionToken = options.sessionToken; - } - - if (options.keys) { - required(options.sessionToken, 'sessionToken'); - } - - var queryParams = ''; - if (options.keys) { - queryParams = '?keys=true'; - } - - return request - .send('/account/reset' + queryParams, 'POST', hawkCreds, data) - .then(function (accountData) { - if (options.keys && accountData.keyFetchToken) { - accountData.unwrapBKey = sjcl.codec.hex.fromBits( - newCreds.unwrapBKey - ); - } - return accountData; - }); - }); -}; - -/** - * Deletes the recovery key associated with this user. - * - * @param sessionToken - */ -FxAccountClient.prototype.deleteRecoveryKey = function (sessionToken) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/recoveryKey', 'DELETE', creds, {}); - }); -}; - -/** - * This checks to see if a recovery key exists for a user. This check - * can be performed with either a sessionToken or an email. - * - * Typically, sessionToken is used when checking from within the `/settings` - * view. If it exists, we can give the user an option to revoke the key. - * - * Checking with an email is typically performed during the password reset - * flow. It is used to decide whether or not we can redirect a user to - * the `Reset password with recovery key` page or regular password reset page. - * - * @param sessionToken - * @param {String} email User's email - * @returns {Promise} A promise that will be fulfilled with whether or not account has recovery key - */ -FxAccountClient.prototype.recoveryKeyExists = function (sessionToken, email) { - var request = this.request; - return Promise.resolve().then(function () { - if (sessionToken) { - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE).then( - function (creds) { - return request.send('/recoveryKey/exists', 'POST', creds, {}); - } - ); - } - - return request.send('/recoveryKey/exists', 'POST', null, { - email: email, - }); - }); -}; - -/** - * @param sessionToken - * @param {String} email User's email - * @param {String} recoveryKeyId User's recovery key id to verify - * @returns {Promise} A promise that will be fulfilled with after the recovery key has been verified - */ -FxAccountClient.prototype.verifyRecoveryKey = function ( - sessionToken, - recoveryKeyId -) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(recoveryKeyId, 'recoveryKeyId'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/recoveryKey/verify', 'POST', creds, { - recoveryKeyId: recoveryKeyId, - }); - }); -}; - -/** - * @param {String} idToken, an OIDC ID Token - * @param {String} clientId, used to check the Audience Claim ('aud') in the - * token matches the current client. - */ -FxAccountClient.prototype.verifyIdToken = function (idToken, clientId) { - var request = this.request; - return Promise.resolve() - .then(function () { - required(idToken, 'idToken'); - required(clientId, 'clientId'); - }) - .then(function (creds) { - return request.send('/oauth/id-token-verify', 'POST', null, { - id_token: idToken, - client_id: clientId, - }); - }); -}; - -/** - * Create an OAuth code using `sessionToken` - * - * @param {String} sessionToken - * @param {String} clientId - * @param {String} state - * @param {Object} [options={}] Options - * @param {String} [options.access_type=online] if `accessType=offline`, a refresh token - * will be issued when trading the code for an access token. - * @param {String} [options.acr_values] allowed ACR values - * @param {String} [options.keys_jwe] Keys used to encrypt - * @param {String} [options.redirect_uri] registered redirect URI to return to - * @param {String} [options.response_type=code] response type - * @param {String} [options.scope] requested scopes - * @param {String} [options.code_challenge_method] PKCE code challenge method - * @param {String} [options.code_challenge] PKCE code challenge - * @returns {Promise} A promise that will be fulfilled with: - * - `redirect` - redirect URI - * - `code` - authorization code - * - `state` - state token - */ -FxAccountClient.prototype.createOAuthCode = function ( - sessionToken, - clientId, - state, - options -) { - options = options || {}; - - var params = { - access_type: options.access_type, - acr_values: options.acr_values, - client_id: clientId, - code_challenge: options.code_challenge, - code_challenge_method: options.code_challenge_method, - keys_jwe: options.keys_jwe, - redirect_uri: options.redirect_uri, - response_type: options.response_type, - scope: options.scope, - state: state, - }; - var request = this.request; - - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(clientId, 'clientId'); - required(state, 'state'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/oauth/authorization', 'POST', creds, params); - }); -}; - -/** - * Create an OAuth token using `sessionToken` - * - * @param {String} sessionToken - * @param {String} clientId - * @param {Object} [options={}] Options - * @param {String} [options.access_type=online] if `accessType=offline`, a refresh token - * will be issued when trading the code for an access token. - * @param {String} [options.scope] requested scopes - * @param {Number} [options.ttl] time to live, in seconds - * @returns {Promise} A promise that will be fulfilled with: - * - `access_token` - The access token - * - `refresh_token` - A refresh token, if `options.accessType=offline` - * - `id_token` - an OIDC ID token, returned if `scope` includes `openid` - * - `scope` - Requested scopes - * - `auth_at` - Time the user authenticated - * - `token_type` - The string `bearer` - * - `expires_in` - Time at which the token expires - */ -FxAccountClient.prototype.createOAuthToken = function ( - sessionToken, - clientId, - options -) { - options = options || {}; - - var params = { - grant_type: 'fxa-credentials', - access_type: options.access_type, - client_id: clientId, - scope: options.scope, - ttl: options.ttl, - }; - - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(clientId, 'clientId'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/oauth/token', 'POST', creds, params); - }); -}; - -/** - * Use `sessionToken` to get scoped key data for the RP associated with `client_id` - * - * @param {String} sessionToken - * @param {String} clientId - * @param {String} scope - * @returns {Promise} A promise that will be fulfilled with: - * - `identifier` - * - `keyRotationSecret` - * - `keyRotationTimestamp` - */ -FxAccountClient.prototype.getOAuthScopedKeyData = function ( - sessionToken, - clientId, - scope -) { - var params = { - client_id: clientId, - scope: scope, - }; - - var request = this.request; - return Promise.resolve() - .then(function () { - required(sessionToken, 'sessionToken'); - required(clientId, 'clientId'); - required(scope, 'scope'); - - return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE); - }) - .then(function (creds) { - return request.send('/account/scoped-key-data', 'POST', creds, params); - }); -}; - -/** - * Get the list of SubHub plans from the auth server. - * - * @param {String} token An access token from the OAuth server. - * @returns {Promise} A promise that will be fulfilled with a list of subscription plans from SubHub. - */ -FxAccountClient.prototype.getSubscriptionPlans = function (token) { - var self = this; - - return Promise.resolve().then(function () { - required(token, 'token'); - const requestOptions = { - headers: { - Authorization: `Bearer ${token}`, - }, - }; - return self.request.send( - '/oauth/subscriptions/plans', - 'GET', - null, - null, - requestOptions - ); - }); -}; - -/** - * Get a user's list of active subscriptions. - * - * @param {String} token A token from the OAuth server. - * @returns {Promise} A promise that will be fulfilled with a list of active - * subscriptions. - */ -FxAccountClient.prototype.getActiveSubscriptions = function (token) { - var self = this; - - return Promise.resolve().then(function () { - required(token, 'token'); - const requestOptions = { - headers: { - Authorization: `Bearer ${token}`, - }, - }; - return self.request.send( - '/oauth/subscriptions/active', - 'GET', - null, - null, - requestOptions - ); - }); -}; - -/** - * Submit a support ticket. - * - * @param {String} authorizationHeader A token from the OAuth server. - * @param {Object} [supportTicket={}] - * @param {String} [supportTicket.topic] - * @param {String} [supportTicket.subject] Optional subject - * @param {String} [supportTicket.message] - * @returns {Promise} A promise that will be fulfilled with: - * - `success` - * - `ticket` OR `error` - */ -FxAccountClient.prototype.createSupportTicket = function ( - token, - supportTicket -) { - var self = this; - - return Promise.resolve().then(function () { - required(token, 'token'); - required(supportTicket, 'supportTicket'); - const requestOptions = { - headers: { - Authorization: `Bearer ${token}`, - }, - }; - return self.request.send( - '/support/ticket', - 'POST', - null, - supportTicket, - requestOptions - ); - }); -}; - -/** - * Check for a required argument. Exposed for unit testing. - * - * @param {Value} val - value to check - * @param {String} name - name of value - * @throws {Error} if argument is falsey, or an empty object - */ -FxAccountClient.prototype._required = required; - -module.exports = FxAccountClient; diff --git a/packages/fxa-js-client/client/lib/credentials.js b/packages/fxa-js-client/client/lib/credentials.js deleted file mode 100644 index 75e1c017fb..0000000000 --- a/packages/fxa-js-client/client/lib/credentials.js +++ /dev/null @@ -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), - }; - }); - }, -}; diff --git a/packages/fxa-js-client/client/lib/errors.js b/packages/fxa-js-client/client/lib/errors.js deleted file mode 100644 index 35a257d958..0000000000 --- a/packages/fxa-js-client/client/lib/errors.js +++ /dev/null @@ -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, -}; diff --git a/packages/fxa-js-client/client/lib/hawk.js b/packages/fxa-js-client/client/lib/hawk.js deleted file mode 100644 index 8a4b8614a6..0000000000 --- a/packages/fxa-js-client/client/lib/hawk.js +++ /dev/null @@ -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 - 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 - // 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; diff --git a/packages/fxa-js-client/client/lib/hawkCredentials.js b/packages/fxa-js-client/client/lib/hawkCredentials.js deleted file mode 100644 index 5b55c3db10..0000000000 --- a/packages/fxa-js-client/client/lib/hawkCredentials.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/client/lib/hkdf.js b/packages/fxa-js-client/client/lib/hkdf.js deleted file mode 100644 index 6685ee6df3..0000000000 --- a/packages/fxa-js-client/client/lib/hkdf.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/client/lib/metricsContext.js b/packages/fxa-js-client/client/lib/metricsContext.js deleted file mode 100644 index 7768f4e8c2..0000000000 --- a/packages/fxa-js-client/client/lib/metricsContext.js +++ /dev/null @@ -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, - }; - }, -}; diff --git a/packages/fxa-js-client/client/lib/pbkdf2.js b/packages/fxa-js-client/client/lib/pbkdf2.js deleted file mode 100644 index c62e656841..0000000000 --- a/packages/fxa-js-client/client/lib/pbkdf2.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/client/lib/request.js b/packages/fxa-js-client/client/lib/request.js deleted file mode 100644 index 165617b611..0000000000 --- a/packages/fxa-js-client/client/lib/request.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/node/index.js b/packages/fxa-js-client/node/index.js deleted file mode 100644 index 9bf151de2a..0000000000 --- a/packages/fxa-js-client/node/index.js +++ /dev/null @@ -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); diff --git a/packages/fxa-js-client/package.json b/packages/fxa-js-client/package.json deleted file mode 100644 index b267d618bb..0000000000 --- a/packages/fxa-js-client/package.json +++ /dev/null @@ -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" - } -} diff --git a/packages/fxa-js-client/tasks/clean.js b/packages/fxa-js-client/tasks/clean.js deleted file mode 100644 index cbfb3faf2d..0000000000 --- a/packages/fxa-js-client/tasks/clean.js +++ /dev/null @@ -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'], - }); -}; diff --git a/packages/fxa-js-client/tasks/copyright.js b/packages/fxa-js-client/tasks/copyright.js deleted file mode 100644 index e8a3ea2bd8..0000000000 --- a/packages/fxa-js-client/tasks/copyright.js +++ /dev/null @@ -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/**', - ], - }, - }); -}; diff --git a/packages/fxa-js-client/tasks/eslint.js b/packages/fxa-js-client/tasks/eslint.js deleted file mode 100644 index 543aab439d..0000000000 --- a/packages/fxa-js-client/tasks/eslint.js +++ /dev/null @@ -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'], - }, - }); -}; diff --git a/packages/fxa-js-client/tasks/open.js b/packages/fxa-js-client/tasks/open.js deleted file mode 100644 index b8c6db46d6..0000000000 --- a/packages/fxa-js-client/tasks/open.js +++ /dev/null @@ -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', - }, - }); -}; diff --git a/packages/fxa-js-client/tasks/watch.js b/packages/fxa-js-client/tasks/watch.js deleted file mode 100644 index 39018b797e..0000000000 --- a/packages/fxa-js-client/tasks/watch.js +++ /dev/null @@ -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'], - }, - }); -}; diff --git a/packages/fxa-js-client/tasks/webpack.js b/packages/fxa-js-client/tasks/webpack.js deleted file mode 100644 index 8b819966cf..0000000000 --- a/packages/fxa-js-client/tasks/webpack.js +++ /dev/null @@ -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), - }); -}; diff --git a/packages/fxa-js-client/tests/.eslintrc b/packages/fxa-js-client/tests/.eslintrc deleted file mode 100644 index c863816cb8..0000000000 --- a/packages/fxa-js-client/tests/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../.eslintrc", - "plugins": ["fxa"], - "rules": { - "no-console": "off" - } -} diff --git a/packages/fxa-js-client/tests/addons/accountHelper.js b/packages/fxa-js-client/tests/addons/accountHelper.js deleted file mode 100644 index 1b2d31ca48..0000000000 --- a/packages/fxa-js-client/tests/addons/accountHelper.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/tests/addons/environment.js b/packages/fxa-js-client/tests/addons/environment.js deleted file mode 100644 index 7166d63da4..0000000000 --- a/packages/fxa-js-client/tests/addons/environment.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/tests/addons/restmail.js b/packages/fxa-js-client/tests/addons/restmail.js deleted file mode 100644 index 53127c4c7b..0000000000 --- a/packages/fxa-js-client/tests/addons/restmail.js +++ /dev/null @@ -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; diff --git a/packages/fxa-js-client/tests/examples/example.html b/packages/fxa-js-client/tests/examples/example.html deleted file mode 100644 index 5bd3ca30ac..0000000000 --- a/packages/fxa-js-client/tests/examples/example.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - -

fxa-js-client tester

- - - - - - - - -
- -
- - - -
- -
- -
- - - -
- -
- -
- - - - - - - -
- -
- -
- - - - -
-
- See Console.... - - - - - - diff --git a/packages/fxa-js-client/tests/examples/proxy.js b/packages/fxa-js-client/tests/examples/proxy.js deleted file mode 100644 index d60d9a102d..0000000000 --- a/packages/fxa-js-client/tests/examples/proxy.js +++ /dev/null @@ -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' -); diff --git a/packages/fxa-js-client/tests/lib/account.js b/packages/fxa-js-client/tests/lib/account.js deleted file mode 100644 index 8422aed468..0000000000 --- a/packages/fxa-js-client/tests/lib/account.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/attachedClients.js b/packages/fxa-js-client/tests/lib/attachedClients.js deleted file mode 100644 index 9863363e3d..0000000000 --- a/packages/fxa-js-client/tests/lib/attachedClients.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/certificateSign.js b/packages/fxa-js-client/tests/lib/certificateSign.js deleted file mode 100644 index f0b4fdedc0..0000000000 --- a/packages/fxa-js-client/tests/lib/certificateSign.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/credentials.js b/packages/fxa-js-client/tests/lib/credentials.js deleted file mode 100644 index c84100bbbb..0000000000 --- a/packages/fxa-js-client/tests/lib/credentials.js +++ /dev/null @@ -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' - ); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/device.js b/packages/fxa-js-client/tests/lib/device.js deleted file mode 100644 index b5bc9a7f44..0000000000 --- a/packages/fxa-js-client/tests/lib/device.js +++ /dev/null @@ -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); - }); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/emails.js b/packages/fxa-js-client/tests/lib/emails.js deleted file mode 100644 index 7d14120f9a..0000000000 --- a/packages/fxa-js-client/tests/lib/emails.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/errors.js b/packages/fxa-js-client/tests/lib/errors.js deleted file mode 100644 index 9bb4a31183..0000000000 --- a/packages/fxa-js-client/tests/lib/errors.js +++ /dev/null @@ -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); - } - ); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/hawkCredentials.js b/packages/fxa-js-client/tests/lib/hawkCredentials.js deleted file mode 100644 index 463b605e56..0000000000 --- a/packages/fxa-js-client/tests/lib/hawkCredentials.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/headerLang.js b/packages/fxa-js-client/tests/lib/headerLang.js deleted file mode 100644 index 835c4f53cf..0000000000 --- a/packages/fxa-js-client/tests/lib/headerLang.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/hkdf.js b/packages/fxa-js-client/tests/lib/hkdf.js deleted file mode 100644 index 94a75fe686..0000000000 --- a/packages/fxa-js-client/tests/lib/hkdf.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/init.js b/packages/fxa-js-client/tests/lib/init.js deleted file mode 100644 index c463354dee..0000000000 --- a/packages/fxa-js-client/tests/lib/init.js +++ /dev/null @@ -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); - } - }); -}); diff --git a/packages/fxa-js-client/tests/lib/metricsContext.js b/packages/fxa-js-client/tests/lib/metricsContext.js deleted file mode 100644 index c6ec4598d6..0000000000 --- a/packages/fxa-js-client/tests/lib/metricsContext.js +++ /dev/null @@ -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', - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/misc.js b/packages/fxa-js-client/tests/lib/misc.js deleted file mode 100644 index 11edb9eacf..0000000000 --- a/packages/fxa-js-client/tests/lib/misc.js +++ /dev/null @@ -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'); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/oauth.js b/packages/fxa-js-client/tests/lib/oauth.js deleted file mode 100644 index 3ea75b6471..0000000000 --- a/packages/fxa-js-client/tests/lib/oauth.js +++ /dev/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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/passwordChange.js b/packages/fxa-js-client/tests/lib/passwordChange.js deleted file mode 100644 index b01eb1bffa..0000000000 --- a/packages/fxa-js-client/tests/lib/passwordChange.js +++ /dev/null @@ -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'); - } - ); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/recoveryCodes.js b/packages/fxa-js-client/tests/lib/recoveryCodes.js deleted file mode 100644 index f51c7b3c22..0000000000 --- a/packages/fxa-js-client/tests/lib/recoveryCodes.js +++ /dev/null @@ -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' - ); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/recoveryEmail.js b/packages/fxa-js-client/tests/lib/recoveryEmail.js deleted file mode 100644 index 3511563472..0000000000 --- a/packages/fxa-js-client/tests/lib/recoveryEmail.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/recoveryKeys.js b/packages/fxa-js-client/tests/lib/recoveryKeys.js deleted file mode 100644 index 3ff7f0a2e4..0000000000 --- a/packages/fxa-js-client/tests/lib/recoveryKeys.js +++ /dev/null @@ -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' - ); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/request.js b/packages/fxa-js-client/tests/lib/request.js deleted file mode 100644 index 7c39ad8208..0000000000 --- a/packages/fxa-js-client/tests/lib/request.js +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/securityEvents.js b/packages/fxa-js-client/tests/lib/securityEvents.js deleted file mode 100644 index 8bfbbca61a..0000000000 --- a/packages/fxa-js-client/tests/lib/securityEvents.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/session.js b/packages/fxa-js-client/tests/lib/session.js deleted file mode 100644 index 9114f057ee..0000000000 --- a/packages/fxa-js-client/tests/lib/session.js +++ /dev/null @@ -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'); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/signIn.js b/packages/fxa-js-client/tests/lib/signIn.js deleted file mode 100644 index 7787d85bdb..0000000000 --- a/packages/fxa-js-client/tests/lib/signIn.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/signUp.js b/packages/fxa-js-client/tests/lib/signUp.js deleted file mode 100644 index 6f36cfb418..0000000000 --- a/packages/fxa-js-client/tests/lib/signUp.js +++ /dev/null @@ -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, {}); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/signinCodes.js b/packages/fxa-js-client/tests/lib/signinCodes.js deleted file mode 100644 index 66e11679b1..0000000000 --- a/packages/fxa-js-client/tests/lib/signinCodes.js +++ /dev/null @@ -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'); - } - ); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/sms.js b/packages/fxa-js-client/tests/lib/sms.js deleted file mode 100644 index 2d53d251e4..0000000000 --- a/packages/fxa-js-client/tests/lib/sms.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/subscriptions.js b/packages/fxa-js-client/tests/lib/subscriptions.js deleted file mode 100644 index f37bada571..0000000000 --- a/packages/fxa-js-client/tests/lib/subscriptions.js +++ /dev/null @@ -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); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/totp.js b/packages/fxa-js-client/tests/lib/totp.js deleted file mode 100644 index bd2d4a36e3..0000000000 --- a/packages/fxa-js-client/tests/lib/totp.js +++ /dev/null @@ -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); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/unbundle.js b/packages/fxa-js-client/tests/lib/unbundle.js deleted file mode 100644 index 023f735089..0000000000 --- a/packages/fxa-js-client/tests/lib/unbundle.js +++ /dev/null @@ -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' - ); - }); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/uriVersion.js b/packages/fxa-js-client/tests/lib/uriVersion.js deleted file mode 100644 index 5bbe0b80f8..0000000000 --- a/packages/fxa-js-client/tests/lib/uriVersion.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/lib/verifyCode.js b/packages/fxa-js-client/tests/lib/verifyCode.js deleted file mode 100644 index 4c64ed99e8..0000000000 --- a/packages/fxa-js-client/tests/lib/verifyCode.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/fxa-js-client/tests/mocks/errors.js b/packages/fxa-js-client/tests/mocks/errors.js deleted file mode 100644 index 084dc6fd72..0000000000 --- a/packages/fxa-js-client/tests/mocks/errors.js +++ /dev/null @@ -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: 'Something is wrong.', - }, - signInBlocked: { - status: 429, - headers: {}, - body: JSON.stringify({ - code: 429, - errno: 125, - verificationMethod: 'email-captcha', - verificationReason: 'login', - }), - }, - signInInvalidUnblockCode: { - status: 400, - body: '{"code":400, "errno": 127}', - }, -}; diff --git a/packages/fxa-js-client/tests/mocks/pushConstants.js b/packages/fxa-js-client/tests/mocks/pushConstants.js deleted file mode 100644 index 5e7acdcbff..0000000000 --- a/packages/fxa-js-client/tests/mocks/pushConstants.js +++ /dev/null @@ -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', -}; diff --git a/packages/fxa-js-client/tests/mocks/request.js b/packages/fxa-js-client/tests/mocks/request.js deleted file mode 100644 index d23a49266d..0000000000 --- a/packages/fxa-js-client/tests/mocks/request.js +++ /dev/null @@ -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"}', - }, -}; diff --git a/packages/fxa-js-client/webpack.config.js b/packages/fxa-js-client/webpack.config.js deleted file mode 100644 index 8d500d382e..0000000000 --- a/packages/fxa-js-client/webpack.config.js +++ /dev/null @@ -1,43 +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/. */ - -/* eslint-disable */ -const path = require('path'); -const webpack = require('webpack'); - -module.exports = { - context: path.resolve(__dirname), - entry: { - 'fxa-client': './client/FxAccountClient', - }, - - output: { - filename: '[name].js', - library: 'FxAccountClient', - libraryTarget: 'umd', - path: path.resolve(__dirname, 'build'), - publicPath: '/', - }, - - resolve: { - extensions: ['.js'], - modules: [path.resolve(__dirname, 'client')], - alias: { - sjcl: require.resolve('sjcl/sjcl'), - }, - }, - - node: { - global: true, - process: false, - Buffer: false, - __filename: false, - __dirname: false, - setImmediate: false, - }, - - module: {}, - stats: { colors: true }, -}; -/* eslint-enable */ diff --git a/types/fxa-js-client/index.d.ts b/types/fxa-js-client/index.d.ts deleted file mode 100644 index f308205531..0000000000 --- a/types/fxa-js-client/index.d.ts +++ /dev/null @@ -1,79 +0,0 @@ -declare module 'fxa-js-client' { - export = FxAccountClient; -} - -declare function FxAccountClient( - uri: string, - config: any -): FxAccountClient.Client; - -declare namespace FxAccountClient { - export type MetricContext = { - deviceId?: string; - entrypoint?: string; - entrypointExperiment?: string; - entrypointVariation?: string; - flowId?: string; - flowBeginTime?: number; - productId?: string; - planId?: string; - utmCampaign?: number; - utmContent?: number; - utmMedium?: number; - utmSource?: number; - utmTerm?: number; - }; - - export interface Client { - sessionStatus( - sessionToken: string - ): Promise<{ state: string; uid: string }>; - attachedClients(sessionToken: string): Promise; - attachedClientDestroy(sessionToken: string, clientInfo: any): Promise; - checkTotpTokenExists( - sessionToken: string - ): Promise<{ exists: boolean; verified: boolean }>; - recoveryKeyExists(sessionToken: string): Promise<{ exists: boolean }>; - account(sessionToken: string): Promise; - createOAuthToken( - sessionToken: string, - clientId: string, - options?: { - scope?: string; - ttl?: number; - access_type?: 'online' | 'offline'; - } - ): Promise<{ - access_token: string; - refresh_token?: string; - id_token?: string; - scope: string[]; - auth_at?: number; - token_type: string; - expires_in: number; - }>; - createTotpToken( - sessionToken: string, - metricOptions: MetricContext - ): Promise<{ qrCodeUrl: string; secret: string; recoveryCodes: string[] }>; - deleteTotpToken(sessionToken: string): Promise; - recoveryEmailCreate(sessionToken: string, email: string): Promise; - recoveryEmailDestroy(sessionToken: string, email: string): Promise; - recoveryEmailSetPrimaryEmail( - sessionToken: string, - email: string - ): Promise; - recoveryEmailSecondaryResendCode( - sessionToken: string, - email: string - ): Promise; - replaceRecoveryCodes( - sessionToken: string - ): Promise<{ recoveryCodes: string[] }>; - verifyTotpCode( - sessionToken: string, - code: string, - options?: { service: string } - ): Promise<{ success: boolean }>; - } -} diff --git a/yarn.lock b/yarn.lock index 32401c7863..6cb6b676bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2899,6 +2899,40 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-schema@npm:^2.0.1, @peculiar/asn1-schema@npm:^2.0.8": + version: 2.0.8 + resolution: "@peculiar/asn1-schema@npm:2.0.8" + dependencies: + "@types/asn1js": ^0.0.1 + asn1js: ^2.0.26 + pvtsutils: ^1.0.10 + tslib: ^1.11.1 + checksum: 2a49030fb9d31ae83292fae43c9a3bc05e6ab86201ca4360a0a156e194976466f8c7808d12d8c4861630b6c6991eb89cfbec7adb2d3251f56cc86601d2ed0df8 + languageName: node + linkType: hard + +"@peculiar/json-schema@npm:^1.1.10": + version: 1.1.10 + resolution: "@peculiar/json-schema@npm:1.1.10" + dependencies: + tslib: ^1.11.1 + checksum: c2ed4599865f2acf877cffccb37845405a185ce1ae96efe47763ad2595d011dc5f9da83fd2689418a7e2691c06105ec43008dff7f32603583812876d8685508e + languageName: node + linkType: hard + +"@peculiar/webcrypto@npm:^1.1.2": + version: 1.1.2 + resolution: "@peculiar/webcrypto@npm:1.1.2" + dependencies: + "@peculiar/asn1-schema": ^2.0.8 + "@peculiar/json-schema": ^1.1.10 + pvtsutils: ^1.0.10 + tslib: ^2.0.0 + webcrypto-core: ^1.1.2 + checksum: 98d898d9d97201795878b716aa6017a7e87c3b0a59830a1f691280fd6564923ac93d69bffde633e0cd04e70a368071690bbda16c417a7bf8be1e09675ce6d8d4 + languageName: node + linkType: hard + "@pm2/agent-node@npm:^1.1.10": version: 1.1.10 resolution: "@pm2/agent-node@npm:1.1.10" @@ -4130,6 +4164,22 @@ __metadata: languageName: node linkType: hard +"@types/asn1js@npm:^0.0.1": + version: 0.0.1 + resolution: "@types/asn1js@npm:0.0.1" + dependencies: + "@types/pvutils": "*" + checksum: 8ab342eec3dc1b3fa0e97f0ee339726eec2ec0328f2d314dba5955e3ee9db590e5e251a89822c5215c286c664caa165968c547d666edbbc3e18caeb30061c749 + languageName: node + linkType: hard + +"@types/assert@npm:^1.5.1": + version: 1.5.1 + resolution: "@types/assert@npm:1.5.1" + checksum: 299654afd3357bee6688ee914c0ba46bb087ed4e1c288c1f01a829a64b735e3b6b2c4b7ee9c8d3fbaff1b33be8ddeae2c1d75d990afb388df84ad00b76ef4e98 + languageName: node + linkType: hard + "@types/babel-types@npm:*": version: 7.0.7 resolution: "@types/babel-types@npm:7.0.7" @@ -4407,6 +4457,13 @@ __metadata: languageName: node linkType: hard +"@types/fast-text-encoding@npm:^1": + version: 1.0.1 + resolution: "@types/fast-text-encoding@npm:1.0.1" + checksum: 6f0b73c4955d01fc75d7d83e315cd28bfcc64d8467c791ec19e5d9ab9f26dc329f02e8dfc0154cc2fb0ad5fd0c77a99b6f64bb8f00b137b0363c3444a5399009 + languageName: node + linkType: hard + "@types/file-loader@npm:^4.2.0": version: 4.2.0 resolution: "@types/file-loader@npm:4.2.0" @@ -4876,7 +4933,7 @@ __metadata: languageName: node linkType: hard -"@types/mocha@npm:^7.0.2": +"@types/mocha@npm:^7, @types/mocha@npm:^7.0.2": version: 7.0.2 resolution: "@types/mocha@npm:7.0.2" checksum: a3b2b5b2ed45e417c6292de33678408093a96bc26dc78d4f28446baa7f629e0945557325ada9f3f56efc44e1cee5f4f04e950da4ff316f2bea58106892de96da @@ -4892,7 +4949,7 @@ __metadata: languageName: node linkType: hard -"@types/node-fetch@npm:2.5.7": +"@types/node-fetch@npm:2.5.7, @types/node-fetch@npm:^2": version: 2.5.7 resolution: "@types/node-fetch@npm:2.5.7" dependencies: @@ -4967,6 +5024,13 @@ __metadata: languageName: node linkType: hard +"@types/pvutils@npm:*": + version: 0.0.2 + resolution: "@types/pvutils@npm:0.0.2" + checksum: 513310de90160dc827bc3e34394fa84750525540b458bf073c7586ddc7fb36674f4d8928da1fcf865099e92ba34e61c35fcef786e5369760ea70bdaf061d3dda + languageName: node + linkType: hard + "@types/q@npm:^1.5.1": version: 1.5.2 resolution: "@types/q@npm:1.5.2" @@ -7245,6 +7309,15 @@ __metadata: languageName: node linkType: hard +"asn1js@npm:^2.0.26": + version: 2.0.26 + resolution: "asn1js@npm:2.0.26" + dependencies: + pvutils: latest + checksum: f2606ad70206df0184c186ef49ae3cb6d1591a50e37ef515830252163698ea8889c04d96c4d3bb80ae7226ac7632cd84b32be9a643edef23393c7bbd452b6afa + languageName: node + linkType: hard + "assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0": version: 1.0.0 resolution: "assert-plus@npm:1.0.0" @@ -7424,7 +7497,7 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.0.0, async@npm:^2.5.0, async@npm:^2.6.0, async@npm:^2.6.1, async@npm:^2.6.2, async@npm:^2.6.3, async@npm:~2.6.1": +"async@npm:^2.0.0, async@npm:^2.5.0, async@npm:^2.6.1, async@npm:^2.6.2, async@npm:^2.6.3, async@npm:~2.6.1": version: 2.6.3 resolution: "async@npm:2.6.3" dependencies: @@ -8616,18 +8689,6 @@ __metadata: languageName: node linkType: hard -"body@npm:^5.1.0": - version: 5.1.0 - resolution: "body@npm:5.1.0" - dependencies: - continuable-cache: ^0.3.1 - error: ^7.0.0 - raw-body: ~1.1.0 - safe-json-parse: ~1.0.1 - checksum: d9694787950ff7c54ec884214599cfe6a4b1be49d0060cdda6104a1c02563328d032dda26c96af3ec99c4a3a43994c7b36b8d97153e613365535e0b15c9b4dce - languageName: node - linkType: hard - "bonjour@npm:^3.5.0": version: 3.5.0 resolution: "bonjour@npm:3.5.0" @@ -9280,13 +9341,6 @@ __metadata: languageName: node linkType: hard -"bytes@npm:1": - version: 1.0.0 - resolution: "bytes@npm:1.0.0" - checksum: ece30287a729bcd942822049ac83ea716c1f59eb3ad27af66fb22ca8c1ff62fd4547b574091e7f784e91f892df896ec978413133c85505a732da1998886d4258 - languageName: node - linkType: hard - "bytes@npm:3.0.0": version: 3.0.0 resolution: "bytes@npm:3.0.0" @@ -10709,13 +10763,6 @@ __metadata: languageName: node linkType: hard -"continuable-cache@npm:^0.3.1": - version: 0.3.1 - resolution: "continuable-cache@npm:0.3.1" - checksum: cf910530ab67b6fc8d570123a93f23fc52d8f84635e5369a3f9f536d421b33dee16c1e4bf41cfe4261d71a0c734864ee799fac41a5e4d087aa33ba8fe28217ad - languageName: node - linkType: hard - "continuation-local-storage@npm:^3.2.1": version: 3.2.1 resolution: "continuation-local-storage@npm:3.2.1" @@ -11890,15 +11937,6 @@ __metadata: languageName: node linkType: hard -"deep-for-each@npm:^2.0.2": - version: 2.0.3 - resolution: "deep-for-each@npm:2.0.3" - dependencies: - lodash.isplainobject: ^4.0.6 - checksum: fe67908bfbffdb53447fe0381572c35163eda7b9cf3b1a0ee2dfd3e044408121c80b70c086d00d54e40eafd53069ecb431df90a0794d1fa54ab31b864734d1b5 - languageName: node - linkType: hard - "deep-is@npm:~0.1.3": version: 0.1.3 resolution: "deep-is@npm:0.1.3" @@ -12953,15 +12991,6 @@ __metadata: languageName: node linkType: hard -"error@npm:^7.0.0": - version: 7.2.1 - resolution: "error@npm:7.2.1" - dependencies: - string-template: ~0.2.1 - checksum: 21ba37fe6750a1a7357509941983af66fa5e3fbe74eca96d48573878916e38fbe77db4d11374c26cd49b3b091ed9e74dc21983fbb86b564989664b1b87ab3003 - languageName: node - linkType: hard - "es-abstract@npm:^1.17.0, es-abstract@npm:^1.17.0-next.0, es-abstract@npm:^1.17.0-next.1, es-abstract@npm:^1.17.4, es-abstract@npm:^1.17.5, es-abstract@npm:^1.5.0": version: 1.17.5 resolution: "es-abstract@npm:1.17.5" @@ -14032,7 +14061,7 @@ __metadata: languageName: node linkType: hard -"faye-websocket@npm:^0.10.0, faye-websocket@npm:~0.10.0": +"faye-websocket@npm:^0.10.0": version: 0.10.0 resolution: "faye-websocket@npm:0.10.0" dependencies: @@ -15202,6 +15231,26 @@ fsevents@^1.2.7: languageName: unknown linkType: soft +"fxa-auth-client@workspace:*, fxa-auth-client@workspace:packages/fxa-auth-client": + version: 0.0.0-use.local + resolution: "fxa-auth-client@workspace:packages/fxa-auth-client" + dependencies: + "@peculiar/webcrypto": ^1.1.2 + "@types/assert": ^1.5.1 + "@types/fast-text-encoding": ^1 + "@types/mocha": ^7 + "@types/node-fetch": ^2 + abab: ^2.0.0 + asmcrypto.js: ^0.22.0 + fast-text-encoding: ^1.0.0 + mocha: ^7.1.2 + node-fetch: ^2.6.0 + ts-node: ^8.10.1 + typescript: 3.8.3 + webcrypto-liner: "git://github.com/mozilla-fxa/webcrypto-liner.git#6b3ad971b3b1f0d4da3855c6ceee9b3afa9f0eeb" + languageName: unknown + linkType: soft + "fxa-auth-db-mysql@workspace:*, fxa-auth-db-mysql@workspace:packages/fxa-auth-db-mysql": version: 0.0.0-use.local resolution: "fxa-auth-db-mysql@workspace:packages/fxa-auth-db-mysql" @@ -15417,6 +15466,7 @@ fsevents@^1.2.7: fast-text-encoding: ^1.0.1 file-loader: ^4.3.0 firefox-profile: 1.2.0 + fxa-auth-client: "workspace:*" fxa-common-password-list: 0.0.4 fxa-crypto-relier: 2.3.0 fxa-geodb: "workspace:*" @@ -15719,7 +15769,7 @@ fsevents@^1.2.7: convict-format-with-moment: ^6.0.0 convict-format-with-validator: ^6.0.0 eslint: ^6.8.0 - fxa-js-client: ^1.0.25 + fxa-auth-client: "workspace:*" fxa-shared: "workspace:*" get-stream: ^5.1.0 graphql: ^14.6.0 @@ -15749,35 +15799,6 @@ fsevents@^1.2.7: languageName: unknown linkType: soft -"fxa-js-client@^1.0.25, fxa-js-client@workspace:packages/fxa-js-client": - version: 0.0.0-use.local - resolution: "fxa-js-client@workspace:packages/fxa-js-client" - dependencies: - 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 - sjcl: "git://github.com/mozilla-fxa/sjcl.git#3d45d88ed9eaac98d88e7ff83e505db6896dd8c1" - webpack: ^4.41.3 - xhr2: 0.0.7 - languageName: unknown - linkType: soft - "fxa-jwtool@npm:0.7.2, fxa-jwtool@npm:0.7.x, fxa-jwtool@npm:^0.7.2": version: 0.7.2 resolution: "fxa-jwtool@npm:0.7.2" @@ -16248,7 +16269,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"gaze@npm:^1.0.0, gaze@npm:^1.1.0": +"gaze@npm:^1.0.0": version: 1.1.3 resolution: "gaze@npm:1.1.3" dependencies: @@ -17070,19 +17091,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"grunt-contrib-watch@npm:^1.1.0": - version: 1.1.0 - resolution: "grunt-contrib-watch@npm:1.1.0" - dependencies: - async: ^2.6.0 - gaze: ^1.1.0 - lodash: ^4.17.10 - tiny-lr: ^1.1.1 - checksum: a297e1528aa19aee651b8e4c4429c5b7ea01b8aa12af568fe3500a26fade4f530306318f24d020e3305c97932d0d7d2dc66b28454666baf8dd0c17fbfc161e28 - languageName: node - linkType: hard - -"grunt-copyright@npm:0.3.0, grunt-copyright@npm:^0.3.0": +"grunt-copyright@npm:0.3.0": version: 0.3.0 resolution: "grunt-copyright@npm:0.3.0" peerDependencies: @@ -17187,17 +17196,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"grunt-open@npm:0.2.4": - version: 0.2.4 - resolution: "grunt-open@npm:0.2.4" - dependencies: - opn: ^5.4.0 - bin: - grunt-open: bin/grunt-open - checksum: 55371f49128dbde6a57bf18007de908862b16237ce2f9703e8001fddee19de284c4ba43b0689d6da54a059c1fb38e8bd4a4c367209c5ba892f4b34ea0d6f1939 - languageName: node - linkType: hard - "grunt-po2json@git://github.com/mozilla-fxa/grunt-po2json.git#2f415c8ac0435bf8942dc7131c3916ecd1684a46": version: 0.2.0 resolution: "grunt-po2json@git://github.com/mozilla-fxa/grunt-po2json.git#commit=2f415c8ac0435bf8942dc7131c3916ecd1684a46" @@ -17262,18 +17260,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"grunt-webpack@npm:^3.1.3": - version: 3.1.3 - resolution: "grunt-webpack@npm:3.1.3" - dependencies: - deep-for-each: ^2.0.2 - lodash: ^4.7.0 - peerDependencies: - webpack: ^2.0.0 || ^3.0.0 || ^4.0.0 - checksum: d4067676aff19205a6fece03fd960dc53c1717237dc12eea5380888244baa627e7ab1ed06a3feb847f2dfa97420bde58e01a0777b05195cfe80d00e4e666997f - languageName: node - linkType: hard - "grunt-z-schema@npm:0.1.0": version: 0.1.0 resolution: "grunt-z-schema@npm:0.1.0" @@ -21399,13 +21385,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"livereload-js@npm:^2.3.0": - version: 2.4.0 - resolution: "livereload-js@npm:2.4.0" - checksum: fb2bb8160cb3c2cb60ab5c998b640a60d1b3c669f1d23700c9b96bc615d26dc93aa07b711c40b9ebdbe669b6d19d2ed692b6192b575efc638abffd762dcbb0b8 - languageName: node - linkType: hard - "load-grunt-tasks@npm:^5.1.0": version: 5.1.0 resolution: "load-grunt-tasks@npm:5.1.0" @@ -21928,7 +21907,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"lodash@npm:>=3.5 <5, lodash@npm:^4.0.0, lodash@npm:^4.13.1, lodash@npm:^4.14.0, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.4, lodash@npm:^4.17.5, lodash@npm:^4.7.0, lodash@npm:^4.8.0, lodash@npm:~4.17.10, lodash@npm:~4.17.12, lodash@npm:~4.17.15, lodash@npm:~4.17.2, lodash@npm:~4.17.5": +"lodash@npm:>=3.5 <5, lodash@npm:^4.0.0, lodash@npm:^4.13.1, lodash@npm:^4.14.0, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.4, lodash@npm:^4.17.5, lodash@npm:^4.8.0, lodash@npm:~4.17.10, lodash@npm:~4.17.12, lodash@npm:~4.17.15, lodash@npm:~4.17.2, lodash@npm:~4.17.5": version: 4.17.19 resolution: "lodash@npm:4.17.19" checksum: ff2b7a95f0129dba9101e346d44e0eda0f159d76bbbf23721eec1969b87a32bde3de0cfef0733218c64620e9be08040a973278d46a686540233b356115f3527c @@ -24329,7 +24308,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"opn@npm:^5.4.0, opn@npm:^5.5.0": +"opn@npm:^5.5.0": version: 5.5.0 resolution: "opn@npm:5.5.0" dependencies: @@ -27119,6 +27098,22 @@ fsevents@^1.2.7: languageName: node linkType: hard +"pvtsutils@npm:^1.0.10": + version: 1.0.10 + resolution: "pvtsutils@npm:1.0.10" + dependencies: + tslib: ^1.10.0 + checksum: 4409c65452d3a4e8c4111315a1b562c62ea0aa66fe47434691ff72834208cb79c0421a3aa137c2bc1e04633aab8141a072f19251cc1a5dd7006c24bff603977a + languageName: node + linkType: hard + +"pvutils@npm:latest": + version: 1.0.17 + resolution: "pvutils@npm:1.0.17" + checksum: dac525a0a4652ee215fa3582c2735ecb28ab4362079158e85e464e8f1a995f8469ebd668dc3b956a494fdba52a27ade121921a421d83818723e7c55cfdeb6afa + languageName: node + linkType: hard + "q@npm:^1.1.2": version: 1.5.1 resolution: "q@npm:1.5.1" @@ -27157,7 +27152,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.5.1, qs@npm:^6.6.0, qs@npm:^6.7.0, qs@npm:^6.9.1, qs@npm:^6.9.3": +"qs@npm:^6.5.1, qs@npm:^6.6.0, qs@npm:^6.7.0, qs@npm:^6.9.1, qs@npm:^6.9.3": version: 6.9.4 resolution: "qs@npm:6.9.4" checksum: beba62d1e1c66e9888cc08a488eb95771c252a92289a96bff4b767a49e4e7afe9487d7ee3269b856292d8281b855ac5eac927c2e77f845e407ee4fe54743fea7 @@ -27313,16 +27308,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"raw-body@npm:~1.1.0": - version: 1.1.7 - resolution: "raw-body@npm:1.1.7" - dependencies: - bytes: 1 - string_decoder: 0.10 - checksum: fe7b4c0d0f789806a5bd5900a32dc9da45ef4210ee85ae0b5952f7f188d24a9c21ceaf849c94711b78439adbd62797c75d7953f4eea3a6dd433770a6d3f869ef - languageName: node - linkType: hard - "raw-loader@npm:^3.1.0": version: 3.1.0 resolution: "raw-loader@npm:3.1.0" @@ -29187,13 +29172,6 @@ resolve@~1.11.1: languageName: node linkType: hard -"safe-json-parse@npm:~1.0.1": - version: 1.0.1 - resolution: "safe-json-parse@npm:1.0.1" - checksum: 5f6c8cd51bacfaf1788903dd52945d5c8033a15f243d523e3f61d0d491df7107c2c7d2701e51e9be91819aaf40f493bd95dc19a044d09b61f0e0efef67c588b7 - languageName: node - linkType: hard - "safe-json-stringify@npm:^1.0.4, safe-json-stringify@npm:~1": version: 1.2.0 resolution: "safe-json-stringify@npm:1.2.0" @@ -30073,13 +30051,6 @@ resolve@~1.11.1: languageName: node linkType: hard -"sjcl@git://github.com/mozilla-fxa/sjcl.git#3d45d88ed9eaac98d88e7ff83e505db6896dd8c1": - version: 1.0.1 - resolution: "sjcl@git://github.com/mozilla-fxa/sjcl.git#commit=3d45d88ed9eaac98d88e7ff83e505db6896dd8c1" - checksum: 8e153c4521f3a073b99738e8898a4b5e56daa63cb5d0bfd9fe5acaab3e6ce6e779551621a5e7ea09a7d5c683af451ca7651664ab662a333bec68692a04c9db2a - languageName: node - linkType: hard - "slash@npm:^1.0.0": version: 1.0.0 resolution: "slash@npm:1.0.0" @@ -30938,13 +30909,6 @@ resolve@~1.11.1: languageName: node linkType: hard -"string-template@npm:~0.2.1": - version: 0.2.1 - resolution: "string-template@npm:0.2.1" - checksum: c64ac6e7f744d8e37961c987c70038ae4e3ab3c6c9b3822709c1350397f7f0be1e84685924afb44468484823b4e1a8b5d8d4ff925cb0a01b13d8b73e50670513 - languageName: node - linkType: hard - "string-width@npm:^1.0.1, string-width@npm:^1.0.2": version: 1.0.2 resolution: "string-width@npm:1.0.2" @@ -31064,13 +31028,6 @@ resolve@~1.11.1: languageName: node linkType: hard -"string_decoder@npm:0.10, string_decoder@npm:~0.10.0, string_decoder@npm:~0.10.x": - version: 0.10.31 - resolution: "string_decoder@npm:0.10.31" - checksum: ae53bca3796913fe686c6b519299a3631d04f0d388f35e7412914e3d351024f711d783d0415babfec276f5f533e84fae687e77220829d872fadb5bb9f7190890 - languageName: node - linkType: hard - "string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -31080,6 +31037,13 @@ resolve@~1.11.1: languageName: node linkType: hard +"string_decoder@npm:~0.10.0, string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: ae53bca3796913fe686c6b519299a3631d04f0d388f35e7412914e3d351024f711d783d0415babfec276f5f533e84fae687e77220829d872fadb5bb9f7190890 + languageName: node + linkType: hard + "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -32102,20 +32066,6 @@ resolve@~1.11.1: languageName: node linkType: hard -"tiny-lr@npm:^1.1.1": - version: 1.1.1 - resolution: "tiny-lr@npm:1.1.1" - dependencies: - body: ^5.1.0 - debug: ^3.1.0 - faye-websocket: ~0.10.0 - livereload-js: ^2.3.0 - object-assign: ^4.1.0 - qs: ^6.4.0 - checksum: e3d6d958da297eb3cc27cedd94362f2dd681902cb930119a50c0c7768b8b33a466f46e06a67286b9c1bebdbf132ce390251ae618d8b3e84bbc002745a3c560d3 - languageName: node - linkType: hard - "tiny-lru@npm:^1.6.1": version: 1.6.4 resolution: "tiny-lru@npm:1.6.4" @@ -32511,7 +32461,7 @@ resolve@~1.11.1: languageName: node linkType: hard -"tslib@npm:1.12.0, tslib@npm:>=1.9.0, tslib@npm:^1.10.0, tslib@npm:^1.11.1, tslib@npm:^1.7.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:1.12.0, tslib@npm:>=1.9.0, tslib@npm:^1.10.0, tslib@npm:^1.11.1, tslib@npm:^1.11.2, tslib@npm:^1.7.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.12.0 resolution: "tslib@npm:1.12.0" checksum: a2f020c1187db465fbfb1c6d7d5dcd1a56aa08153d868b614f36fd59ef1225e938288a7cc8c593562faf5a681c53caccc6b580b06b7618a6670830079117f803 @@ -32525,6 +32475,13 @@ resolve@~1.11.1: languageName: node linkType: hard +"tslib@npm:^2.0.0": + version: 2.0.0 + resolution: "tslib@npm:2.0.0" + checksum: a7369a224f12e223fb42f2a720389601a24a1e1c96c55bf0d8d4b60c131e574c175ae23578b8d1bd3f4ec790c7e0a82b43733f022f866d48a23aeadd3910755d + languageName: node + linkType: hard + "tsutils@npm:^3.17.1": version: 3.17.1 resolution: "tsutils@npm:3.17.1" @@ -33730,6 +33687,19 @@ typescript@3.8.3: languageName: node linkType: hard +"webcrypto-core@npm:^1.1.2": + version: 1.1.2 + resolution: "webcrypto-core@npm:1.1.2" + dependencies: + "@peculiar/asn1-schema": ^2.0.1 + "@peculiar/json-schema": ^1.1.10 + asn1js: ^2.0.26 + pvtsutils: ^1.0.10 + tslib: ^1.11.2 + checksum: 535d2f473ae4ecc8e816b6d0d7018328779f91cc477efe00aa1a47322ce40b42aa9f62280e90147b134aac441cabd33f13aa1fa9a2fb2c4faf41b6619c338f20 + languageName: node + linkType: hard + "webcrypto-liner@git://github.com/mozilla-fxa/webcrypto-liner.git#6b3ad971b3b1f0d4da3855c6ceee9b3afa9f0eeb": version: 0.1.36 resolution: "webcrypto-liner@git://github.com/mozilla-fxa/webcrypto-liner.git#commit=6b3ad971b3b1f0d4da3855c6ceee9b3afa9f0eeb" @@ -33937,7 +33907,7 @@ typescript@3.8.3: languageName: node linkType: hard -"webpack@npm:4.43.0, webpack@npm:^4.33.0, webpack@npm:^4.38.0, webpack@npm:^4.41.2, webpack@npm:^4.41.3, webpack@npm:^4.43.0": +"webpack@npm:4.43.0, webpack@npm:^4.33.0, webpack@npm:^4.38.0, webpack@npm:^4.41.2, webpack@npm:^4.43.0": version: 4.43.0 resolution: "webpack@npm:4.43.0" dependencies: @@ -34561,13 +34531,6 @@ typescript@3.8.3: languageName: node linkType: hard -"xhr2@npm:0.0.7": - version: 0.0.7 - resolution: "xhr2@npm:0.0.7" - checksum: 9b0fb9ccf960215239bcb92c36cea4a22d1167cb32af57fcd310bf0d357e3b00abc857f48160938295c154320d103f32b75f8ebd56cf2f18b01ad0e3e5386a93 - languageName: node - linkType: hard - "xml-name-validator@npm:^3.0.0": version: 3.0.0 resolution: "xml-name-validator@npm:3.0.0"