mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 949526 - FxAccountsManager and B2G implementation. Part 1: FxAccountsManager. r=markh
This commit is contained in:
parent
fde745a83e
commit
038666babf
@ -16,18 +16,11 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsConsts.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
|
||||
"resource://gre/modules/identity/jwcrypto.jsm");
|
||||
|
||||
const DATA_FORMAT_VERSION = 1;
|
||||
const DEFAULT_STORAGE_FILENAME = "signedInUser.json";
|
||||
const ASSERTION_LIFETIME = 1000 * 60 * 5; // 5 minutes
|
||||
const KEY_LIFETIME = 1000 * 3600 * 12; // 12 hours
|
||||
const CERT_LIFETIME = 1000 * 3600 * 6; // 6 hours
|
||||
const POLL_SESSION = 1000 * 60 * 5; // 5 minutes
|
||||
const POLL_STEP = 1000 * 3; // 3 seconds
|
||||
|
||||
// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
|
||||
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
|
||||
// default.
|
||||
|
85
services/fxaccounts/FxAccountsConsts.js
Normal file
85
services/fxaccounts/FxAccountsConsts.js
Normal file
@ -0,0 +1,85 @@
|
||||
/* 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/. */
|
||||
|
||||
this.DATA_FORMAT_VERSION = 1;
|
||||
this.DEFAULT_STORAGE_FILENAME = "signedInUser.json";
|
||||
|
||||
// Token life times.
|
||||
this.ASSERTION_LIFETIME = 1000 * 60 * 5; // 5 minutes
|
||||
this.CERT_LIFETIME = 1000 * 3600 * 6; // 6 hours
|
||||
this.KEY_LIFETIME = 1000 * 3600 * 12; // 12 hours
|
||||
|
||||
// Polling timings.
|
||||
this.POLL_SESSION = 1000 * 60 * 5; // 5 minutes
|
||||
this.POLL_STEP = 1000 * 3; // 3 seconds
|
||||
|
||||
// Server errno.
|
||||
// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
|
||||
this.ERRNO_ACCOUNT_ALREADY_EXISTS = 101;
|
||||
this.ERRNO_ACCOUNT_DOES_NOT_EXISTS = 102;
|
||||
this.ERRNO_INCORRECT_PASSWORD = 103;
|
||||
this.ERRNO_UNVERIFIED_ACCOUNT = 104;
|
||||
this.ERRNO_INVALID_VERIFICATION_CODE = 105;
|
||||
this.ERRNO_NOT_VALID_JSON_BODY = 106;
|
||||
this.ERRNO_INVALID_BODY_PARAMETERS = 107;
|
||||
this.ERRNO_MISSING_BODY_PARAMETERS = 108;
|
||||
this.ERRNO_INVALID_REQUEST_SIGNATURE = 109;
|
||||
this.ERRNO_INVALID_AUTH_TOKEN = 110;
|
||||
this.ERRNO_INVALID_AUTH_TIMESTAMP = 111;
|
||||
this.ERRNO_MISSING_CONTENT_LENGTH = 112;
|
||||
this.ERRNO_REQUEST_BODY_TOO_LARGE = 113;
|
||||
this.ERRNO_TOO_MANY_CLIENT_REQUESTS = 114;
|
||||
this.ERRNO_INVALID_AUTH_NONCE = 115;
|
||||
this.ERRNO_SERVICE_TEMP_UNAVAILABLE = 201;
|
||||
this.ERRNO_UNKNOWN_ERROR = 999;
|
||||
|
||||
// Errors.
|
||||
this.ERROR_ACCOUNT_ALREADY_EXISTS = "ACCOUNT_ALREADY_EXISTS";
|
||||
this.ERROR_ACCOUNT_DOES_NOT_EXISTS = "ACCOUNT_DOES_NOT_EXISTS";
|
||||
this.ERROR_ALREADY_SIGNED_IN_USER = "ALREADY_SIGNED_IN_USER";
|
||||
this.ERROR_INVALID_ACCOUNTID = "INVALID_ACCOUNTID";
|
||||
this.ERROR_INVALID_AUDIENCE = "INVALID_AUDIENCE";
|
||||
this.ERROR_INVALID_AUTH_TOKEN = "INVALID_AUTH_TOKEN";
|
||||
this.ERROR_INVALID_AUTH_TIMESTAMP = "INVALID_AUTH_TIMESTAMP";
|
||||
this.ERROR_INVALID_AUTH_NONCE = "INVALID_AUTH_NONCE";
|
||||
this.ERROR_INVALID_BODY_PARAMETERS = "INVALID_BODY_PARAMETERS";
|
||||
this.ERROR_INVALID_PASSWORD = "INVALID_PASSWORD";
|
||||
this.ERROR_INVALID_VERIFICATION_CODE = "INVALID_VERIFICATION_CODE";
|
||||
this.ERROR_INVALID_REQUEST_SIGNATURE = "INVALID_REQUEST_SIGNATURE";
|
||||
this.ERROR_INTERNAL_INVALID_USER = "INTERNAL_ERROR_INVALID_USER";
|
||||
this.ERROR_MISSING_BODY_PARAMETERS = "MISSING_BODY_PARAMETERS";
|
||||
this.ERROR_MISSING_CONTENT_LENGTH = "MISSING_CONTENT_LENGTH";
|
||||
this.ERROR_NO_TOKEN_SESSION = "NO_TOKEN_SESSION";
|
||||
this.ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY";
|
||||
this.ERROR_OFFLINE = "OFFLINE";
|
||||
this.ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
|
||||
this.ERROR_SERVER_ERROR = "SERVER_ERROR";
|
||||
this.ERROR_TOO_MANY_CLIENT_REQUESTS = "TOO_MANY_CLIENT_REQUESTS";
|
||||
this.ERROR_SERVICE_TEMP_UNAVAILABLE = "SERVICE_TEMPORARY_UNAVAILABLE";
|
||||
this.ERROR_UI_ERROR = "UI_ERROR";
|
||||
this.ERROR_UNKNOWN = "UNKNOWN_ERROR";
|
||||
this.ERROR_UNVERIFIED_ACCOUNT = "UNVERIFIED_ACCOUNT";
|
||||
|
||||
// Error matching.
|
||||
this.SERVER_ERRNO_TO_ERROR = {};
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_ALREADY_EXISTS] = ERROR_ACCOUNT_ALREADY_EXISTS;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXISTS] = ERROR_ACCOUNT_DOES_NOT_EXISTS;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_PASSWORD] = ERROR_INVALID_PASSWORD;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_UNVERIFIED_ACCOUNT] = ERROR_UNVERIFIED_ACCOUNT;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_VERIFICATION_CODE] = ERROR_INVALID_VERIFICATION_CODE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_NOT_VALID_JSON_BODY] = ERROR_NOT_VALID_JSON_BODY;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_BODY_PARAMETERS] = ERROR_INVALID_BODY_PARAMETERS;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_BODY_PARAMETERS] = ERROR_MISSING_BODY_PARAMETERS;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_REQUEST_SIGNATURE] = ERROR_INVALID_REQUEST_SIGNATURE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TOKEN] = ERROR_INVALID_AUTH_TOKEN;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TIMESTAMP] = ERROR_INVALID_AUTH_TIMESTAMP;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_CONTENT_LENGTH] = ERROR_MISSING_CONTENT_LENGTH;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_REQUEST_BODY_TOO_LARGE] = ERROR_REQUEST_BODY_TOO_LARGE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_CLIENT_REQUESTS] = ERROR_TOO_MANY_CLIENT_REQUESTS;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_NONCE] = ERROR_INVALID_AUTH_NONCE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE] = ERROR_SERVICE_TEMP_UNAVAILABLE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR] = ERROR_UNKNOWN;
|
||||
|
||||
// Allow this file to be imported via Components.utils.import().
|
||||
this.EXPORTED_SYMBOLS = Object.keys(this);
|
382
services/fxaccounts/FxAccountsManager.jsm
Normal file
382
services/fxaccounts/FxAccountsManager.jsm
Normal file
@ -0,0 +1,382 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Temporary abstraction layer for common Fx Accounts operations.
|
||||
* For now, we will be using this module only from B2G but in the end we might
|
||||
* want this to be merged with FxAccounts.jsm and let other products also use
|
||||
* it.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FxAccountsManager"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsConsts.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsClient",
|
||||
"resource://gre/modules/FxAccountsClient.jsm");
|
||||
|
||||
this.FxAccountsManager = {
|
||||
|
||||
// We don't really need to save fxAccounts instance but this way we allow
|
||||
// to mock FxAccounts from tests.
|
||||
_fxAccounts: fxAccounts,
|
||||
|
||||
// We keep the session details here so consumers don't need to deal with
|
||||
// session tokens and are only required to handle the email.
|
||||
_activeSession: null,
|
||||
|
||||
// We only expose the email and the verified status so far.
|
||||
get _user() {
|
||||
if (!this._activeSession || !this._activeSession.email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
accountId: this._activeSession.email,
|
||||
verified: this._activeSession.verified
|
||||
}
|
||||
},
|
||||
|
||||
_getError: function(aServerResponse) {
|
||||
if (!aServerResponse || !aServerResponse.error || !aServerResponse.error.errno) {
|
||||
return;
|
||||
}
|
||||
return SERVER_ERRNO_TO_ERROR[aServerResponse.error.errno];
|
||||
},
|
||||
|
||||
_serverError: function(aServerResponse) {
|
||||
let error = this._getError({ error: aServerResponse });
|
||||
return Promise.reject({
|
||||
error: error ? error : ERROR_SERVER_ERROR,
|
||||
details: aServerResponse
|
||||
});
|
||||
},
|
||||
|
||||
// As we do with _fxAccounts, we don't really need this factory, but this way
|
||||
// we allow tests to mock FxAccountsClient.
|
||||
_createFxAccountsClient: function() {
|
||||
return new FxAccountsClient();
|
||||
},
|
||||
|
||||
_signInSignUp: function(aMethod, aAccountId, aPassword) {
|
||||
if (Services.io.offline) {
|
||||
return Promise.reject({
|
||||
error: ERROR_OFFLINE
|
||||
});
|
||||
}
|
||||
|
||||
if (!aAccountId) {
|
||||
return Promise.reject({
|
||||
error: ERROR_INVALID_ACCOUNTID
|
||||
});
|
||||
}
|
||||
|
||||
if (!aPassword) {
|
||||
return Promise.reject({
|
||||
error: ERROR_INVALID_PASSWORD
|
||||
});
|
||||
}
|
||||
|
||||
// Check that there is no signed in account first.
|
||||
if (this._activeSession) {
|
||||
return Promise.reject({
|
||||
error: ERROR_ALREADY_SIGNED_IN_USER,
|
||||
details: {
|
||||
user: this._user
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let client = this._createFxAccountsClient();
|
||||
return this._fxAccounts.getSignedInUser().then(
|
||||
user => {
|
||||
if (user) {
|
||||
return Promise.reject({
|
||||
error: ERROR_ALREADY_SIGNED_IN_USER,
|
||||
details: {
|
||||
user: user
|
||||
}
|
||||
});
|
||||
}
|
||||
return client[aMethod](aAccountId, aPassword);
|
||||
}
|
||||
).then(
|
||||
user => {
|
||||
let error = this._getError(user);
|
||||
if (!user || !user.uid || !user.sessionToken || error) {
|
||||
return Promise.reject({
|
||||
error: error ? error : ERROR_INTERNAL_INVALID_USER,
|
||||
details: {
|
||||
user: user
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save the credentials of the signed in user.
|
||||
user.email = aAccountId;
|
||||
return this._fxAccounts.setSignedInUser(user, false).then(
|
||||
() => {
|
||||
this._activeSession = user;
|
||||
return Promise.resolve({
|
||||
accountCreated: aMethod === "signUp",
|
||||
user: this._user
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
reason => { return this._serverError(reason); }
|
||||
);
|
||||
},
|
||||
|
||||
_getAssertion: function(aAudience) {
|
||||
return this._fxAccounts.getAssertion(aAudience);
|
||||
},
|
||||
|
||||
_signOut: function() {
|
||||
if (!this._activeSession) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this._fxAccounts.signOut(this._activeSession.sessionToken).then(
|
||||
() => {
|
||||
// If there is no connection, removing the local session should be
|
||||
// enough. The client can create new sessions up to the limit (100?).
|
||||
// Orphaned tokens on the server will eventually be garbage collected.
|
||||
if (Services.io.offline) {
|
||||
this._activeSession = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Otherwise, we try to remove the remote session.
|
||||
let client = this._createFxAccountsClient();
|
||||
return client.signOut(this._activeSession.sessionToken).then(
|
||||
result => {
|
||||
// Even if there is a remote server error, we remove the local
|
||||
// session.
|
||||
this._activeSession = null;
|
||||
let error = this._getError(result);
|
||||
if (error) {
|
||||
return Promise.reject({
|
||||
error: error,
|
||||
details: result
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
reason => {
|
||||
// Even if there is a remote server error, we remove the local
|
||||
// session.
|
||||
this._activeSession = null;
|
||||
return this._serverError(reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// -- API --
|
||||
|
||||
signIn: function(aAccountId, aPassword) {
|
||||
return this._signInSignUp("signIn", aAccountId, aPassword);
|
||||
},
|
||||
|
||||
signUp: function(aAccountId, aPassword) {
|
||||
return this._signInSignUp("signUp", aAccountId, aPassword);
|
||||
},
|
||||
|
||||
signOut: function() {
|
||||
if (!this._activeSession) {
|
||||
// If there is no cached active session, we try to get it from the
|
||||
// account storage.
|
||||
return this.getAccount().then(
|
||||
result => {
|
||||
if (!result) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._signOut();
|
||||
}
|
||||
);
|
||||
}
|
||||
return this._signOut();
|
||||
},
|
||||
|
||||
getAccount: function() {
|
||||
// We check first if we have session details cached.
|
||||
if (this._activeSession) {
|
||||
// If our cache says that the account is not yet verified, we check that
|
||||
// this information is correct, and update the cached data if not.
|
||||
if (this._activeSession && !this._activeSession.verified &&
|
||||
!Services.io.offline) {
|
||||
return this.verificationStatus(this._activeSession);
|
||||
}
|
||||
|
||||
return Promise.resolve(this._user);
|
||||
}
|
||||
|
||||
// If no cached information, we try to get it from the persistent storage.
|
||||
return this._fxAccounts.getSignedInUser().then(
|
||||
user => {
|
||||
if (!user || !user.email) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
this._activeSession = user;
|
||||
// If we get a stored information of a not yet verified account,
|
||||
// we check this information with the server, update the stored
|
||||
// data if needed and finally return the account details.
|
||||
if (!user.verified && !Services.io.offline) {
|
||||
return this.verificationStatus(user);
|
||||
}
|
||||
|
||||
return Promise.resolve(this._user);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
queryAccount: function(aAccountId) {
|
||||
if (Services.io.offline) {
|
||||
return Promise.reject({
|
||||
error: ERROR_OFFLINE
|
||||
});
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
|
||||
if (!aAccountId) {
|
||||
return Promise.reject({
|
||||
error: ERROR_INVALID_ACCOUNTID
|
||||
});
|
||||
}
|
||||
|
||||
let client = this._createFxAccountsClient();
|
||||
return client.accountExists(aAccountId).then(
|
||||
result => {
|
||||
let error = this._getError(result);
|
||||
if (error) {
|
||||
return Promise.reject({
|
||||
error: error,
|
||||
details: result
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
registered: result
|
||||
});
|
||||
},
|
||||
reason => { this._serverError(reason); }
|
||||
);
|
||||
},
|
||||
|
||||
verificationStatus: function() {
|
||||
if (!this._activeSession || !this._activeSession.sessionToken) {
|
||||
return Promise.reject({
|
||||
error: ERROR_NO_TOKEN_SESSION
|
||||
});
|
||||
}
|
||||
|
||||
// There is no way to unverify an already verified account, so we just
|
||||
// return the account details of a verified account
|
||||
if (this._activeSession.verified) {
|
||||
return Promise.resolve(this._user);
|
||||
}
|
||||
|
||||
if (Services.io.offline) {
|
||||
return Promise.reject({
|
||||
error: ERROR_OFFLINE
|
||||
});
|
||||
}
|
||||
|
||||
let client = this._createFxAccountsClient();
|
||||
return client.recoveryEmailStatus(this._activeSession.sessionToken).then(
|
||||
data => {
|
||||
let error = this._getError(data);
|
||||
if (error) {
|
||||
return Promise.reject({
|
||||
error: error,
|
||||
details: data
|
||||
});
|
||||
}
|
||||
|
||||
// If the verification status is different from the one that we have
|
||||
// stored, we update it and return the session data. If not, we simply
|
||||
// return the session data.
|
||||
if (this._activeSession.verified != data.verified) {
|
||||
this._activeSession.verified = data.verified;
|
||||
return this._fxAccounts.setSignedInUser(this._activeSession).then(
|
||||
() => {
|
||||
return Promise.resolve(this._user);
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.resolve(this._user);
|
||||
},
|
||||
reason => { return this._serverError(reason); }
|
||||
);
|
||||
},
|
||||
|
||||
getAssertion: function(aAudience) {
|
||||
if (!aAudience) {
|
||||
return Promise.reject({
|
||||
error: ERROR_INVALID_AUDIENCE
|
||||
});
|
||||
}
|
||||
|
||||
if (Services.io.offline) {
|
||||
return Promise.reject({
|
||||
error: ERROR_OFFLINE
|
||||
});
|
||||
}
|
||||
|
||||
return this.getAccount().then(
|
||||
user => {
|
||||
if (user) {
|
||||
// We cannot get assertions for unverified accounts.
|
||||
if (user.verified) {
|
||||
return this._getAssertion(aAudience);
|
||||
}
|
||||
|
||||
return Promise.reject({
|
||||
error: ERROR_UNVERIFIED_ACCOUNT,
|
||||
details: {
|
||||
user: user
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If there is no currently signed in user, we trigger the signIn UI
|
||||
// flow.
|
||||
let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
|
||||
.createInstance(Ci.nsIFxAccountsUIGlue);
|
||||
return ui.signInFlow().then(
|
||||
result => {
|
||||
// Even if we get a successful result from the UI, the account will
|
||||
// most likely be unverified, so we cannot get an assertion.
|
||||
if (result && result.verified) {
|
||||
return this._getAssertion(aAudience);
|
||||
}
|
||||
return Promise.reject({
|
||||
error: ERROR_UNVERIFIED_ACCOUNT,
|
||||
details: {
|
||||
user: result
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {
|
||||
return Promise.reject({
|
||||
error: ERROR_UI_ERROR,
|
||||
details: error
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
@ -5,7 +5,13 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
TEST_DIRS += ['tests']
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'FxAccounts.jsm',
|
||||
'FxAccountsClient.jsm'
|
||||
'FxAccountsClient.jsm',
|
||||
'FxAccountsConsts.js'
|
||||
]
|
||||
|
||||
# For now, we will only be using the FxA manager in B2G.
|
||||
if CONFIG['MOZ_B2G']:
|
||||
EXTRA_JS_MODULES += ['FxAccountsManager.jsm']
|
||||
|
Loading…
Reference in New Issue
Block a user