browser/base/content/aboutaccounts/aboutaccounts.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/Services.jsm");
michael@0 10 Cu.import("resource://gre/modules/FxAccounts.jsm");
michael@0 11
michael@0 12 let fxAccountsCommon = {};
michael@0 13 Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
michael@0 14
michael@0 15 const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
michael@0 16 const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync.ui.showCustomizationDialog";
michael@0 17
michael@0 18 const OBSERVER_TOPICS = [
michael@0 19 fxAccountsCommon.ONVERIFIED_NOTIFICATION,
michael@0 20 fxAccountsCommon.ONLOGOUT_NOTIFICATION,
michael@0 21 ];
michael@0 22
michael@0 23 function log(msg) {
michael@0 24 //dump("FXA: " + msg + "\n");
michael@0 25 };
michael@0 26
michael@0 27 function error(msg) {
michael@0 28 console.log("Firefox Account Error: " + msg + "\n");
michael@0 29 };
michael@0 30
michael@0 31 function getPreviousAccountNameHash() {
michael@0 32 try {
michael@0 33 return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
michael@0 34 } catch (_) {
michael@0 35 return "";
michael@0 36 }
michael@0 37 }
michael@0 38
michael@0 39 function setPreviousAccountNameHash(acctName) {
michael@0 40 let string = Cc["@mozilla.org/supports-string;1"]
michael@0 41 .createInstance(Ci.nsISupportsString);
michael@0 42 string.data = sha256(acctName);
michael@0 43 Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
michael@0 44 }
michael@0 45
michael@0 46 function needRelinkWarning(acctName) {
michael@0 47 let prevAcctHash = getPreviousAccountNameHash();
michael@0 48 return prevAcctHash && prevAcctHash != sha256(acctName);
michael@0 49 }
michael@0 50
michael@0 51 // Given a string, returns the SHA265 hash in base64
michael@0 52 function sha256(str) {
michael@0 53 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 54 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 55 converter.charset = "UTF-8";
michael@0 56 // Data is an array of bytes.
michael@0 57 let data = converter.convertToByteArray(str, {});
michael@0 58 let hasher = Cc["@mozilla.org/security/hash;1"]
michael@0 59 .createInstance(Ci.nsICryptoHash);
michael@0 60 hasher.init(hasher.SHA256);
michael@0 61 hasher.update(data, data.length);
michael@0 62
michael@0 63 return hasher.finish(true);
michael@0 64 }
michael@0 65
michael@0 66 function promptForRelink(acctName) {
michael@0 67 let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
michael@0 68 let continueLabel = sb.GetStringFromName("continue.label");
michael@0 69 let title = sb.GetStringFromName("relinkVerify.title");
michael@0 70 let description = sb.formatStringFromName("relinkVerify.description",
michael@0 71 [acctName], 1);
michael@0 72 let body = sb.GetStringFromName("relinkVerify.heading") +
michael@0 73 "\n\n" + description;
michael@0 74 let ps = Services.prompt;
michael@0 75 let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
michael@0 76 (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
michael@0 77 ps.BUTTON_POS_1_DEFAULT;
michael@0 78 let pressed = Services.prompt.confirmEx(window, title, body, buttonFlags,
michael@0 79 continueLabel, null, null, null,
michael@0 80 {});
michael@0 81 return pressed == 0; // 0 is the "continue" button
michael@0 82 }
michael@0 83
michael@0 84 // If the last fxa account used for sync isn't this account, we display
michael@0 85 // a modal dialog checking they really really want to do this...
michael@0 86 // (This is sync-specific, so ideally would be in sync's identity module,
michael@0 87 // but it's a little more seamless to do here, and sync is currently the
michael@0 88 // only fxa consumer, so...
michael@0 89 function shouldAllowRelink(acctName) {
michael@0 90 return !needRelinkWarning(acctName) || promptForRelink(acctName);
michael@0 91 }
michael@0 92
michael@0 93 let wrapper = {
michael@0 94 iframe: null,
michael@0 95
michael@0 96 init: function (url=null) {
michael@0 97 let weave = Cc["@mozilla.org/weave/service;1"]
michael@0 98 .getService(Ci.nsISupports)
michael@0 99 .wrappedJSObject;
michael@0 100
michael@0 101 // Don't show about:accounts with FxA disabled.
michael@0 102 if (!weave.fxAccountsEnabled) {
michael@0 103 document.body.remove();
michael@0 104 return;
michael@0 105 }
michael@0 106
michael@0 107 let iframe = document.getElementById("remote");
michael@0 108 this.iframe = iframe;
michael@0 109 iframe.addEventListener("load", this);
michael@0 110
michael@0 111 try {
michael@0 112 iframe.src = url || fxAccounts.getAccountsSignUpURI();
michael@0 113 } catch (e) {
michael@0 114 error("Couldn't init Firefox Account wrapper: " + e.message);
michael@0 115 }
michael@0 116 },
michael@0 117
michael@0 118 handleEvent: function (evt) {
michael@0 119 switch (evt.type) {
michael@0 120 case "load":
michael@0 121 this.iframe.contentWindow.addEventListener("FirefoxAccountsCommand", this);
michael@0 122 this.iframe.removeEventListener("load", this);
michael@0 123 break;
michael@0 124 case "FirefoxAccountsCommand":
michael@0 125 this.handleRemoteCommand(evt);
michael@0 126 break;
michael@0 127 }
michael@0 128 },
michael@0 129
michael@0 130 /**
michael@0 131 * onLogin handler receives user credentials from the jelly after a
michael@0 132 * sucessful login and stores it in the fxaccounts service
michael@0 133 *
michael@0 134 * @param accountData the user's account data and credentials
michael@0 135 */
michael@0 136 onLogin: function (accountData) {
michael@0 137 log("Received: 'login'. Data:" + JSON.stringify(accountData));
michael@0 138
michael@0 139 if (accountData.customizeSync) {
michael@0 140 Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, true);
michael@0 141 delete accountData.customizeSync;
michael@0 142 }
michael@0 143
michael@0 144 // We need to confirm a relink - see shouldAllowRelink for more
michael@0 145 let newAccountEmail = accountData.email;
michael@0 146 // The hosted code may have already checked for the relink situation
michael@0 147 // by sending the can_link_account command. If it did, then
michael@0 148 // it will indicate we don't need to ask twice.
michael@0 149 if (!accountData.verifiedCanLinkAccount && !shouldAllowRelink(newAccountEmail)) {
michael@0 150 // we need to tell the page we successfully received the message, but
michael@0 151 // then bail without telling fxAccounts
michael@0 152 this.injectData("message", { status: "login" });
michael@0 153 // and re-init the page by navigating to about:accounts
michael@0 154 window.location = "about:accounts";
michael@0 155 return;
michael@0 156 }
michael@0 157 delete accountData.verifiedCanLinkAccount;
michael@0 158
michael@0 159 // Remember who it was so we can log out next time.
michael@0 160 setPreviousAccountNameHash(newAccountEmail);
michael@0 161
michael@0 162 // A sync-specific hack - we want to ensure sync has been initialized
michael@0 163 // before we set the signed-in user.
michael@0 164 let xps = Cc["@mozilla.org/weave/service;1"]
michael@0 165 .getService(Ci.nsISupports)
michael@0 166 .wrappedJSObject;
michael@0 167 xps.whenLoaded().then(() => {
michael@0 168 return fxAccounts.setSignedInUser(accountData);
michael@0 169 }).then(() => {
michael@0 170 // If the user data is verified, we want it to immediately look like
michael@0 171 // they are signed in without waiting for messages to bounce around.
michael@0 172 if (accountData.verified) {
michael@0 173 showManage();
michael@0 174 }
michael@0 175 this.injectData("message", { status: "login" });
michael@0 176 // until we sort out a better UX, just leave the jelly page in place.
michael@0 177 // If the account email is not yet verified, it will tell the user to
michael@0 178 // go check their email, but then it will *not* change state after
michael@0 179 // the verification completes (the browser will begin syncing, but
michael@0 180 // won't notify the user). If the email has already been verified,
michael@0 181 // the jelly will say "Welcome! You are successfully signed in as
michael@0 182 // EMAIL", but it won't then say "syncing started".
michael@0 183 }, (err) => this.injectData("message", { status: "error", error: err })
michael@0 184 );
michael@0 185 },
michael@0 186
michael@0 187 onCanLinkAccount: function(accountData) {
michael@0 188 // We need to confirm a relink - see shouldAllowRelink for more
michael@0 189 let ok = shouldAllowRelink(accountData.email);
michael@0 190 this.injectData("message", { status: "can_link_account", data: { ok: ok } });
michael@0 191 },
michael@0 192
michael@0 193 /**
michael@0 194 * onSessionStatus sends the currently signed in user's credentials
michael@0 195 * to the jelly.
michael@0 196 */
michael@0 197 onSessionStatus: function () {
michael@0 198 log("Received: 'session_status'.");
michael@0 199
michael@0 200 fxAccounts.getSignedInUser().then(
michael@0 201 (accountData) => this.injectData("message", { status: "session_status", data: accountData }),
michael@0 202 (err) => this.injectData("message", { status: "error", error: err })
michael@0 203 );
michael@0 204 },
michael@0 205
michael@0 206 /**
michael@0 207 * onSignOut handler erases the current user's session from the fxaccounts service
michael@0 208 */
michael@0 209 onSignOut: function () {
michael@0 210 log("Received: 'sign_out'.");
michael@0 211
michael@0 212 fxAccounts.signOut().then(
michael@0 213 () => this.injectData("message", { status: "sign_out" }),
michael@0 214 (err) => this.injectData("message", { status: "error", error: err })
michael@0 215 );
michael@0 216 },
michael@0 217
michael@0 218 handleRemoteCommand: function (evt) {
michael@0 219 log('command: ' + evt.detail.command);
michael@0 220 let data = evt.detail.data;
michael@0 221
michael@0 222 switch (evt.detail.command) {
michael@0 223 case "login":
michael@0 224 this.onLogin(data);
michael@0 225 break;
michael@0 226 case "can_link_account":
michael@0 227 this.onCanLinkAccount(data);
michael@0 228 break;
michael@0 229 case "session_status":
michael@0 230 this.onSessionStatus(data);
michael@0 231 break;
michael@0 232 case "sign_out":
michael@0 233 this.onSignOut(data);
michael@0 234 break;
michael@0 235 default:
michael@0 236 log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
michael@0 237 break;
michael@0 238 }
michael@0 239 },
michael@0 240
michael@0 241 injectData: function (type, content) {
michael@0 242 let authUrl;
michael@0 243 try {
michael@0 244 authUrl = fxAccounts.getAccountsSignUpURI();
michael@0 245 } catch (e) {
michael@0 246 error("Couldn't inject data: " + e.message);
michael@0 247 return;
michael@0 248 }
michael@0 249 let data = {
michael@0 250 type: type,
michael@0 251 content: content
michael@0 252 };
michael@0 253 this.iframe.contentWindow.postMessage(data, authUrl);
michael@0 254 },
michael@0 255 };
michael@0 256
michael@0 257
michael@0 258 // Button onclick handlers
michael@0 259 function handleOldSync() {
michael@0 260 let chromeWin = window
michael@0 261 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 262 .getInterface(Ci.nsIWebNavigation)
michael@0 263 .QueryInterface(Ci.nsIDocShellTreeItem)
michael@0 264 .rootTreeItem
michael@0 265 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 266 .getInterface(Ci.nsIDOMWindow)
michael@0 267 .QueryInterface(Ci.nsIDOMChromeWindow);
michael@0 268 let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
michael@0 269 chromeWin.switchToTabHavingURI(url, true);
michael@0 270 }
michael@0 271
michael@0 272 function getStarted() {
michael@0 273 hide("intro");
michael@0 274 hide("stage");
michael@0 275 show("remote");
michael@0 276 }
michael@0 277
michael@0 278 function openPrefs() {
michael@0 279 window.openPreferences("paneSync");
michael@0 280 }
michael@0 281
michael@0 282 function init() {
michael@0 283 fxAccounts.getSignedInUser().then(user => {
michael@0 284 // tests in particular might cause the window to start closing before
michael@0 285 // getSignedInUser has returned.
michael@0 286 if (window.closed) {
michael@0 287 return;
michael@0 288 }
michael@0 289 if (window.location.href.contains("action=signin")) {
michael@0 290 if (user) {
michael@0 291 // asking to sign-in when already signed in just shows manage.
michael@0 292 showManage();
michael@0 293 } else {
michael@0 294 show("remote");
michael@0 295 wrapper.init(fxAccounts.getAccountsSignInURI());
michael@0 296 }
michael@0 297 } else if (window.location.href.contains("action=signup")) {
michael@0 298 if (user) {
michael@0 299 // asking to sign-up when already signed in just shows manage.
michael@0 300 showManage();
michael@0 301 } else {
michael@0 302 show("remote");
michael@0 303 wrapper.init();
michael@0 304 }
michael@0 305 } else if (window.location.href.contains("action=reauth")) {
michael@0 306 // ideally we would only show this when we know the user is in a
michael@0 307 // "must reauthenticate" state - but we don't.
michael@0 308 // As the email address will be included in the URL returned from
michael@0 309 // promiseAccountsForceSigninURI, just always show it.
michael@0 310 fxAccounts.promiseAccountsForceSigninURI().then(url => {
michael@0 311 show("remote");
michael@0 312 wrapper.init(url);
michael@0 313 });
michael@0 314 } else {
michael@0 315 // No action specified
michael@0 316 if (user) {
michael@0 317 showManage();
michael@0 318 let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
michael@0 319 document.title = sb.GetStringFromName("manage.pageTitle");
michael@0 320 } else {
michael@0 321 show("stage");
michael@0 322 show("intro");
michael@0 323 // load the remote frame in the background
michael@0 324 wrapper.init();
michael@0 325 }
michael@0 326 }
michael@0 327 });
michael@0 328 }
michael@0 329
michael@0 330 function show(id) {
michael@0 331 document.getElementById(id).style.display = 'block';
michael@0 332 }
michael@0 333 function hide(id) {
michael@0 334 document.getElementById(id).style.display = 'none';
michael@0 335 }
michael@0 336
michael@0 337 function showManage() {
michael@0 338 show("stage");
michael@0 339 show("manage");
michael@0 340 hide("remote");
michael@0 341 hide("intro");
michael@0 342 }
michael@0 343
michael@0 344 document.addEventListener("DOMContentLoaded", function onload() {
michael@0 345 document.removeEventListener("DOMContentLoaded", onload, true);
michael@0 346 init();
michael@0 347 }, true);
michael@0 348
michael@0 349 function initObservers() {
michael@0 350 function observe(subject, topic, data) {
michael@0 351 log("about:accounts observed " + topic);
michael@0 352 if (topic == fxAccountsCommon.ONLOGOUT_NOTIFICATION) {
michael@0 353 // All about:account windows get changed to action=signin on logout.
michael@0 354 window.location = "about:accounts?action=signin";
michael@0 355 return;
michael@0 356 }
michael@0 357 // must be onverified - just about:accounts is loaded.
michael@0 358 window.location = "about:accounts";
michael@0 359 }
michael@0 360
michael@0 361 for (let topic of OBSERVER_TOPICS) {
michael@0 362 Services.obs.addObserver(observe, topic, false);
michael@0 363 }
michael@0 364 window.addEventListener("unload", function(event) {
michael@0 365 log("about:accounts unloading")
michael@0 366 for (let topic of OBSERVER_TOPICS) {
michael@0 367 Services.obs.removeObserver(observe, topic);
michael@0 368 }
michael@0 369 });
michael@0 370 }
michael@0 371 initObservers();

mercurial