forked from mirrors/gecko-dev
Backed out changeset 74922ea29d44 (bug 1490671) for browser-chrome failures in browser/components/preferences/in-content/tests/browser_sync_pairing.js
This commit is contained in:
parent
4b76f8a9eb
commit
6e4967f76e
28 changed files with 64 additions and 5061 deletions
|
|
@ -294,9 +294,6 @@ security/nss/**
|
|||
|
||||
# services/ exclusions
|
||||
|
||||
# Webpack-bundled library
|
||||
services/fxaccounts/FxAccountsPairingChannel.js
|
||||
|
||||
# Uses `#filter substitution`
|
||||
services/sync/modules/constants.js
|
||||
services/sync/services-sync.js
|
||||
|
|
|
|||
|
|
@ -1416,12 +1416,6 @@ pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox
|
|||
// The remote URL of the FxA OAuth Server
|
||||
pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
|
||||
|
||||
// Whether FxA pairing using QR codes is enabled.
|
||||
pref("identity.fxaccounts.pairing.enabled", false);
|
||||
|
||||
// The remote URI of the FxA pairing server
|
||||
pref("identity.fxaccounts.remote.pairing.uri", "wss://channelserver.services.mozilla.com");
|
||||
|
||||
// Token server used by the FxA Sync identity.
|
||||
pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,98 +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 {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {FxAccounts} = ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
|
||||
const {Weave} = ChromeUtils.import("resource://services-sync/main.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
EventEmitter: "resource://gre/modules/EventEmitter.jsm",
|
||||
FxAccountsPairingFlow: "resource://gre/modules/FxAccountsPairing.jsm",
|
||||
});
|
||||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const QR = require("devtools/shared/qrcode/index");
|
||||
|
||||
// This is only for "labor illusion", see
|
||||
// https://www.fastcompany.com/3061519/the-ux-secret-that-will-ruin-apps-for-you
|
||||
const MIN_PAIRING_LOADING_TIME_MS = 1000;
|
||||
|
||||
/**
|
||||
* Communication between FxAccountsPairingFlow and gFxaPairDeviceDialog
|
||||
* is done using an emitter via the following messages:
|
||||
* <- [view:SwitchToWebContent] - Notifies the view to navigate to a specific URL.
|
||||
* <- [view:Error] - Notifies the view something went wrong during the pairing process.
|
||||
* -> [view:Closed] - Notifies the pairing module the view was closed.
|
||||
*/
|
||||
var gFxaPairDeviceDialog = {
|
||||
init() {
|
||||
this._resetBackgroundQR();
|
||||
FxAccounts.config.promiseConnectDeviceURI("pairing-modal").then(connectURI => {
|
||||
document.getElementById("connect-another-device-link").setAttribute("href", connectURI);
|
||||
});
|
||||
// We let the modal show itself before eventually showing a master-password dialog later.
|
||||
Services.tm.dispatchToMainThread(() => this.startPairingFlow());
|
||||
},
|
||||
|
||||
uninit() {
|
||||
this.teardownListeners();
|
||||
this._emitter.emit("view:Closed");
|
||||
},
|
||||
|
||||
async startPairingFlow() {
|
||||
this._resetBackgroundQR();
|
||||
document.getElementById("qrWrapper").setAttribute("pairing-status", "loading");
|
||||
this._emitter = new EventEmitter();
|
||||
this.setupListeners();
|
||||
try {
|
||||
if (!Weave.Utils.ensureMPUnlocked()) {
|
||||
throw new Error("Master-password locked.");
|
||||
}
|
||||
const [, uri] = await Promise.all([
|
||||
new Promise(res => setTimeout(res, MIN_PAIRING_LOADING_TIME_MS)),
|
||||
FxAccountsPairingFlow.start({emitter: this._emitter}),
|
||||
]);
|
||||
const imgData = QR.encodeToDataURI(uri, "L");
|
||||
document.getElementById("qrContainer").style.backgroundImage = `url("${imgData.src}")`;
|
||||
document.getElementById("qrWrapper").setAttribute("pairing-status", "ready");
|
||||
} catch (e) {
|
||||
this.onError(e);
|
||||
}
|
||||
},
|
||||
|
||||
_resetBackgroundQR() {
|
||||
// The text we encode doesn't really matter as it is un-scannable (blurry and very transparent).
|
||||
const imgData = QR.encodeToDataURI("https://accounts.firefox.com/pair", "L");
|
||||
document.getElementById("qrContainer").style.backgroundImage = `url("${imgData.src}")`;
|
||||
},
|
||||
|
||||
onError(err) {
|
||||
Cu.reportError(err);
|
||||
this.teardownListeners();
|
||||
document.getElementById("qrWrapper").setAttribute("pairing-status", "error");
|
||||
},
|
||||
|
||||
_switchToUrl(url) {
|
||||
const browser = window.docShell.chromeEventHandler;
|
||||
browser.loadURI(url, {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
|
||||
});
|
||||
},
|
||||
|
||||
setupListeners() {
|
||||
this._switchToWebContent = (_, url) => this._switchToUrl(url);
|
||||
this._onError = (_, error) => this.onError(error);
|
||||
this._emitter.once("view:SwitchToWebContent", this._switchToWebContent);
|
||||
this._emitter.on("view:Error", this._onError);
|
||||
},
|
||||
|
||||
teardownListeners() {
|
||||
try {
|
||||
this._emitter.off("view:SwitchToWebContent", this._switchToWebContent);
|
||||
this._emitter.off("view:Error", this._onError);
|
||||
} catch (e) {
|
||||
console.warn("Error while tearing down listeners.", e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -->
|
||||
<!-- 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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/preferences/in-content/fxaPairDevice.css" type="text/css"?>
|
||||
|
||||
<window id="fxaPairDeviceDialog" class="windowDialog"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
role="dialog"
|
||||
onload="gFxaPairDeviceDialog.init();"
|
||||
onunload="gFxaPairDeviceDialog.uninit()"
|
||||
data-l10n-id="fxa-pair-device-dialog"
|
||||
data-l10n-attrs="title, style">
|
||||
|
||||
<linkset>
|
||||
<link rel="localization" href="browser/branding/sync-brand.ftl"/>
|
||||
<link rel="localization" href="browser/preferences/fxaPairDevice.ftl"/>
|
||||
</linkset>
|
||||
<script src="chrome://browser/content/preferences/in-content/fxaPairDevice.js"/>
|
||||
|
||||
<vbox id="qrCodeDisplay">
|
||||
<description class="pairHeading" data-l10n-id="fxa-qrcode-heading-phase1">
|
||||
<html:a
|
||||
id="connect-another-device-link"
|
||||
data-l10n-name="connect-another-device"
|
||||
class="text-link" target="_blank"/>
|
||||
</description>
|
||||
<description class="pairHeading" data-l10n-id="fxa-qrcode-heading-phase2"></description>
|
||||
<vbox>
|
||||
<vbox align="center" id="qrWrapper" pairing-status="loading">
|
||||
<box id="qrContainer"></box>
|
||||
<box id="qrSpinner"></box>
|
||||
<vbox id="qrError" onclick="gFxaPairDeviceDialog.startPairingFlow();">
|
||||
<image id="refresh-qr" />
|
||||
<label class="qr-error-text" data-l10n-id="fxa-qrcode-error-title"></label>
|
||||
<label class="qr-error-text" data-l10n-id="fxa-qrcode-error-body"></label>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</window>
|
||||
|
|
@ -16,6 +16,4 @@ browser.jar:
|
|||
content/browser/preferences/in-content/sync.js
|
||||
content/browser/preferences/in-content/syncDisconnect.xul
|
||||
content/browser/preferences/in-content/syncDisconnect.js
|
||||
content/browser/preferences/in-content/fxaPairDevice.xul
|
||||
content/browser/preferences/in-content/fxaPairDevice.js
|
||||
content/browser/preferences/in-content/findInPage.js
|
||||
|
|
|
|||
|
|
@ -149,11 +149,11 @@ var gSyncPane = {
|
|||
|
||||
// Links for mobile devices shown after the user is logged in.
|
||||
FxAccounts.config.promiseConnectDeviceURI(this._getEntryPoint()).then(connectURI => {
|
||||
document.getElementById("connect-another-device").setAttribute("href", connectURI);
|
||||
document.getElementById("mobilePromo-singledevice").setAttribute("href", connectURI);
|
||||
});
|
||||
|
||||
FxAccounts.config.promiseManageDevicesURI(this._getEntryPoint()).then(manageURI => {
|
||||
document.getElementById("manage-devices").setAttribute("href", manageURI);
|
||||
document.getElementById("mobilePromo-multidevice").setAttribute("href", manageURI);
|
||||
});
|
||||
|
||||
document.getElementById("tosPP-small-ToS").setAttribute("href", Weave.Svc.Prefs.get("fxa.termsURL"));
|
||||
|
|
@ -167,12 +167,6 @@ var gSyncPane = {
|
|||
|
||||
// Notify observers that the UI is now ready
|
||||
Services.obs.notifyObservers(window, "sync-pane-loaded");
|
||||
|
||||
// document.location.search is empty, so we simply match on `action=pair`.
|
||||
if (location.href.includes("action=pair") && location.hash == "#sync" &&
|
||||
UIState.get().status == UIState.STATUS_SIGNED_IN) {
|
||||
gSyncPane.pairAnotherDevice();
|
||||
}
|
||||
},
|
||||
|
||||
_toggleComputerNameControls(editMode) {
|
||||
|
|
@ -344,8 +338,8 @@ var gSyncPane = {
|
|||
let isUnverified = state.status == UIState.STATUS_NOT_VERIFIED;
|
||||
// The mobile promo links - which one is shown depends on the number of devices.
|
||||
let isMultiDevice = Weave.Service.clientsEngine.stats.numClients > 1;
|
||||
document.getElementById("connect-another-device").hidden = isUnverified;
|
||||
document.getElementById("manage-devices").hidden = isUnverified || !isMultiDevice;
|
||||
document.getElementById("mobilePromo-singledevice").hidden = isUnverified || isMultiDevice;
|
||||
document.getElementById("mobilePromo-multidevice").hidden = isUnverified || !isMultiDevice;
|
||||
},
|
||||
|
||||
_getEntryPoint() {
|
||||
|
|
@ -474,14 +468,6 @@ var gSyncPane = {
|
|||
}
|
||||
},
|
||||
|
||||
pairAnotherDevice() {
|
||||
gSubDialog.open("chrome://browser/content/preferences/in-content/fxaPairDevice.xul",
|
||||
"resizable=no", /* aFeatures */
|
||||
null, /* aParams */
|
||||
null /* aClosingCallback */
|
||||
);
|
||||
},
|
||||
|
||||
_populateComputerName(value) {
|
||||
let textbox = document.getElementById("fxaSyncComputerName");
|
||||
if (!textbox.hasAttribute("placeholder")) {
|
||||
|
|
|
|||
|
|
@ -185,10 +185,10 @@
|
|||
</hbox>
|
||||
</groupbox>
|
||||
<vbox align="start">
|
||||
<label id="connect-another-device" is="text-link"
|
||||
class="fxaMobilePromo" data-l10n-id="sync-connect-another-device"/>
|
||||
<label id="manage-devices" is="text-link"
|
||||
class="fxaMobilePromo" data-l10n-id="sync-manage-devices"/>
|
||||
<label id="mobilePromo-singledevice" is="text-link"
|
||||
class="fxaMobilePromo" data-l10n-id="sync-mobilepromo-single"/>
|
||||
<label id="mobilePromo-multidevice" is="text-link"
|
||||
class="fxaMobilePromo" data-l10n-id="sync-mobilepromo-multi"/>
|
||||
</vbox>
|
||||
<vbox id="tosPP-small" align="start">
|
||||
<label id="tosPP-small-ToS" is="text-link" data-l10n-id="sync-tos-link"/>
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ support-files =
|
|||
subdialog2.xul
|
||||
[browser_sync_sanitize.js]
|
||||
skip-if = os == 'win' && processor == "x86_64" && bits == 64 # bug 1522821
|
||||
[browser_sync_pairing.js]
|
||||
[browser_telemetry.js]
|
||||
# Skip this test on Android as FHR and Telemetry are separate systems there.
|
||||
skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* global sinon */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {UIState} = ChromeUtils.import("resource://services-sync/UIState.jsm", {});
|
||||
const {FxAccountsPairingFlow} = ChromeUtils.import("resource://gre/modules/FxAccountsPairing.jsm", {});
|
||||
|
||||
// Use sinon for mocking.
|
||||
Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
|
||||
registerCleanupFunction(() => {
|
||||
delete window.sinon; // test fails with this reference left behind.
|
||||
});
|
||||
|
||||
let flowCounter = 0;
|
||||
|
||||
add_task(async function setup() {
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.pairing.enabled", true);
|
||||
// Sync start-up might interfere with our tests, don't let UIState send UI updates.
|
||||
const origNotifyStateUpdated = UIState._internal.notifyStateUpdated;
|
||||
UIState._internal.notifyStateUpdated = () => {};
|
||||
|
||||
const origGet = UIState.get;
|
||||
UIState.get = () => { return { status: UIState.STATUS_SIGNED_IN, email: "foo@bar.com" }; };
|
||||
|
||||
const origStart = FxAccountsPairingFlow.start;
|
||||
FxAccountsPairingFlow.start = ({emitter: e}) => {
|
||||
return `https://foo.bar/${flowCounter++}`;
|
||||
};
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
UIState._internal.notifyStateUpdated = origNotifyStateUpdated;
|
||||
UIState.get = origGet;
|
||||
FxAccountsPairingFlow.start = origStart;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testShowsQRCode() {
|
||||
await runWithPairingDialog(async (win, sinon) => {
|
||||
let doc = win.document;
|
||||
let qrContainer = doc.getElementById("qrContainer");
|
||||
let qrWrapper = doc.getElementById("qrWrapper");
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "ready");
|
||||
|
||||
// Verify that a QRcode is being shown.
|
||||
Assert.ok(qrContainer.style.backgroundImage.startsWith(`url("data:image/gif;base64,R0lGODdhOgA6AIAAAAAAAP///ywAAAAAOgA6AAAC/4yPqcvtD6OctNqLs968+w+G4gKU5nkiJYO2JuW6KsDGKEw3a7AbPZ+r4Ry7nzFIQkKKN6Avlzowo78`));
|
||||
|
||||
// Close the dialog.
|
||||
let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
|
||||
gBrowser.contentDocument.querySelector(".dialogClose").click();
|
||||
|
||||
info("waiting for dialog to unload");
|
||||
await promiseUnloaded;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testCantShowQrCode() {
|
||||
const origStart = FxAccountsPairingFlow.start;
|
||||
FxAccountsPairingFlow.start = async () => { throw new Error("boom"); };
|
||||
await runWithPairingDialog(async (win, sinon) => {
|
||||
let doc = win.document;
|
||||
let qrWrapper = doc.getElementById("qrWrapper");
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "error");
|
||||
|
||||
// Close the dialog.
|
||||
let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
|
||||
gBrowser.contentDocument.querySelector(".dialogClose").click();
|
||||
|
||||
info("waiting for dialog to unload");
|
||||
await promiseUnloaded;
|
||||
});
|
||||
FxAccountsPairingFlow.start = origStart;
|
||||
});
|
||||
|
||||
add_task(async function testSwitchToWebContent() {
|
||||
await runWithPairingDialog(async (win, sinon) => {
|
||||
let doc = win.document;
|
||||
let qrWrapper = doc.getElementById("qrWrapper");
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "ready");
|
||||
|
||||
const spySwitchURL = sinon.spy(win.gFxaPairDeviceDialog, "_switchToUrl");
|
||||
const emitter = win.gFxaPairDeviceDialog._emitter;
|
||||
emitter.emit("view:SwitchToWebContent", "about:robots");
|
||||
|
||||
Assert.equal(spySwitchURL.callCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testError() {
|
||||
await runWithPairingDialog(async (win, sinon) => {
|
||||
let doc = win.document;
|
||||
let qrWrapper = doc.getElementById("qrWrapper");
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "ready");
|
||||
|
||||
const emitter = win.gFxaPairDeviceDialog._emitter;
|
||||
emitter.emit("view:Error");
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "error");
|
||||
|
||||
// Close the dialog.
|
||||
let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
|
||||
gBrowser.contentDocument.querySelector(".dialogClose").click();
|
||||
|
||||
info("waiting for dialog to unload");
|
||||
await promiseUnloaded;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testMPLocked() {
|
||||
Weave.Utils.ensureMPUnlocked = () => false;
|
||||
await runWithPairingDialog(async (win, sinon) => {
|
||||
let doc = win.document;
|
||||
let qrWrapper = doc.getElementById("qrWrapper");
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "error");
|
||||
|
||||
// Simulate unlock.
|
||||
Weave.Utils.ensureMPUnlocked = () => true;
|
||||
|
||||
await doc.getElementById("qrError").click();
|
||||
|
||||
await TestUtils.waitForCondition(() => qrWrapper.getAttribute("pairing-status") == "ready");
|
||||
|
||||
// Close the dialog.
|
||||
let promiseUnloaded = BrowserTestUtils.waitForEvent(win, "unload");
|
||||
gBrowser.contentDocument.querySelector(".dialogClose").click();
|
||||
|
||||
info("waiting for dialog to unload");
|
||||
await promiseUnloaded;
|
||||
});
|
||||
});
|
||||
|
||||
async function runWithPairingDialog(test) {
|
||||
await openPreferencesViaOpenPreferencesAPI("paneSync", {leaveOpen: true});
|
||||
|
||||
let promiseSubDialogLoaded =
|
||||
promiseLoadSubDialog("chrome://browser/content/preferences/in-content/fxaPairDevice.xul");
|
||||
gBrowser.contentWindow.gSyncPane.pairAnotherDevice();
|
||||
|
||||
let win = await promiseSubDialogLoaded;
|
||||
|
||||
let ss = sinon.sandbox.create();
|
||||
|
||||
await test(win, ss);
|
||||
|
||||
ss.restore();
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
|
|
@ -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/.
|
||||
|
||||
fxa-pair-device-dialog =
|
||||
.title = Connect Another Device
|
||||
.style = width: 26em; min-height: 35em;
|
||||
|
||||
fxa-qrcode-heading-phase1 = 1. If you haven’t already, install <a data-l10n-name="connect-another-device">Firefox on your mobile device</a>.
|
||||
|
||||
fxa-qrcode-heading-phase2 = 2. Then sign in to { -sync-brand-short-name }, or on Android scan the pairing code from inside the { -sync-brand-short-name } settings.
|
||||
|
||||
fxa-qrcode-error-title = Pairing unsuccessful.
|
||||
|
||||
fxa-qrcode-error-body = Try again.
|
||||
|
|
@ -664,11 +664,9 @@ sync-device-name-save =
|
|||
.label = Save
|
||||
.accesskey = v
|
||||
|
||||
sync-connect-another-device = Connect another device
|
||||
sync-mobilepromo-single = Connect another device
|
||||
|
||||
sync-manage-devices = Manage devices
|
||||
|
||||
sync-fxa-begin-pairing = Pair a device
|
||||
sync-mobilepromo-multi = Manage devices
|
||||
|
||||
sync-tos-link = Terms of Service
|
||||
|
||||
|
|
|
|||
|
|
@ -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/. -->
|
||||
|
||||
<svg width="73px" height="73px" viewBox="0 0 73 73" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<defs>
|
||||
<linearGradient x1="93.0928096%" y1="52.7734375%" x2="68.5133398%" y2="119.326007%" id="linearGradient-1">
|
||||
<stop stop-color="#0A84FF" stop-opacity="0" offset="0%"></stop>
|
||||
<stop stop-color="#0A84FF" offset="69.3698182%"></stop>
|
||||
<stop stop-color="#0A84FF" offset="100%"></stop>
|
||||
<stop stop-color="#2484C6" stop-opacity="0.00477766951" offset="100%"></stop>
|
||||
<stop stop-color="#2484C6" stop-opacity="0" offset="100%"></stop>
|
||||
<stop stop-color="#2484C6" stop-opacity="0" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<rect id="path-2" x="0" y="0" width="48" height="60"></rect>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Shape" transform="translate(-5.000000, -1.000000)">
|
||||
<path d="M41.8,73.8 C21.9,73.8 5.8,57.7 5.8,37.8 C5.8,18.1 21.6,2.2 41.1,1.8 C41.3,1.8 41.4,1.8 41.4,1.8 C41.5,1.8 41.7,1.8 41.8,1.8 C44.6,2.2 46.8,4.5 46.8,7.3 C46.8,10.1 44.6,12.5 41.8,12.7 C28,12.8 16.8,24 16.8,37.8 C16.8,51.6 28,62.8 41.8,62.8 C55.6,62.8 66.8,51.6 66.8,37.8 L77.8,37.8 C77.8,57.7 61.7,73.8 41.8,73.8 Z" fill="url(#linearGradient-1)"></path>
|
||||
<mask id="mask-3" fill="white">
|
||||
<use xlink:href="#path-2"></use>
|
||||
</mask>
|
||||
<g id="Mask"></g>
|
||||
<path d="M41.8,73.8 C21.9,73.8 5.8,57.7 5.8,37.8 C5.8,18.1 21.6,2.2 41.1,1.8 C41.3,1.8 41.4,1.8 41.4,1.8 C41.5,1.8 41.7,1.8 41.8,1.8 C44.6,2.2 46.8,4.5 46.8,7.3 C46.8,10.1 44.6,12.5 41.8,12.7 C28,12.8 16.8,24 16.8,37.8 C16.8,51.6 28,62.8 41.8,62.8 C55.6,62.8 66.8,51.6 66.8,37.8 L77.8,37.8 C77.8,57.7 61.7,73.8 41.8,73.8 Z" fill="#0A84FF" mask="url(#mask-3)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
|
|
@ -1,94 +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/. */
|
||||
|
||||
#fxaPairDeviceDialog {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.pairHeading {
|
||||
padding-bottom: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#qrWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#qrContainer {
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
background-size: contain;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
transition: filter 250ms cubic-bezier(.07,.95,0,1);
|
||||
}
|
||||
|
||||
#qrWrapper:not([pairing-status="ready"]) #qrContainer {
|
||||
opacity: 0.05;
|
||||
filter: blur(3px);
|
||||
}
|
||||
|
||||
#qrWrapper:not([pairing-status="loading"]) #qrSpinner {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#qrSpinner {
|
||||
background-image: url("chrome://browser/skin/fxa/fxa-spinner.svg");
|
||||
animation: 0.9s spin infinite linear;
|
||||
background-size: 36px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transition: opacity 250ms cubic-bezier(.07,.95,0,1);
|
||||
}
|
||||
|
||||
#qrWrapper:not([pairing-status="error"]) #qrError {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qrError {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 300px; /* Same as #qrContainer */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
transition: opacity 250ms cubic-bezier(.07,.95,0,1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.qr-error-text {
|
||||
text-align: center;
|
||||
-moz-user-select: none;
|
||||
display: block;
|
||||
color: #2484C6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#refresh-qr {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background-image: url("chrome://browser/skin/reload.svg");
|
||||
background-size: contain;
|
||||
-moz-context-properties: fill;
|
||||
fill: #2484C6;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -109,13 +109,12 @@
|
|||
#endif
|
||||
skin/classic/browser/panel-icon-retry.svg (../shared/panel-icon-retry.svg)
|
||||
skin/classic/browser/preferences/in-content/critters-postcard.jpg (../shared/incontentprefs/critters-postcard.jpg)
|
||||
skin/classic/browser/preferences/in-content/face-sad.svg (../shared/incontentprefs/face-sad.svg)
|
||||
skin/classic/browser/preferences/in-content/face-smile.svg (../shared/incontentprefs/face-smile.svg)
|
||||
skin/classic/browser/preferences/in-content/fxa-avatar.svg (../shared/incontentprefs/fxa-avatar.svg)
|
||||
skin/classic/browser/preferences/in-content/fxaPairDevice.css (../shared/incontentprefs/fxaPairDevice.css)
|
||||
skin/classic/browser/preferences/in-content/general.svg (../shared/incontentprefs/general.svg)
|
||||
skin/classic/browser/preferences/in-content/logo-android.svg (../shared/incontentprefs/logo-android.svg)
|
||||
skin/classic/browser/preferences/in-content/logo-ios.svg (../shared/incontentprefs/logo-ios.svg)
|
||||
skin/classic/browser/preferences/in-content/face-sad.svg (../shared/incontentprefs/face-sad.svg)
|
||||
skin/classic/browser/preferences/in-content/face-smile.svg (../shared/incontentprefs/face-smile.svg)
|
||||
skin/classic/browser/preferences/in-content/fxa-avatar.svg (../shared/incontentprefs/fxa-avatar.svg)
|
||||
skin/classic/browser/preferences/in-content/general.svg (../shared/incontentprefs/general.svg)
|
||||
skin/classic/browser/preferences/in-content/logo-android.svg (../shared/incontentprefs/logo-android.svg)
|
||||
skin/classic/browser/preferences/in-content/logo-ios.svg (../shared/incontentprefs/logo-ios.svg)
|
||||
skin/classic/browser/preferences/in-content/no-search-bar.svg (../shared/incontentprefs/no-search-bar.svg)
|
||||
skin/classic/browser/preferences/in-content/no-search-results.svg (../shared/incontentprefs/no-search-results.svg)
|
||||
skin/classic/browser/preferences/in-content/privacy-security.svg (../shared/incontentprefs/privacy-security.svg)
|
||||
|
|
@ -131,7 +130,6 @@
|
|||
* skin/classic/browser/preferences/in-content/containers.css (../shared/incontentprefs/containers.css)
|
||||
* skin/classic/browser/preferences/containers.css (../shared/preferences/containers.css)
|
||||
skin/classic/browser/fxa/default-avatar.svg (../shared/fxa/default-avatar.svg)
|
||||
skin/classic/browser/fxa/fxa-spinner.svg (../shared/fxa/fxa-spinner.svg)
|
||||
skin/classic/browser/fxa/sync-illustration.svg (../shared/fxa/sync-illustration.svg)
|
||||
skin/classic/browser/fxa/sync-illustration-issue.svg (../shared/fxa/sync-illustration-issue.svg)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {clearTimeout, setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
|
||||
const {FxAccountsStorageManager} = ChromeUtils.import("resource://gre/modules/FxAccountsStorage.jsm");
|
||||
const {ASSERTION_LIFETIME, ASSERTION_USE_PERIOD, CERT_LIFETIME, COMMAND_SENDTAB, DERIVED_KEYS_NAMES, ERRNO_DEVICE_SESSION_CONFLICT, ERRNO_INVALID_AUTH_TOKEN, ERRNO_UNKNOWN_DEVICE, ERROR_AUTH_ERROR, ERROR_INVALID_PARAMETER, ERROR_NO_ACCOUNT, ERROR_OFFLINE, ERROR_TO_GENERAL_ERROR_CLASS, ERROR_UNKNOWN, ERROR_UNVERIFIED_ACCOUNT, FXA_PWDMGR_MEMORY_FIELDS, FXA_PWDMGR_PLAINTEXT_FIELDS, FXA_PWDMGR_REAUTH_WHITELIST, FXA_PWDMGR_SECURE_FIELDS, FX_OAUTH_CLIENT_ID, KEY_LIFETIME, ONLOGIN_NOTIFICATION, ONLOGOUT_NOTIFICATION, ONVERIFIED_NOTIFICATION, ON_DEVICE_DISCONNECTED_NOTIFICATION, ON_NEW_DEVICE_ID, POLL_SESSION, PREF_LAST_FXA_USER, SERVER_ERRNO_TO_ERROR, SCOPE_OLD_SYNC, log, logPII} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {ASSERTION_LIFETIME, ASSERTION_USE_PERIOD, CERT_LIFETIME, COMMAND_SENDTAB, DERIVED_KEYS_NAMES, ERRNO_DEVICE_SESSION_CONFLICT, ERRNO_INVALID_AUTH_TOKEN, ERRNO_UNKNOWN_DEVICE, ERROR_AUTH_ERROR, ERROR_INVALID_PARAMETER, ERROR_NO_ACCOUNT, ERROR_OFFLINE, ERROR_TO_GENERAL_ERROR_CLASS, ERROR_UNKNOWN, ERROR_UNVERIFIED_ACCOUNT, FXA_PWDMGR_MEMORY_FIELDS, FXA_PWDMGR_PLAINTEXT_FIELDS, FXA_PWDMGR_REAUTH_WHITELIST, FXA_PWDMGR_SECURE_FIELDS, FX_OAUTH_CLIENT_ID, KEY_LIFETIME, ONLOGIN_NOTIFICATION, ONLOGOUT_NOTIFICATION, ONVERIFIED_NOTIFICATION, ON_DEVICE_DISCONNECTED_NOTIFICATION, ON_NEW_DEVICE_ID, POLL_SESSION, PREF_LAST_FXA_USER, SERVER_ERRNO_TO_ERROR, log, logPII} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "FxAccountsClient",
|
||||
"resource://gre/modules/FxAccountsClient.jsm");
|
||||
|
|
@ -48,11 +48,9 @@ var publicProperties = [
|
|||
"getDeviceId",
|
||||
"getDeviceList",
|
||||
"getKeys",
|
||||
"authorizeOAuthCode",
|
||||
"getOAuthToken",
|
||||
"getProfileCache",
|
||||
"getPushSubscription",
|
||||
"getScopedKeys",
|
||||
"getSignedInUser",
|
||||
"getSignedInUserProfile",
|
||||
"handleAccountDestroyed",
|
||||
|
|
@ -420,18 +418,6 @@ FxAccountsInternal.prototype = {
|
|||
return this._commands;
|
||||
},
|
||||
|
||||
_oauthClient: null,
|
||||
get oauthClient() {
|
||||
if (!this._oauthClient) {
|
||||
const serverURL = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.oauth.uri");
|
||||
this._oauthClient = new FxAccountsOAuthGrantClient({
|
||||
serverURL,
|
||||
client_id: FX_OAUTH_CLIENT_ID,
|
||||
});
|
||||
}
|
||||
return this._oauthClient;
|
||||
},
|
||||
|
||||
// A hook-point for tests who may want a mocked AccountState or mocked storage.
|
||||
newAccountState(credentials) {
|
||||
let storage = new FxAccountsStorageManager();
|
||||
|
|
@ -1253,41 +1239,6 @@ FxAccountsInternal.prototype = {
|
|||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} scope Single key bearing scope
|
||||
*/
|
||||
async getKeyForScope(scope, {keyRotationTimestamp}) {
|
||||
if (scope !== SCOPE_OLD_SYNC) {
|
||||
throw new Error(`Unavailable key material for ${scope}`);
|
||||
}
|
||||
let {kSync, kXCS} = await this.getKeys();
|
||||
if (!kSync || !kXCS) {
|
||||
throw new Error("Could not find requested key.");
|
||||
}
|
||||
kXCS = ChromeUtils.base64URLEncode(CommonUtils.hexToArrayBuffer(kXCS), {pad: false});
|
||||
kSync = ChromeUtils.base64URLEncode(CommonUtils.hexToArrayBuffer(kSync), {pad: false});
|
||||
const kid = `${keyRotationTimestamp}-${kXCS}`;
|
||||
return {
|
||||
scope,
|
||||
kid,
|
||||
k: kSync,
|
||||
kty: "oct",
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} scopes Space separated requested scopes
|
||||
*/
|
||||
async getScopedKeys(scopes, clientId) {
|
||||
const {sessionToken} = await this._getVerifiedAccountOrReject();
|
||||
const keyData = await this.fxAccountsClient.getScopedKeyData(sessionToken, clientId, scopes);
|
||||
const scopedKeys = {};
|
||||
for (const [scope, data] of Object.entries(keyData)) {
|
||||
scopedKeys[scope] = await this.getKeyForScope(scope, data);
|
||||
}
|
||||
return scopedKeys;
|
||||
},
|
||||
|
||||
getUserAccountData() {
|
||||
return this.currentAccountState.getUserAccountData();
|
||||
},
|
||||
|
|
@ -1504,7 +1455,19 @@ FxAccountsInternal.prototype = {
|
|||
|
||||
// We are going to hit the server - this is the string we pass to it.
|
||||
let scopeString = scope.join(" ");
|
||||
let client = options.client || this.oauthClient;
|
||||
let client = options.client;
|
||||
|
||||
if (!client) {
|
||||
try {
|
||||
let defaultURL = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.oauth.uri");
|
||||
client = new FxAccountsOAuthGrantClient({
|
||||
serverURL: defaultURL,
|
||||
client_id: FX_OAUTH_CLIENT_ID,
|
||||
});
|
||||
} catch (e) {
|
||||
throw this._error(ERROR_INVALID_PARAMETER, e);
|
||||
}
|
||||
}
|
||||
let oAuthURL = client.serverURL.href;
|
||||
|
||||
try {
|
||||
|
|
@ -1533,49 +1496,6 @@ FxAccountsInternal.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} clientId
|
||||
* @param {String} scope Space separated requested scopes
|
||||
* @param {Object} jwk
|
||||
*/
|
||||
async createKeysJWE(clientId, scope, jwk) {
|
||||
let scopedKeys = await this.getScopedKeys(scope, clientId);
|
||||
scopedKeys = new TextEncoder().encode(JSON.stringify(scopedKeys));
|
||||
return jwcrypto.generateJWE(jwk, scopedKeys);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves an OAuth authorization code
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param options.client_id
|
||||
* @param options.state
|
||||
* @param options.scope
|
||||
* @param options.access_type
|
||||
* @param options.code_challenge_method
|
||||
* @param options.code_challenge
|
||||
* @param [options.keys_jwe]
|
||||
* @returns {Promise<Object>} Object containing "code" and "state" properties.
|
||||
*/
|
||||
async authorizeOAuthCode(options) {
|
||||
await this._getVerifiedAccountOrReject();
|
||||
const client = this.oauthClient;
|
||||
const oAuthURL = client.serverURL.href;
|
||||
const params = {...options};
|
||||
if (params.keys_jwk) {
|
||||
const jwk = JSON.parse(new TextDecoder().decode(ChromeUtils.base64URLDecode(params.keys_jwk, {padding: "reject"})));
|
||||
params.keys_jwe = await this.createKeysJWE(params.client_id, params.scope, jwk);
|
||||
delete params.keys_jwk;
|
||||
}
|
||||
try {
|
||||
const assertion = await this.getAssertion(oAuthURL);
|
||||
return client.authorizeCodeFromAssertion(assertion, params);
|
||||
} catch (err) {
|
||||
throw this._errorToErrorClass(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an OAuth token from the token cache. Callers should call this
|
||||
* after they determine a token is invalid, so a new token will be fetched
|
||||
|
|
|
|||
|
|
@ -200,32 +200,6 @@ this.FxAccountsClient.prototype = {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query for the information required to derive
|
||||
* scoped encryption keys requested by the specified OAuth client.
|
||||
*
|
||||
* @param sessionTokenHex
|
||||
* The session token encoded in hex
|
||||
* @param clientId
|
||||
* @param scope
|
||||
* Space separated list of scopes
|
||||
* @return Promise
|
||||
*/
|
||||
async getScopedKeyData(sessionTokenHex, clientId, scope) {
|
||||
if (!clientId) {
|
||||
throw new Error("Missing 'clientId' parameter");
|
||||
}
|
||||
if (!scope) {
|
||||
throw new Error("Missing 'scope' parameter");
|
||||
}
|
||||
const params = {
|
||||
client_id: clientId,
|
||||
scope,
|
||||
};
|
||||
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||
return this._request("/account/scoped-key-data", "POST", credentials, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the current session with the Firefox Account API server and its
|
||||
* associated device.
|
||||
|
|
|
|||
|
|
@ -78,37 +78,17 @@ exports.ON_NEW_DEVICE_ID = "fxaccounts:new_device_id";
|
|||
|
||||
exports.COMMAND_SENDTAB = "https://identity.mozilla.com/cmd/open-uri";
|
||||
|
||||
// OAuth
|
||||
exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";
|
||||
exports.SCOPE_PROFILE = "profile";
|
||||
exports.SCOPE_OLD_SYNC = "https://identity.mozilla.com/apps/oldsync";
|
||||
|
||||
// UI Requests.
|
||||
exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
|
||||
exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
|
||||
|
||||
// The OAuth client ID for Firefox Desktop
|
||||
exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";
|
||||
|
||||
// Firefox Accounts WebChannel ID
|
||||
exports.WEBCHANNEL_ID = "account_updates";
|
||||
|
||||
// WebChannel commands
|
||||
exports.COMMAND_PAIR_HEARTBEAT = "fxaccounts:pair_heartbeat";
|
||||
exports.COMMAND_PAIR_SUPP_METADATA = "fxaccounts:pair_supplicant_metadata";
|
||||
exports.COMMAND_PAIR_AUTHORIZE = "fxaccounts:pair_authorize";
|
||||
exports.COMMAND_PAIR_DECLINE = "fxaccounts:pair_decline";
|
||||
exports.COMMAND_PAIR_COMPLETE = "fxaccounts:pair_complete";
|
||||
|
||||
exports.COMMAND_PROFILE_CHANGE = "profile:change";
|
||||
exports.COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
|
||||
exports.COMMAND_LOGIN = "fxaccounts:login";
|
||||
exports.COMMAND_LOGOUT = "fxaccounts:logout";
|
||||
exports.COMMAND_DELETE = "fxaccounts:delete";
|
||||
exports.COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
|
||||
exports.COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password";
|
||||
exports.COMMAND_FXA_STATUS = "fxaccounts:fxa_status";
|
||||
exports.COMMAND_PAIR_PREFERENCES = "fxaccounts:pair_preferences";
|
||||
|
||||
exports.PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
|
||||
exports.PREF_REMOTE_PAIRING_URI = "identity.fxaccounts.remote.pairing.uri";
|
||||
|
||||
// Server errno.
|
||||
// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
|
||||
|
|
|
|||
|
|
@ -29,27 +29,20 @@ const CONFIG_PREFS = [
|
|||
"identity.fxaccounts.auth.uri",
|
||||
"identity.fxaccounts.remote.oauth.uri",
|
||||
"identity.fxaccounts.remote.profile.uri",
|
||||
"identity.fxaccounts.remote.pairing.uri",
|
||||
"identity.sync.tokenserver.uri",
|
||||
];
|
||||
|
||||
var FxAccountsConfig = {
|
||||
async promiseSignUpURI(entrypoint) {
|
||||
return this._buildURL("signup", {
|
||||
extraParams: {entrypoint},
|
||||
});
|
||||
return this._buildURL("signup", {entrypoint});
|
||||
},
|
||||
|
||||
async promiseSignInURI(entrypoint) {
|
||||
return this._buildURL("signin", {
|
||||
extraParams: {entrypoint},
|
||||
});
|
||||
return this._buildURL("signin", {entrypoint});
|
||||
},
|
||||
|
||||
async promiseEmailURI(email, entrypoint) {
|
||||
return this._buildURL("", {
|
||||
extraParams: {entrypoint, email},
|
||||
});
|
||||
return this._buildURL("", {entrypoint, email});
|
||||
},
|
||||
|
||||
async promiseEmailFirstURI(entrypoint) {
|
||||
|
|
@ -57,50 +50,23 @@ var FxAccountsConfig = {
|
|||
},
|
||||
|
||||
async promiseForceSigninURI(entrypoint) {
|
||||
return this._buildURL("force_auth", {
|
||||
extraParams: {entrypoint},
|
||||
addAccountIdentifiers: true,
|
||||
});
|
||||
return this._buildURL("force_auth", {entrypoint}, true);
|
||||
},
|
||||
|
||||
async promiseManageURI(entrypoint) {
|
||||
return this._buildURL("settings", {
|
||||
extraParams: {entrypoint},
|
||||
addAccountIdentifiers: true,
|
||||
});
|
||||
return this._buildURL("settings", {entrypoint}, true);
|
||||
},
|
||||
|
||||
async promiseChangeAvatarURI(entrypoint) {
|
||||
return this._buildURL("settings/avatar/change", {
|
||||
extraParams: {entrypoint},
|
||||
addAccountIdentifiers: true,
|
||||
});
|
||||
return this._buildURL("settings/avatar/change", {entrypoint}, true);
|
||||
},
|
||||
|
||||
async promiseManageDevicesURI(entrypoint) {
|
||||
return this._buildURL("settings/clients", {
|
||||
extraParams: {entrypoint},
|
||||
addAccountIdentifiers: true,
|
||||
});
|
||||
return this._buildURL("settings/clients", {entrypoint}, true);
|
||||
},
|
||||
|
||||
async promiseConnectDeviceURI(entrypoint) {
|
||||
return this._buildURL("connect_another_device", {
|
||||
extraParams: {entrypoint},
|
||||
addAccountIdentifiers: true,
|
||||
});
|
||||
},
|
||||
|
||||
async promisePairingURI() {
|
||||
return this._buildURL("pair", {
|
||||
includeDefaultParams: false,
|
||||
});
|
||||
},
|
||||
|
||||
async promiseOAuthURI() {
|
||||
return this._buildURL("oauth", {
|
||||
includeDefaultParams: false,
|
||||
});
|
||||
return this._buildURL("connect_another_device", {entrypoint}, true);
|
||||
},
|
||||
|
||||
get defaultParams() {
|
||||
|
|
@ -109,21 +75,21 @@ var FxAccountsConfig = {
|
|||
|
||||
/**
|
||||
* @param path should be parsable by the URL constructor first parameter.
|
||||
* @param {bool} [options.includeDefaultParams] If true include the default search params.
|
||||
* @param {Object.<string, string>} [options.extraParams] Additionnal search params.
|
||||
* @param {bool} [options.addAccountIdentifiers] if true we add the current logged-in user uid and email to the search params.
|
||||
* @param {Object.<string, string>} [extraParams] Additionnal search params.
|
||||
* @param {bool} [addCredentials] if true we add the current logged-in user
|
||||
* uid and email to the search params.
|
||||
*/
|
||||
async _buildURL(path, {includeDefaultParams = true, extraParams = {}, addAccountIdentifiers = false}) {
|
||||
async _buildURL(path, extraParams, addCredentials = false) {
|
||||
await this.ensureConfigured();
|
||||
const url = new URL(path, ROOT_URL);
|
||||
if (REQUIRES_HTTPS && url.protocol != "https:") {
|
||||
throw new Error("Firefox Accounts server must use HTTPS");
|
||||
}
|
||||
const params = {...(includeDefaultParams ? this.defaultParams : null), ...extraParams};
|
||||
const params = {...this.defaultParams, ...extraParams};
|
||||
for (let [k, v] of Object.entries(params)) {
|
||||
url.searchParams.append(k, v);
|
||||
}
|
||||
if (addAccountIdentifiers) {
|
||||
if (addCredentials) {
|
||||
const accountData = await this.getSignedInUser();
|
||||
if (!accountData) {
|
||||
return null;
|
||||
|
|
@ -241,11 +207,6 @@ var FxAccountsConfig = {
|
|||
}
|
||||
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", authServerBase);
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", config.oauth_server_base_url + "/v1");
|
||||
// At the time of landing this, our servers didn't yet answer with pairing_server_base_uri.
|
||||
// Remove this condition check once Firefox 68 is stable.
|
||||
if (config.pairing_server_base_uri) {
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.pairing.uri", config.pairing_server_base_uri);
|
||||
}
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", config.profile_server_base_url + "/v1");
|
||||
Services.prefs.setCharPref("identity.sync.tokenserver.uri", config.sync_tokenserver_base_url + "/1.0/sync/1.5");
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.root", rootURL);
|
||||
|
|
|
|||
|
|
@ -80,41 +80,6 @@ this.FxAccountsOAuthGrantClient.prototype = {
|
|||
return this._createRequest(AUTH_ENDPOINT, "POST", params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves an OAuth authorization code using an assertion
|
||||
*
|
||||
* @param {Object} assertion BrowserID assertion
|
||||
* @param {Object} options
|
||||
* @param options.client_id
|
||||
* @param options.state
|
||||
* @param options.scope
|
||||
* @param options.access_type
|
||||
* @param options.code_challenge_method
|
||||
* @param options.code_challenge
|
||||
* @param [options.keys_jwe]
|
||||
* @returns {Promise<Object>} Object containing "code" and "state" properties.
|
||||
*/
|
||||
authorizeCodeFromAssertion(assertion, options) {
|
||||
if (!assertion) {
|
||||
throw new Error("Missing 'assertion' parameter");
|
||||
}
|
||||
const {client_id, state, scope, access_type, code_challenge, code_challenge_method, keys_jwe} = options;
|
||||
const params = {
|
||||
assertion,
|
||||
client_id,
|
||||
response_type: "code",
|
||||
state,
|
||||
scope,
|
||||
access_type,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
};
|
||||
if (keys_jwe) {
|
||||
params.keys_jwe = keys_jwe;
|
||||
}
|
||||
return this._createRequest(AUTH_ENDPOINT, "POST", params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys a previously fetched OAuth access token.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,340 +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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {log, PREF_REMOTE_PAIRING_URI, COMMAND_PAIR_SUPP_METADATA, COMMAND_PAIR_AUTHORIZE, COMMAND_PAIR_DECLINE, COMMAND_PAIR_HEARTBEAT, COMMAND_PAIR_COMPLETE} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {fxAccounts, FxAccounts} = ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
|
||||
const {setTimeout, clearTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
|
||||
ChromeUtils.import("resource://services-common/utils.js");
|
||||
ChromeUtils.defineModuleGetter(this, "Weave", "resource://services-sync/main.js");
|
||||
ChromeUtils.defineModuleGetter(this, "FxAccountsPairingChannel", "resource://gre/modules/FxAccountsPairingChannel.js");
|
||||
|
||||
const PAIRING_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob:pair-auth-webchannel";
|
||||
// A pairing flow is not tied to a specific browser window, can also finish in
|
||||
// various ways and subsequently might leak a Web Socket, so just in case we
|
||||
// time out and free-up the resources after a specified amount of time.
|
||||
const FLOW_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes.
|
||||
|
||||
class PairingStateMachine {
|
||||
constructor(emitter) {
|
||||
this._emitter = emitter;
|
||||
this._transition(SuppConnectionPending);
|
||||
}
|
||||
|
||||
get currentState() {
|
||||
return this._currentState;
|
||||
}
|
||||
|
||||
_transition(StateCtor, ...args) {
|
||||
const state = new StateCtor(this, ...args);
|
||||
this._currentState = state;
|
||||
}
|
||||
|
||||
assertState(RequiredStates, messagePrefix = null) {
|
||||
if (!(RequiredStates instanceof Array)) {
|
||||
RequiredStates = [RequiredStates];
|
||||
}
|
||||
if (!RequiredStates.some(RequiredState => this._currentState instanceof RequiredState)) {
|
||||
const msg = `${messagePrefix ? `${messagePrefix}. ` : ""}Valid expected states: ${RequiredStates.map(({name}) => name).join(", ")}. Current state: ${this._currentState.label}.`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The pairing flow can be modeled by a finite state machine:
|
||||
* We start by connecting to a WebSocket channel (SuppConnectionPending).
|
||||
* Then the other party connects and requests some metadata from us (PendingConfirmations).
|
||||
* A confirmation happens locally first (PendingRemoteConfirmation)
|
||||
* or the oppposite (PendingLocalConfirmation).
|
||||
* Any side can decline this confirmation (Aborted).
|
||||
* Once both sides have confirmed, the pairing flow is finished (Completed).
|
||||
* During this flow errors can happen and should be handled (Errored).
|
||||
*/
|
||||
class State {
|
||||
constructor(stateMachine, ...args) {
|
||||
this._transition = (...args) => stateMachine._transition(...args);
|
||||
this._notify = (...args) => stateMachine._emitter.emit(...args);
|
||||
this.init(...args);
|
||||
}
|
||||
|
||||
init() { /* Does nothing by default but can be re-implemented. */ }
|
||||
|
||||
get label() {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
hasErrored(error) {
|
||||
this._notify("view:Error", error);
|
||||
this._transition(Errored, error);
|
||||
}
|
||||
|
||||
hasAborted() {
|
||||
this._transition(Aborted);
|
||||
}
|
||||
}
|
||||
class SuppConnectionPending extends State {
|
||||
suppConnected(sender, oauthOptions) {
|
||||
this._transition(PendingConfirmations, sender, oauthOptions);
|
||||
}
|
||||
}
|
||||
class PendingConfirmationsState extends State {
|
||||
localConfirmed() { throw new Error("Subclasses must implement this method."); }
|
||||
remoteConfirmed() { throw new Error("Subclasses must implement this method."); }
|
||||
}
|
||||
class PendingConfirmations extends PendingConfirmationsState {
|
||||
init(sender, oauthOptions) {
|
||||
this.sender = sender;
|
||||
this.oauthOptions = oauthOptions;
|
||||
}
|
||||
|
||||
localConfirmed() {
|
||||
this._transition(PendingRemoteConfirmation);
|
||||
}
|
||||
|
||||
remoteConfirmed() {
|
||||
this._transition(PendingLocalConfirmation, this.sender, this.oauthOptions);
|
||||
}
|
||||
}
|
||||
class PendingLocalConfirmation extends PendingConfirmationsState {
|
||||
init(sender, oauthOptions) {
|
||||
this.sender = sender;
|
||||
this.oauthOptions = oauthOptions;
|
||||
}
|
||||
|
||||
localConfirmed() {
|
||||
this._transition(Completed);
|
||||
}
|
||||
|
||||
remoteConfirmed() {
|
||||
throw new Error("Insane state! Remote has already been confirmed at this point.");
|
||||
}
|
||||
}
|
||||
class PendingRemoteConfirmation extends PendingConfirmationsState {
|
||||
localConfirmed() {
|
||||
throw new Error("Insane state! Local has already been confirmed at this point.");
|
||||
}
|
||||
|
||||
remoteConfirmed() {
|
||||
this._transition(Completed);
|
||||
}
|
||||
}
|
||||
class Completed extends State {}
|
||||
class Aborted extends State {}
|
||||
class Errored extends State {
|
||||
init(error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
const flows = new Map();
|
||||
this.FxAccountsPairingFlow = class FxAccountsPairingFlow {
|
||||
static get(channelId) {
|
||||
return flows.get(channelId);
|
||||
}
|
||||
|
||||
static finalizeAll() {
|
||||
for (const flow of flows) {
|
||||
flow.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
static async start(options) {
|
||||
const {emitter} = options;
|
||||
const fxaConfig = options.fxaConfig || FxAccounts.config;
|
||||
const fxa = options.fxAccounts || fxAccounts;
|
||||
const weave = options.weave || Weave;
|
||||
const flowTimeout = options.flowTimeout || FLOW_TIMEOUT_MS;
|
||||
|
||||
const contentPairingURI = await fxaConfig.promisePairingURI();
|
||||
const wsUri = Services.urlFormatter.formatURLPref(PREF_REMOTE_PAIRING_URI);
|
||||
const pairingChannel = options.pairingChannel || (await FxAccountsPairingChannel.create(wsUri));
|
||||
const {channelId, channelKey} = pairingChannel;
|
||||
const channelKeyB64 = ChromeUtils.base64URLEncode(channelKey, {pad: false});
|
||||
const pairingFlow = new FxAccountsPairingFlow({
|
||||
channelId,
|
||||
pairingChannel,
|
||||
emitter,
|
||||
fxa,
|
||||
fxaConfig,
|
||||
flowTimeout,
|
||||
weave,
|
||||
});
|
||||
flows.set(channelId, pairingFlow);
|
||||
|
||||
return `${contentPairingURI}#channel_id=${channelId}&channel_key=${channelKeyB64}`;
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
this._channelId = options.channelId;
|
||||
this._pairingChannel = options.pairingChannel;
|
||||
this._emitter = options.emitter;
|
||||
this._fxa = options.fxa;
|
||||
this._fxaConfig = options.fxaConfig;
|
||||
this._weave = options.weave;
|
||||
this._stateMachine = new PairingStateMachine(this._emitter);
|
||||
this._setupListeners();
|
||||
this._flowTimeoutId = setTimeout(() => this._onFlowTimeout(), options.flowTimeout);
|
||||
}
|
||||
|
||||
_onFlowTimeout() {
|
||||
log.warn(`The pairing flow ${this._channelId} timed out.`);
|
||||
this._onError(new Error("Timeout"));
|
||||
this.finalize();
|
||||
}
|
||||
|
||||
_closeChannel() {
|
||||
if (!this._closed && !this._pairingChannel.closed) {
|
||||
this._pairingChannel.close();
|
||||
this._closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
finalize() {
|
||||
this._closeChannel();
|
||||
clearTimeout(this._flowTimeoutId);
|
||||
// Free up resources and let the GC do its thing.
|
||||
flows.delete(this._channelId);
|
||||
}
|
||||
|
||||
_setupListeners() {
|
||||
this._pairingChannel.addEventListener("message", ({detail: {sender, data}}) => this.onPairingChannelMessage(sender, data));
|
||||
this._pairingChannel.addEventListener("error", event => this._onPairingChannelError(event.detail.error));
|
||||
this._emitter.on("view:Closed", () => this.onPrefViewClosed());
|
||||
}
|
||||
|
||||
_onAbort() {
|
||||
this._stateMachine.currentState.hasAborted();
|
||||
this.finalize();
|
||||
}
|
||||
|
||||
_onError(error) {
|
||||
this._stateMachine.currentState.hasErrored(error);
|
||||
this._closeChannel();
|
||||
}
|
||||
|
||||
_onPairingChannelError(error) {
|
||||
log.error("Pairing channel error", error);
|
||||
this._onError(error);
|
||||
}
|
||||
|
||||
// Any non-falsy returned value is sent back through WebChannel.
|
||||
async onWebChannelMessage(command) {
|
||||
const stateMachine = this._stateMachine;
|
||||
const curState = stateMachine.currentState;
|
||||
try {
|
||||
switch (command) {
|
||||
case COMMAND_PAIR_SUPP_METADATA:
|
||||
stateMachine.assertState([PendingConfirmations, PendingLocalConfirmation], `Wrong state for ${command}`);
|
||||
const {ua, city, region, country, remote: ipAddress} = curState.sender;
|
||||
return {ua, city, region, country, ipAddress};
|
||||
case COMMAND_PAIR_AUTHORIZE:
|
||||
stateMachine.assertState([PendingConfirmations, PendingLocalConfirmation], `Wrong state for ${command}`);
|
||||
const {client_id, state, scope, code_challenge, code_challenge_method, keys_jwk} = curState.oauthOptions;
|
||||
const authorizeParams = {
|
||||
client_id,
|
||||
access_type: "offline",
|
||||
state,
|
||||
scope,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
keys_jwk,
|
||||
};
|
||||
const codeAndState = await this._fxa.authorizeOAuthCode(authorizeParams);
|
||||
if (codeAndState.state != state) {
|
||||
throw new Error(`OAuth state mismatch`);
|
||||
}
|
||||
await this._pairingChannel.send({
|
||||
message: "pair:auth:authorize",
|
||||
data: {
|
||||
...codeAndState,
|
||||
},
|
||||
});
|
||||
curState.localConfirmed();
|
||||
break;
|
||||
case COMMAND_PAIR_DECLINE:
|
||||
this._onAbort();
|
||||
break;
|
||||
case COMMAND_PAIR_HEARTBEAT:
|
||||
if (curState instanceof Errored || this._pairingChannel.closed) {
|
||||
return {err: curState.error.message || "Pairing channel closed"};
|
||||
}
|
||||
const suppAuthorized = !(curState instanceof PendingConfirmations || curState instanceof PendingRemoteConfirmation);
|
||||
return {suppAuthorized};
|
||||
case COMMAND_PAIR_COMPLETE:
|
||||
this.finalize();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Received unknown WebChannel command: ${command}`);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
curState.hasErrored(e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async onPairingChannelMessage(sender, payload) {
|
||||
const {message} = payload;
|
||||
const stateMachine = this._stateMachine;
|
||||
const curState = stateMachine.currentState;
|
||||
try {
|
||||
switch (message) {
|
||||
case "pair:supp:request":
|
||||
stateMachine.assertState(SuppConnectionPending, `Wrong state for ${message}`);
|
||||
const oauthUri = await this._fxaConfig.promiseOAuthURI();
|
||||
const {uid, email, avatar, displayName} = await this._fxa.getSignedInUserProfile();
|
||||
const deviceName = this._weave.Service.clientsEngine.localName;
|
||||
await this._pairingChannel.send({
|
||||
message: "pair:auth:metadata",
|
||||
data: {
|
||||
email,
|
||||
avatar,
|
||||
displayName,
|
||||
deviceName,
|
||||
},
|
||||
});
|
||||
const {client_id, state, scope, code_challenge, code_challenge_method, keys_jwk} = payload.data;
|
||||
const url = new URL(oauthUri);
|
||||
url.searchParams.append("client_id", client_id);
|
||||
url.searchParams.append("scope", scope);
|
||||
url.searchParams.append("email", email);
|
||||
url.searchParams.append("uid", uid);
|
||||
url.searchParams.append("channel_id", this._channelId);
|
||||
url.searchParams.append("redirect_uri", PAIRING_REDIRECT_URI);
|
||||
this._emitter.emit("view:SwitchToWebContent", url.href);
|
||||
curState.suppConnected(sender, {
|
||||
client_id,
|
||||
state,
|
||||
scope,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
keys_jwk,
|
||||
});
|
||||
break;
|
||||
case "pair:supp:authorize":
|
||||
stateMachine.assertState([PendingConfirmations, PendingRemoteConfirmation], `Wrong state for ${message}`);
|
||||
curState.remoteConfirmed();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Received unknown Pairing Channel message: ${message}`);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
curState.hasErrored(e);
|
||||
}
|
||||
}
|
||||
|
||||
onPrefViewClosed() {
|
||||
const curState = this._stateMachine.currentState;
|
||||
// We don't want to stop the pairing process in the later stages.
|
||||
if (curState instanceof SuppConnectionPending || curState instanceof Aborted || curState instanceof Errored) {
|
||||
this.finalize();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const EXPORTED_SYMBOLS = ["FxAccountsPairingFlow"];
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ["FxAccountsProfileClient", "FxAccountsProfileClientError"];
|
||||
|
||||
const {ERRNO_NETWORK, ERRNO_PARSE, ERRNO_UNKNOWN_ERROR, ERROR_CODE_METHOD_NOT_ALLOWED, ERROR_MSG_METHOD_NOT_ALLOWED, ERROR_NETWORK, ERROR_PARSE, ERROR_UNKNOWN, log, SCOPE_PROFILE} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {ERRNO_NETWORK, ERRNO_PARSE, ERRNO_UNKNOWN_ERROR, ERROR_CODE_METHOD_NOT_ALLOWED, ERROR_MSG_METHOD_NOT_ALLOWED, ERROR_NETWORK, ERROR_PARSE, ERROR_UNKNOWN, log} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {fxAccounts} = ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
|
||||
const {RESTRequest} = ChromeUtils.import("resource://services-common/rest.js");
|
||||
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
|
@ -47,7 +47,7 @@ var FxAccountsProfileClient = function(options) {
|
|||
throw new Error("Invalid 'serverURL'");
|
||||
}
|
||||
this.oauthOptions = {
|
||||
scope: SCOPE_PROFILE,
|
||||
scope: "profile",
|
||||
};
|
||||
log.debug("FxAccountsProfileClient: Initialized");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
var EXPORTED_SYMBOLS = ["EnsureFxAccountsWebChannel"];
|
||||
|
||||
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {COMMAND_PROFILE_CHANGE, COMMAND_LOGIN, COMMAND_LOGOUT, COMMAND_DELETE, COMMAND_CAN_LINK_ACCOUNT, COMMAND_SYNC_PREFERENCES, COMMAND_CHANGE_PASSWORD, COMMAND_FXA_STATUS, COMMAND_PAIR_HEARTBEAT, COMMAND_PAIR_SUPP_METADATA, COMMAND_PAIR_AUTHORIZE, COMMAND_PAIR_DECLINE, COMMAND_PAIR_COMPLETE, COMMAND_PAIR_PREFERENCES, ON_PROFILE_CHANGE_NOTIFICATION, PREF_LAST_FXA_USER, WEBCHANNEL_ID, log, logPII} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {ON_PROFILE_CHANGE_NOTIFICATION, PREF_LAST_FXA_USER, WEBCHANNEL_ID, log, logPII} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
|
@ -29,10 +29,15 @@ ChromeUtils.defineModuleGetter(this, "Weave",
|
|||
"resource://services-sync/main.js");
|
||||
ChromeUtils.defineModuleGetter(this, "CryptoUtils",
|
||||
"resource://services-crypto/utils.js");
|
||||
ChromeUtils.defineModuleGetter(this, "FxAccountsPairingFlow",
|
||||
"resource://gre/modules/FxAccountsPairing.jsm");
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "pairingEnabled",
|
||||
"identity.fxaccounts.pairing.enabled");
|
||||
|
||||
const COMMAND_PROFILE_CHANGE = "profile:change";
|
||||
const COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
|
||||
const COMMAND_LOGIN = "fxaccounts:login";
|
||||
const COMMAND_LOGOUT = "fxaccounts:logout";
|
||||
const COMMAND_DELETE = "fxaccounts:delete";
|
||||
const COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
|
||||
const COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password";
|
||||
const COMMAND_FXA_STATUS = "fxaccounts:fxa_status";
|
||||
|
||||
// These engines were added years after Sync had been introduced, they need
|
||||
// special handling since they are system add-ons and are un-available on
|
||||
|
|
@ -140,7 +145,8 @@ this.FxAccountsWebChannel.prototype = {
|
|||
},
|
||||
|
||||
_receiveMessage(message, sendingContext) {
|
||||
const {command, data} = message;
|
||||
let command = message.command;
|
||||
let data = message.data;
|
||||
|
||||
switch (command) {
|
||||
case COMMAND_PROFILE_CHANGE:
|
||||
|
|
@ -170,13 +176,6 @@ this.FxAccountsWebChannel.prototype = {
|
|||
case COMMAND_SYNC_PREFERENCES:
|
||||
this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
|
||||
break;
|
||||
case COMMAND_PAIR_PREFERENCES:
|
||||
if (pairingEnabled) {
|
||||
sendingContext.browser.loadURI("about:preferences?action=pair#sync", {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
case COMMAND_CHANGE_PASSWORD:
|
||||
this._helpers.changePassword(data).catch(error =>
|
||||
this._sendError(error, message, sendingContext));
|
||||
|
|
@ -197,31 +196,8 @@ this.FxAccountsWebChannel.prototype = {
|
|||
this._sendError(error, message, sendingContext)
|
||||
);
|
||||
break;
|
||||
case COMMAND_PAIR_HEARTBEAT:
|
||||
case COMMAND_PAIR_SUPP_METADATA:
|
||||
case COMMAND_PAIR_AUTHORIZE:
|
||||
case COMMAND_PAIR_DECLINE:
|
||||
case COMMAND_PAIR_COMPLETE:
|
||||
log.debug(`Pairing command ${command} received`);
|
||||
const {channel_id: channelId} = data;
|
||||
delete data.channel_id;
|
||||
const flow = FxAccountsPairingFlow.get(channelId);
|
||||
if (!flow) {
|
||||
log.warn(`Could not find a pairing flow for ${channelId}`);
|
||||
return;
|
||||
}
|
||||
flow.onWebChannelMessage(command, data).then(replyData => {
|
||||
this._channel.send({
|
||||
command,
|
||||
messageId: message.messageId,
|
||||
data: replyData,
|
||||
}, sendingContext);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
log.warn("Unrecognized FxAccountsWebChannel command", command);
|
||||
// As a safety measure we also terminate any pending FxA pairing flow.
|
||||
FxAccountsPairingFlow.finalizeAll();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
@ -424,7 +400,6 @@ this.FxAccountsWebChannelHelpers.prototype = {
|
|||
return {
|
||||
signedInUser,
|
||||
capabilities: {
|
||||
pairing: pairingEnabled,
|
||||
engines: this._getAvailableExtraEngines(),
|
||||
},
|
||||
};
|
||||
|
|
@ -504,9 +479,7 @@ this.FxAccountsWebChannelHelpers.prototype = {
|
|||
}
|
||||
uri += "#sync";
|
||||
|
||||
browser.loadURI(uri, {
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
});
|
||||
browser.loadURI(uri);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ EXTRA_JS_MODULES += [
|
|||
'FxAccountsCommon.js',
|
||||
'FxAccountsConfig.jsm',
|
||||
'FxAccountsOAuthGrantClient.jsm',
|
||||
'FxAccountsPairing.jsm',
|
||||
'FxAccountsPairingChannel.js',
|
||||
'FxAccountsProfile.jsm',
|
||||
'FxAccountsProfileClient.jsm',
|
||||
'FxAccountsPush.jsm',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
const {FxAccounts} = ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
|
||||
const {FxAccountsClient} = ChromeUtils.import("resource://gre/modules/FxAccountsClient.jsm");
|
||||
const {ASSERTION_LIFETIME, CERT_LIFETIME, ERRNO_INVALID_AUTH_TOKEN, ERRNO_INVALID_FXA_ASSERTION, ERRNO_NETWORK, ERROR_INVALID_FXA_ASSERTION, ERROR_NETWORK, KEY_LIFETIME, ONLOGIN_NOTIFICATION, ONLOGOUT_NOTIFICATION, ONVERIFIED_NOTIFICATION, SCOPE_OLD_SYNC} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {ASSERTION_LIFETIME, CERT_LIFETIME, ERRNO_INVALID_AUTH_TOKEN, ERRNO_INVALID_FXA_ASSERTION, ERRNO_NETWORK, ERROR_INVALID_FXA_ASSERTION, ERROR_NETWORK, KEY_LIFETIME, ONLOGIN_NOTIFICATION, ONLOGOUT_NOTIFICATION, ONVERIFIED_NOTIFICATION} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
const {FxAccountsOAuthGrantClient, FxAccountsOAuthGrantClientError} = ChromeUtils.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
|
||||
const {PromiseUtils} = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
|
||||
|
||||
|
|
@ -739,69 +739,6 @@ add_task(async function test_getKeys_invalid_token() {
|
|||
Assert.equal(user.keyFetchToken, null);
|
||||
await fxa.internal.abortExistingFlow();
|
||||
});
|
||||
|
||||
// This is the exact same test vectors as
|
||||
// https://github.com/mozilla/fxa-crypto-relier/blob/f94f441159029a645a474d4b6439c38308da0bb0/test/deriver/ScopedKeys.js#L58
|
||||
add_task(async function test_getScopedKeys_oldsync() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let client = fxa.internal.fxAccountsClient;
|
||||
client.getScopedKeyData = () => Promise.resolve({
|
||||
"https://identity.mozilla.com/apps/oldsync": {
|
||||
"identifier": "https://identity.mozilla.com/apps/oldsync",
|
||||
"keyRotationSecret": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"keyRotationTimestamp": 1510726317123,
|
||||
},
|
||||
});
|
||||
let user = {
|
||||
...getTestUser("eusebius"),
|
||||
uid: "aeaa1725c7a24ff983c6295725d5fc9b",
|
||||
verified: true,
|
||||
kSync: "0d6fe59791b05fa489e463ea25502e3143f6b7a903aa152e95cd9c6eddbac5b4dc68a19097ef65dbd147010ee45222444e66b8b3d7c8a441ebb7dd3dce015a9e",
|
||||
kXCS: "22a42fe289dced5715135913424cb23b",
|
||||
kExtSync: "baded53eb3587d7900e604e8a68d860abf9de30b5c955d3c4d5dba63f26fd88265cd85923f6e9dcd16aef3b82bc88039a89c59ecd9e88de09a7418c7d94f90c9",
|
||||
kExtKbHash: "b776a89db29f22daedd154b44ff88397d0b210223fb956f5a749521dd8de8ddf",
|
||||
};
|
||||
await fxa.setSignedInUser(user);
|
||||
const keys = await fxa.internal.getScopedKeys(`${SCOPE_OLD_SYNC} profile`, "123456789a");
|
||||
Assert.deepEqual(keys, {
|
||||
[SCOPE_OLD_SYNC]: {
|
||||
"scope": SCOPE_OLD_SYNC,
|
||||
"kid": "1510726317123-IqQv4onc7VcVE1kTQkyyOw",
|
||||
"k": "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
|
||||
"kty": "oct",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_getScopedKeys_unavailable_key() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let client = fxa.internal.fxAccountsClient;
|
||||
client.getScopedKeyData = () => Promise.resolve({
|
||||
"https://identity.mozilla.com/apps/oldsync": {
|
||||
"identifier": "https://identity.mozilla.com/apps/oldsync",
|
||||
"keyRotationSecret": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"keyRotationTimestamp": 1510726317123,
|
||||
},
|
||||
"otherkeybearingscope": {
|
||||
"identifier": "otherkeybearingscope",
|
||||
"keyRotationSecret": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"keyRotationTimestamp": 1510726331712,
|
||||
},
|
||||
});
|
||||
let user = {
|
||||
...getTestUser("eusebius"),
|
||||
uid: "aeaa1725c7a24ff983c6295725d5fc9b",
|
||||
verified: true,
|
||||
kSync: "0d6fe59791b05fa489e463ea25502e3143f6b7a903aa152e95cd9c6eddbac5b4dc68a19097ef65dbd147010ee45222444e66b8b3d7c8a441ebb7dd3dce015a9e",
|
||||
kXCS: "22a42fe289dced5715135913424cb23b",
|
||||
kExtSync: "baded53eb3587d7900e604e8a68d860abf9de30b5c955d3c4d5dba63f26fd88265cd85923f6e9dcd16aef3b82bc88039a89c59ecd9e88de09a7418c7d94f90c9",
|
||||
kExtKbHash: "b776a89db29f22daedd154b44ff88397d0b210223fb956f5a749521dd8de8ddf",
|
||||
};
|
||||
await fxa.setSignedInUser(user);
|
||||
await Assert.rejects(fxa.internal.getScopedKeys(`${SCOPE_OLD_SYNC} otherkeybearingscope profile`, "123456789a"),
|
||||
/Unavailable key material for otherkeybearingscope/);
|
||||
});
|
||||
|
||||
// fetchAndUnwrapKeys with no keyFetchToken should trigger signOut
|
||||
add_test(function test_fetchAndUnwrapKeys_no_token() {
|
||||
let fxa = new MockFxAccounts();
|
||||
|
|
|
|||
|
|
@ -1,244 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {FxAccountsPairingFlow} = ChromeUtils.import("resource://gre/modules/FxAccountsPairing.jsm", {});
|
||||
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm", {});
|
||||
const {PromiseUtils} = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", {});
|
||||
const {CryptoUtils} = ChromeUtils.import("resource://services-crypto/utils.js", {});
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
jwcrypto: "resource://services-crypto/jwcrypto.jsm",
|
||||
});
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "crypto"]);
|
||||
|
||||
const CHANNEL_ID = "sW-UA97Q6Dljqen7XRlYPw";
|
||||
const CHANNEL_KEY = crypto.getRandomValues(new Uint8Array(32));
|
||||
|
||||
const SENDER_SUPP = {
|
||||
ua: "Firefox Supp",
|
||||
city: "Nice",
|
||||
region: "PACA",
|
||||
country: "France",
|
||||
remote: "127.0.0.1",
|
||||
};
|
||||
const UID = "abcd";
|
||||
const EMAIL = "foo@bar.com";
|
||||
const AVATAR = "https://foo.bar/avatar";
|
||||
const DISPLAY_NAME = "Foo bar";
|
||||
const DEVICE_NAME = "Foo's computer";
|
||||
|
||||
const PAIR_URI = "https://foo.bar/pair";
|
||||
const OAUTH_URI = "https://foo.bar/oauth";
|
||||
const KSYNC = "myksync";
|
||||
const fxaConfig = {
|
||||
promisePairingURI() { return PAIR_URI; },
|
||||
promiseOAuthURI() { return OAUTH_URI; },
|
||||
};
|
||||
const fxAccounts = {
|
||||
getScopedKeys(scope) {
|
||||
return {
|
||||
[scope]: {
|
||||
kid: "123456",
|
||||
k: KSYNC,
|
||||
kty: "oct",
|
||||
},
|
||||
};
|
||||
},
|
||||
authorizeOAuthCode() {
|
||||
return {code: "mycode", state: "mystate"};
|
||||
},
|
||||
getSignedInUserProfile() {
|
||||
return {
|
||||
uid: UID,
|
||||
email: EMAIL,
|
||||
avatar: AVATAR,
|
||||
displayName: DISPLAY_NAME,
|
||||
};
|
||||
},
|
||||
};
|
||||
const weave = {
|
||||
Service: { clientsEngine: { localName: DEVICE_NAME } },
|
||||
};
|
||||
|
||||
class MockPairingChannel extends EventTarget {
|
||||
get channelId() {
|
||||
return CHANNEL_ID;
|
||||
}
|
||||
|
||||
get channelKey() {
|
||||
return CHANNEL_KEY;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.dispatchEvent(new CustomEvent("send", {
|
||||
detail: { data },
|
||||
}));
|
||||
}
|
||||
|
||||
simulateIncoming(data) {
|
||||
this.dispatchEvent(new CustomEvent("message", {
|
||||
detail: { data, sender: SENDER_SUPP },
|
||||
}));
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function testFullFlow() {
|
||||
const emitter = new EventEmitter();
|
||||
const pairingChannel = new MockPairingChannel();
|
||||
const pairingUri = await FxAccountsPairingFlow.start({emitter, pairingChannel, fxAccounts, fxaConfig, weave});
|
||||
Assert.equal(pairingUri, `${PAIR_URI}#channel_id=${CHANNEL_ID}&channel_key=${ChromeUtils.base64URLEncode(CHANNEL_KEY, {pad: false})}`);
|
||||
|
||||
const flow = FxAccountsPairingFlow.get(CHANNEL_ID);
|
||||
|
||||
const promiseSwitchToWebContent = emitter.once("view:SwitchToWebContent");
|
||||
const promiseMetadataSent = promiseOutgoingMessage(pairingChannel);
|
||||
const epk = await generateEphemeralKeypair();
|
||||
pairingChannel.simulateIncoming({
|
||||
message: "pair:supp:request",
|
||||
data: {
|
||||
client_id: "client_id_1",
|
||||
state: "mystate",
|
||||
keys_jwk: ChromeUtils.base64URLEncode(new TextEncoder().encode(JSON.stringify(epk.publicJWK)), {pad: false}),
|
||||
scope: "profile https://identity.mozilla.com/apps/oldsync",
|
||||
code_challenge: "chal",
|
||||
code_challenge_method: "S256",
|
||||
},
|
||||
});
|
||||
const sentAuthMetadata = await promiseMetadataSent;
|
||||
Assert.deepEqual(sentAuthMetadata, {
|
||||
message: "pair:auth:metadata",
|
||||
data: {email: EMAIL, avatar: AVATAR, displayName: DISPLAY_NAME, deviceName: DEVICE_NAME},
|
||||
});
|
||||
const oauthUrl = await promiseSwitchToWebContent;
|
||||
Assert.equal(oauthUrl, `${OAUTH_URI}?client_id=client_id_1&scope=profile+https%3A%2F%2Fidentity.mozilla.com%2Fapps%2Foldsync&email=foo%40bar.com&uid=abcd&channel_id=${CHANNEL_ID}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob%3Apair-auth-webchannel`);
|
||||
|
||||
let pairSuppMetadata = await simulateIncomingWebChannel(flow, "fxaccounts:pair_supplicant_metadata");
|
||||
Assert.deepEqual({
|
||||
ua: "Firefox Supp",
|
||||
city: "Nice",
|
||||
region: "PACA",
|
||||
country: "France",
|
||||
ipAddress: "127.0.0.1",
|
||||
}, pairSuppMetadata);
|
||||
|
||||
const authorizeOAuthCode = sinon.spy(fxAccounts, "authorizeOAuthCode");
|
||||
const promiseOAuthParamsMsg = promiseOutgoingMessage(pairingChannel);
|
||||
await simulateIncomingWebChannel(flow, "fxaccounts:pair_authorize");
|
||||
Assert.ok(authorizeOAuthCode.calledOnce);
|
||||
const oauthCodeArgs = authorizeOAuthCode.firstCall.args[0];
|
||||
Assert.equal(oauthCodeArgs.keys_jwk, ChromeUtils.base64URLEncode(new TextEncoder().encode(JSON.stringify(epk.publicJWK)), {pad: false}));
|
||||
Assert.equal(oauthCodeArgs.client_id, "client_id_1");
|
||||
Assert.equal(oauthCodeArgs.access_type, "offline");
|
||||
Assert.equal(oauthCodeArgs.state, "mystate");
|
||||
Assert.equal(oauthCodeArgs.scope, "profile https://identity.mozilla.com/apps/oldsync");
|
||||
Assert.equal(oauthCodeArgs.code_challenge, "chal");
|
||||
Assert.equal(oauthCodeArgs.code_challenge_method, "S256");
|
||||
const oAuthParams = await promiseOAuthParamsMsg;
|
||||
Assert.deepEqual(oAuthParams, {
|
||||
"message": "pair:auth:authorize",
|
||||
"data": {"code": "mycode", "state": "mystate"},
|
||||
});
|
||||
let heartbeat = await simulateIncomingWebChannel(flow, "fxaccounts:pair_heartbeat");
|
||||
Assert.ok(!heartbeat.suppAuthorized);
|
||||
await pairingChannel.simulateIncoming({
|
||||
message: "pair:supp:authorize",
|
||||
});
|
||||
heartbeat = await simulateIncomingWebChannel(flow, "fxaccounts:pair_heartbeat");
|
||||
Assert.ok(heartbeat.suppAuthorized);
|
||||
await simulateIncomingWebChannel(flow, "fxaccounts:pair_complete");
|
||||
// The flow should have been destroyed!
|
||||
Assert.ok(!FxAccountsPairingFlow.get(CHANNEL_ID));
|
||||
Assert.ok(pairingChannel.closed);
|
||||
fxAccounts.authorizeOAuthCode.restore();
|
||||
});
|
||||
|
||||
add_task(async function testUnknownPairingMessage() {
|
||||
const emitter = new EventEmitter();
|
||||
const pairingChannel = new MockPairingChannel();
|
||||
await FxAccountsPairingFlow.start({emitter, pairingChannel, fxAccounts, fxaConfig, weave});
|
||||
const flow = FxAccountsPairingFlow.get(CHANNEL_ID);
|
||||
const viewErrorObserved = emitter.once("view:Error");
|
||||
pairingChannel.simulateIncoming({
|
||||
message: "pair:boom",
|
||||
});
|
||||
await viewErrorObserved;
|
||||
let heartbeat = await simulateIncomingWebChannel(flow, "fxaccounts:pair_heartbeat");
|
||||
Assert.ok(heartbeat.err);
|
||||
});
|
||||
|
||||
add_task(async function testUnknownWebChannelCommand() {
|
||||
const emitter = new EventEmitter();
|
||||
const pairingChannel = new MockPairingChannel();
|
||||
await FxAccountsPairingFlow.start({emitter, pairingChannel, fxAccounts, fxaConfig, weave});
|
||||
const flow = FxAccountsPairingFlow.get(CHANNEL_ID);
|
||||
const viewErrorObserved = emitter.once("view:Error");
|
||||
await simulateIncomingWebChannel(flow, "fxaccounts:boom");
|
||||
await viewErrorObserved;
|
||||
let heartbeat = await simulateIncomingWebChannel(flow, "fxaccounts:pair_heartbeat");
|
||||
Assert.ok(heartbeat.err);
|
||||
});
|
||||
|
||||
add_task(async function testPairingChannelFailure() {
|
||||
const emitter = new EventEmitter();
|
||||
const pairingChannel = new MockPairingChannel();
|
||||
await FxAccountsPairingFlow.start({emitter, pairingChannel, fxAccounts, fxaConfig, weave});
|
||||
const flow = FxAccountsPairingFlow.get(CHANNEL_ID);
|
||||
const viewErrorObserved = emitter.once("view:Error");
|
||||
sinon.stub(pairingChannel, "send").callsFake(() => { throw new Error("Boom!"); });
|
||||
pairingChannel.simulateIncoming({
|
||||
message: "pair:supp:request",
|
||||
data: {
|
||||
client_id: "client_id_1",
|
||||
state: "mystate",
|
||||
scope: "profile https://identity.mozilla.com/apps/oldsync",
|
||||
code_challenge: "chal",
|
||||
code_challenge_method: "S256",
|
||||
},
|
||||
});
|
||||
await viewErrorObserved;
|
||||
|
||||
let heartbeat = await simulateIncomingWebChannel(flow, "fxaccounts:pair_heartbeat");
|
||||
Assert.ok(heartbeat.err);
|
||||
});
|
||||
|
||||
add_task(async function testFlowTimeout() {
|
||||
const emitter = new EventEmitter();
|
||||
const pairingChannel = new MockPairingChannel();
|
||||
const viewErrorObserved = emitter.once("view:Error");
|
||||
await FxAccountsPairingFlow.start({emitter, pairingChannel, fxAccounts, fxaConfig, weave, flowTimeout: 1});
|
||||
const flow = FxAccountsPairingFlow.get(CHANNEL_ID);
|
||||
await viewErrorObserved;
|
||||
|
||||
let heartbeat = await simulateIncomingWebChannel(flow, "fxaccounts:pair_heartbeat");
|
||||
Assert.ok(heartbeat.err.match(/Timeout/));
|
||||
});
|
||||
|
||||
async function simulateIncomingWebChannel(flow, command) {
|
||||
return flow.onWebChannelMessage(command);
|
||||
}
|
||||
|
||||
async function promiseOutgoingMessage(pairingChannel) {
|
||||
return new Promise(res => {
|
||||
const onMessage = event => {
|
||||
pairingChannel.removeEventListener("send", onMessage);
|
||||
res(event.detail.data);
|
||||
};
|
||||
pairingChannel.addEventListener("send", onMessage);
|
||||
});
|
||||
}
|
||||
|
||||
async function generateEphemeralKeypair() {
|
||||
const keypair = await crypto.subtle.generateKey({name: "ECDH", namedCurve: "P-256"}, true, ["deriveKey"]);
|
||||
const publicJWK = await crypto.subtle.exportKey("jwk", keypair.publicKey);
|
||||
const privateJWK = await crypto.subtle.exportKey("jwk", keypair.privateKey);
|
||||
delete publicJWK.key_ops;
|
||||
return {
|
||||
publicJWK,
|
||||
privateJWK,
|
||||
};
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ support-files =
|
|||
[test_oauth_grant_client_server.js]
|
||||
[test_oauth_tokens.js]
|
||||
[test_oauth_token_storage.js]
|
||||
[test_pairing.js]
|
||||
[test_profile_client.js]
|
||||
[test_push_service.js]
|
||||
[test_web_channel.js]
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
"fxaccounts.jsm": ["Authentication"],
|
||||
"FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
|
||||
"FxAccountsCommands.js": ["SendTab", "FxAccountsCommands"],
|
||||
"FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_COMMAND_RECEIVED_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "ON_NEW_DEVICE_ID", "COMMAND_SENDTAB", "SCOPE_PROFILE", "SCOPE_OLD_SYNC", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "COMMAND_PAIR_HEARTBEAT", "COMMAND_PAIR_SUPP_METADATA", "COMMAND_PAIR_AUTHORIZE", "COMMAND_PAIR_DECLINE", "COMMAND_PAIR_COMPLETE", "COMMAND_PAIR_PREFERENCES", "COMMAND_PROFILE_CHANGE", "COMMAND_CAN_LINK_ACCOUNT", "COMMAND_LOGIN", "COMMAND_LOGOUT", "COMMAND_DELETE", "COMMAND_SYNC_PREFERENCES", "COMMAND_CHANGE_PASSWORD", "COMMAND_FXA_STATUS", "PREF_LAST_FXA_USER", "PREF_REMOTE_PAIRING_URI", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "DERIVED_KEYS_NAMES", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],
|
||||
"FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_COMMAND_RECEIVED_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "ON_NEW_DEVICE_ID", "COMMAND_SENDTAB", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "PREF_LAST_FXA_USER", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "DERIVED_KEYS_NAMES", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],
|
||||
"FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"],
|
||||
"FxAccountsProfileClient.jsm": ["FxAccountsProfileClient", "FxAccountsProfileClientError"],
|
||||
"FxAccountsPush.js": ["FxAccountsPushService"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue