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.

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

mercurial