services/fxaccounts/FxAccountsManager.jsm

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 file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /**
michael@0 6 * Temporary abstraction layer for common Fx Accounts operations.
michael@0 7 * For now, we will be using this module only from B2G but in the end we might
michael@0 8 * want this to be merged with FxAccounts.jsm and let other products also use
michael@0 9 * it.
michael@0 10 */
michael@0 11
michael@0 12 "use strict";
michael@0 13
michael@0 14 this.EXPORTED_SYMBOLS = ["FxAccountsManager"];
michael@0 15
michael@0 16 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
michael@0 17
michael@0 18 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 19 Cu.import("resource://gre/modules/Services.jsm");
michael@0 20 Cu.import("resource://gre/modules/FxAccounts.jsm");
michael@0 21 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 22 Cu.import("resource://gre/modules/FxAccountsCommon.js");
michael@0 23
michael@0 24 this.FxAccountsManager = {
michael@0 25
michael@0 26 init: function() {
michael@0 27 Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
michael@0 28 },
michael@0 29
michael@0 30 observe: function(aSubject, aTopic, aData) {
michael@0 31 if (aTopic !== ONLOGOUT_NOTIFICATION) {
michael@0 32 return;
michael@0 33 }
michael@0 34
michael@0 35 // Remove the cached session if we get a logout notification.
michael@0 36 this._activeSession = null;
michael@0 37 },
michael@0 38
michael@0 39 // We don't really need to save fxAccounts instance but this way we allow
michael@0 40 // to mock FxAccounts from tests.
michael@0 41 _fxAccounts: fxAccounts,
michael@0 42
michael@0 43 // We keep the session details here so consumers don't need to deal with
michael@0 44 // session tokens and are only required to handle the email.
michael@0 45 _activeSession: null,
michael@0 46
michael@0 47 // We only expose the email and the verified status so far.
michael@0 48 get _user() {
michael@0 49 if (!this._activeSession || !this._activeSession.email) {
michael@0 50 return null;
michael@0 51 }
michael@0 52
michael@0 53 return {
michael@0 54 accountId: this._activeSession.email,
michael@0 55 verified: this._activeSession.verified
michael@0 56 }
michael@0 57 },
michael@0 58
michael@0 59 _error: function(aError, aDetails) {
michael@0 60 log.error(aError);
michael@0 61 let reason = {
michael@0 62 error: aError
michael@0 63 };
michael@0 64 if (aDetails) {
michael@0 65 reason.details = aDetails;
michael@0 66 }
michael@0 67 return Promise.reject(reason);
michael@0 68 },
michael@0 69
michael@0 70 _getError: function(aServerResponse) {
michael@0 71 if (!aServerResponse || !aServerResponse.error || !aServerResponse.error.errno) {
michael@0 72 return;
michael@0 73 }
michael@0 74 let error = SERVER_ERRNO_TO_ERROR[aServerResponse.error.errno];
michael@0 75 return error;
michael@0 76 },
michael@0 77
michael@0 78 _serverError: function(aServerResponse) {
michael@0 79 let error = this._getError({ error: aServerResponse });
michael@0 80 return this._error(error ? error : ERROR_SERVER_ERROR, aServerResponse);
michael@0 81 },
michael@0 82
michael@0 83 // As with _fxAccounts, we don't really need this method, but this way we
michael@0 84 // allow tests to mock FxAccountsClient. By default, we want to return the
michael@0 85 // client used by the fxAccounts object because deep down they should have
michael@0 86 // access to the same hawk request object which will enable them to share
michael@0 87 // local clock skeq data.
michael@0 88 _getFxAccountsClient: function() {
michael@0 89 return this._fxAccounts.getAccountsClient();
michael@0 90 },
michael@0 91
michael@0 92 _signInSignUp: function(aMethod, aAccountId, aPassword) {
michael@0 93 if (Services.io.offline) {
michael@0 94 return this._error(ERROR_OFFLINE);
michael@0 95 }
michael@0 96
michael@0 97 if (!aAccountId) {
michael@0 98 return this._error(ERROR_INVALID_ACCOUNTID);
michael@0 99 }
michael@0 100
michael@0 101 if (!aPassword) {
michael@0 102 return this._error(ERROR_INVALID_PASSWORD);
michael@0 103 }
michael@0 104
michael@0 105 // Check that there is no signed in account first.
michael@0 106 if (this._activeSession) {
michael@0 107 return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
michael@0 108 user: this._user
michael@0 109 });
michael@0 110 }
michael@0 111
michael@0 112 let client = this._getFxAccountsClient();
michael@0 113 return this._fxAccounts.getSignedInUser().then(
michael@0 114 user => {
michael@0 115 if (user) {
michael@0 116 return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
michael@0 117 user: this._user
michael@0 118 });
michael@0 119 }
michael@0 120 return client[aMethod](aAccountId, aPassword);
michael@0 121 }
michael@0 122 ).then(
michael@0 123 user => {
michael@0 124 let error = this._getError(user);
michael@0 125 if (!user || !user.uid || !user.sessionToken || error) {
michael@0 126 return this._error(error ? error : ERROR_INTERNAL_INVALID_USER, {
michael@0 127 user: user
michael@0 128 });
michael@0 129 }
michael@0 130
michael@0 131 // If the user object includes an email field, it may differ in
michael@0 132 // capitalization from what we sent down. This is the server's
michael@0 133 // canonical capitalization and should be used instead.
michael@0 134 user.email = user.email || aAccountId;
michael@0 135 return this._fxAccounts.setSignedInUser(user).then(
michael@0 136 () => {
michael@0 137 this._activeSession = user;
michael@0 138 log.debug("User signed in: " + JSON.stringify(this._user) +
michael@0 139 " - Account created " + (aMethod == "signUp"));
michael@0 140 return Promise.resolve({
michael@0 141 accountCreated: aMethod === "signUp",
michael@0 142 user: this._user
michael@0 143 });
michael@0 144 }
michael@0 145 );
michael@0 146 },
michael@0 147 reason => { return this._serverError(reason); }
michael@0 148 );
michael@0 149 },
michael@0 150
michael@0 151 _getAssertion: function(aAudience) {
michael@0 152 return this._fxAccounts.getAssertion(aAudience);
michael@0 153 },
michael@0 154
michael@0 155 _signOut: function() {
michael@0 156 if (!this._activeSession) {
michael@0 157 return Promise.resolve();
michael@0 158 }
michael@0 159
michael@0 160 // We clear the local session cache as soon as we get the onlogout
michael@0 161 // notification triggered within FxAccounts.signOut, so we save the
michael@0 162 // session token value to be able to remove the remote server session
michael@0 163 // in case that we have network connection.
michael@0 164 let sessionToken = this._activeSession.sessionToken;
michael@0 165
michael@0 166 return this._fxAccounts.signOut(true).then(
michael@0 167 () => {
michael@0 168 // At this point the local session should already be removed.
michael@0 169
michael@0 170 // The client can create new sessions up to the limit (100?).
michael@0 171 // Orphaned tokens on the server will eventually be garbage collected.
michael@0 172 if (Services.io.offline) {
michael@0 173 return Promise.resolve();
michael@0 174 }
michael@0 175 // Otherwise, we try to remove the remote session.
michael@0 176 let client = this._getFxAccountsClient();
michael@0 177 return client.signOut(sessionToken).then(
michael@0 178 result => {
michael@0 179 let error = this._getError(result);
michael@0 180 if (error) {
michael@0 181 return this._error(error, result);
michael@0 182 }
michael@0 183 log.debug("Signed out");
michael@0 184 return Promise.resolve();
michael@0 185 },
michael@0 186 reason => {
michael@0 187 return this._serverError(reason);
michael@0 188 }
michael@0 189 );
michael@0 190 }
michael@0 191 );
michael@0 192 },
michael@0 193
michael@0 194 _uiRequest: function(aRequest, aAudience, aParams) {
michael@0 195 let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
michael@0 196 .createInstance(Ci.nsIFxAccountsUIGlue);
michael@0 197 if (!ui[aRequest]) {
michael@0 198 return this._error(ERROR_UI_REQUEST);
michael@0 199 }
michael@0 200
michael@0 201 if (!aParams || !Array.isArray(aParams)) {
michael@0 202 aParams = [aParams];
michael@0 203 }
michael@0 204
michael@0 205 return ui[aRequest].apply(this, aParams).then(
michael@0 206 result => {
michael@0 207 // Even if we get a successful result from the UI, the account will
michael@0 208 // most likely be unverified, so we cannot get an assertion.
michael@0 209 if (result && result.verified) {
michael@0 210 return this._getAssertion(aAudience);
michael@0 211 }
michael@0 212
michael@0 213 return this._error(ERROR_UNVERIFIED_ACCOUNT, {
michael@0 214 user: result
michael@0 215 });
michael@0 216 },
michael@0 217 error => {
michael@0 218 return this._error(ERROR_UI_ERROR, error);
michael@0 219 }
michael@0 220 );
michael@0 221 },
michael@0 222
michael@0 223 // -- API --
michael@0 224
michael@0 225 signIn: function(aAccountId, aPassword) {
michael@0 226 return this._signInSignUp("signIn", aAccountId, aPassword);
michael@0 227 },
michael@0 228
michael@0 229 signUp: function(aAccountId, aPassword) {
michael@0 230 return this._signInSignUp("signUp", aAccountId, aPassword);
michael@0 231 },
michael@0 232
michael@0 233 signOut: function() {
michael@0 234 if (!this._activeSession) {
michael@0 235 // If there is no cached active session, we try to get it from the
michael@0 236 // account storage.
michael@0 237 return this.getAccount().then(
michael@0 238 result => {
michael@0 239 if (!result) {
michael@0 240 return Promise.resolve();
michael@0 241 }
michael@0 242 return this._signOut();
michael@0 243 }
michael@0 244 );
michael@0 245 }
michael@0 246 return this._signOut();
michael@0 247 },
michael@0 248
michael@0 249 getAccount: function() {
michael@0 250 // We check first if we have session details cached.
michael@0 251 if (this._activeSession) {
michael@0 252 // If our cache says that the account is not yet verified,
michael@0 253 // we kick off verification before returning what we have.
michael@0 254 if (this._activeSession && !this._activeSession.verified &&
michael@0 255 !Services.io.offline) {
michael@0 256 this.verificationStatus(this._activeSession);
michael@0 257 }
michael@0 258
michael@0 259 log.debug("Account " + JSON.stringify(this._user));
michael@0 260 return Promise.resolve(this._user);
michael@0 261 }
michael@0 262
michael@0 263 // If no cached information, we try to get it from the persistent storage.
michael@0 264 return this._fxAccounts.getSignedInUser().then(
michael@0 265 user => {
michael@0 266 if (!user || !user.email) {
michael@0 267 log.debug("No signed in account");
michael@0 268 return Promise.resolve(null);
michael@0 269 }
michael@0 270
michael@0 271 this._activeSession = user;
michael@0 272 // If we get a stored information of a not yet verified account,
michael@0 273 // we kick off verification before returning what we have.
michael@0 274 if (!user.verified && !Services.io.offline) {
michael@0 275 log.debug("Unverified account");
michael@0 276 this.verificationStatus(user);
michael@0 277 }
michael@0 278
michael@0 279 log.debug("Account " + JSON.stringify(this._user));
michael@0 280 return Promise.resolve(this._user);
michael@0 281 }
michael@0 282 );
michael@0 283 },
michael@0 284
michael@0 285 queryAccount: function(aAccountId) {
michael@0 286 log.debug("queryAccount " + aAccountId);
michael@0 287 if (Services.io.offline) {
michael@0 288 return this._error(ERROR_OFFLINE);
michael@0 289 }
michael@0 290
michael@0 291 let deferred = Promise.defer();
michael@0 292
michael@0 293 if (!aAccountId) {
michael@0 294 return this._error(ERROR_INVALID_ACCOUNTID);
michael@0 295 }
michael@0 296
michael@0 297 let client = this._getFxAccountsClient();
michael@0 298 return client.accountExists(aAccountId).then(
michael@0 299 result => {
michael@0 300 log.debug("Account " + result ? "" : "does not" + " exists");
michael@0 301 let error = this._getError(result);
michael@0 302 if (error) {
michael@0 303 return this._error(error, result);
michael@0 304 }
michael@0 305
michael@0 306 return Promise.resolve({
michael@0 307 registered: result
michael@0 308 });
michael@0 309 },
michael@0 310 reason => { this._serverError(reason); }
michael@0 311 );
michael@0 312 },
michael@0 313
michael@0 314 verificationStatus: function() {
michael@0 315 log.debug("verificationStatus");
michael@0 316 if (!this._activeSession || !this._activeSession.sessionToken) {
michael@0 317 this._error(ERROR_NO_TOKEN_SESSION);
michael@0 318 }
michael@0 319
michael@0 320 // There is no way to unverify an already verified account, so we just
michael@0 321 // return the account details of a verified account
michael@0 322 if (this._activeSession.verified) {
michael@0 323 log.debug("Account already verified");
michael@0 324 return;
michael@0 325 }
michael@0 326
michael@0 327 if (Services.io.offline) {
michael@0 328 this._error(ERROR_OFFLINE);
michael@0 329 }
michael@0 330
michael@0 331 let client = this._getFxAccountsClient();
michael@0 332 client.recoveryEmailStatus(this._activeSession.sessionToken).then(
michael@0 333 data => {
michael@0 334 let error = this._getError(data);
michael@0 335 if (error) {
michael@0 336 this._error(error, data);
michael@0 337 }
michael@0 338 // If the verification status has changed, update state.
michael@0 339 if (this._activeSession.verified != data.verified) {
michael@0 340 this._activeSession.verified = data.verified;
michael@0 341 this._fxAccounts.setSignedInUser(this._activeSession);
michael@0 342 }
michael@0 343 log.debug(JSON.stringify(this._user));
michael@0 344 },
michael@0 345 reason => { this._serverError(reason); }
michael@0 346 );
michael@0 347 },
michael@0 348
michael@0 349 /*
michael@0 350 * Try to get an assertion for the given audience.
michael@0 351 *
michael@0 352 * aOptions can include:
michael@0 353 *
michael@0 354 * refreshAuthentication - (bool) Force re-auth.
michael@0 355 *
michael@0 356 * silent - (bool) Prevent any UI interaction.
michael@0 357 * I.e., try to get an automatic assertion.
michael@0 358 *
michael@0 359 */
michael@0 360 getAssertion: function(aAudience, aOptions) {
michael@0 361 if (!aAudience) {
michael@0 362 return this._error(ERROR_INVALID_AUDIENCE);
michael@0 363 }
michael@0 364
michael@0 365 if (Services.io.offline) {
michael@0 366 return this._error(ERROR_OFFLINE);
michael@0 367 }
michael@0 368
michael@0 369 return this.getAccount().then(
michael@0 370 user => {
michael@0 371 if (user) {
michael@0 372 // We cannot get assertions for unverified accounts.
michael@0 373 if (!user.verified) {
michael@0 374 return this._error(ERROR_UNVERIFIED_ACCOUNT, {
michael@0 375 user: user
michael@0 376 });
michael@0 377 }
michael@0 378
michael@0 379 // RPs might require an authentication refresh.
michael@0 380 if (aOptions &&
michael@0 381 aOptions.refreshAuthentication) {
michael@0 382 let gracePeriod = aOptions.refreshAuthentication;
michael@0 383 if (typeof gracePeriod != 'number' || isNaN(gracePeriod)) {
michael@0 384 return this._error(ERROR_INVALID_REFRESH_AUTH_VALUE);
michael@0 385 }
michael@0 386
michael@0 387 if ((Date.now() / 1000) - this._activeSession.authAt > gracePeriod) {
michael@0 388 // Grace period expired, so we sign out and request the user to
michael@0 389 // authenticate herself again. If the authentication succeeds, we
michael@0 390 // will return the assertion. Otherwise, we will return an error.
michael@0 391 return this._signOut().then(
michael@0 392 () => {
michael@0 393 if (aOptions.silent) {
michael@0 394 return Promise.resolve(null);
michael@0 395 }
michael@0 396 return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
michael@0 397 aAudience, user.accountId);
michael@0 398 }
michael@0 399 );
michael@0 400 }
michael@0 401 }
michael@0 402
michael@0 403 return this._getAssertion(aAudience);
michael@0 404 }
michael@0 405
michael@0 406 log.debug("No signed in user");
michael@0 407
michael@0 408 if (aOptions && aOptions.silent) {
michael@0 409 return Promise.resolve(null);
michael@0 410 }
michael@0 411
michael@0 412 // If there is no currently signed in user, we trigger the signIn UI
michael@0 413 // flow.
michael@0 414 return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);
michael@0 415 }
michael@0 416 );
michael@0 417 }
michael@0 418
michael@0 419 };
michael@0 420
michael@0 421 FxAccountsManager.init();

mercurial