зеркало из https://github.com/mozilla/gecko-dev.git
299 строки
7.8 KiB
JavaScript
299 строки
7.8 KiB
JavaScript
/* 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/. */
|
|
|
|
/**
|
|
* Firefox Accounts OAuth Grant Client allows clients to obtain
|
|
* an OAuth token from a BrowserID assertion. Only certain client
|
|
* IDs support this privilage.
|
|
*/
|
|
|
|
var EXPORTED_SYMBOLS = [
|
|
"FxAccountsOAuthGrantClient",
|
|
"FxAccountsOAuthGrantClientError",
|
|
];
|
|
|
|
const {
|
|
ERRNO_NETWORK,
|
|
ERRNO_PARSE,
|
|
ERRNO_UNKNOWN_ERROR,
|
|
ERROR_CODE_METHOD_NOT_ALLOWED,
|
|
ERROR_MSG_METHOD_NOT_ALLOWED,
|
|
ERROR_NETWORK,
|
|
ERROR_PARSE,
|
|
ERROR_UNKNOWN,
|
|
OAUTH_SERVER_ERRNO_OFFSET,
|
|
log,
|
|
} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
|
const { RESTRequest } = ChromeUtils.import(
|
|
"resource://services-common/rest.js"
|
|
);
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
|
|
|
const AUTH_ENDPOINT = "/authorization";
|
|
const DESTROY_ENDPOINT = "/destroy";
|
|
// This is the same pref that's used by FxAccounts.jsm.
|
|
const ALLOW_HTTP_PREF = "identity.fxaccounts.allowHttp";
|
|
|
|
/**
|
|
* Create a new FxAccountsOAuthClient for browser some service.
|
|
*
|
|
* @param {Object} options Options
|
|
* @param {Object} options.parameters
|
|
* @param {String} options.parameters.client_id
|
|
* OAuth id returned from client registration
|
|
* @param {String} options.parameters.serverURL
|
|
* The FxA OAuth server URL
|
|
* @param [authorizationEndpoint] {String}
|
|
* Optional authorization endpoint for the OAuth server
|
|
* @constructor
|
|
*/
|
|
var FxAccountsOAuthGrantClient = function(options) {
|
|
this._validateOptions(options);
|
|
this.parameters = options;
|
|
|
|
try {
|
|
this.serverURL = new URL(this.parameters.serverURL);
|
|
} catch (e) {
|
|
throw new Error("Invalid 'serverURL'");
|
|
}
|
|
|
|
let forceHTTPS = !Services.prefs.getBoolPref(ALLOW_HTTP_PREF, false);
|
|
if (forceHTTPS && this.serverURL.protocol != "https:") {
|
|
throw new Error("'serverURL' must be HTTPS");
|
|
}
|
|
|
|
log.debug("FxAccountsOAuthGrantClient Initialized");
|
|
};
|
|
|
|
this.FxAccountsOAuthGrantClient.prototype = {
|
|
/**
|
|
* Retrieves an OAuth access token for the signed in user
|
|
*
|
|
* @param {Object} assertion BrowserID assertion
|
|
* @param {String} scope OAuth scope
|
|
* @return Promise
|
|
* Resolves: {Object} Object with access_token property
|
|
*/
|
|
getTokenFromAssertion(assertion, scope) {
|
|
if (!assertion) {
|
|
throw new Error("Missing 'assertion' parameter");
|
|
}
|
|
if (!scope) {
|
|
throw new Error("Missing 'scope' parameter");
|
|
}
|
|
let params = {
|
|
scope,
|
|
client_id: this.parameters.client_id,
|
|
assertion,
|
|
response_type: "token",
|
|
};
|
|
|
|
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.
|
|
*
|
|
* @param {String} token The previously fetched token
|
|
* @return Promise
|
|
* Resolves: {Object} with the server response, which is typically
|
|
* ignored.
|
|
*/
|
|
destroyToken(token) {
|
|
if (!token) {
|
|
throw new Error("Missing 'token' parameter");
|
|
}
|
|
let params = {
|
|
token,
|
|
};
|
|
|
|
return this._createRequest(DESTROY_ENDPOINT, "POST", params);
|
|
},
|
|
|
|
/**
|
|
* Validates the required FxA OAuth parameters
|
|
*
|
|
* @param options {Object}
|
|
* OAuth client options
|
|
* @private
|
|
*/
|
|
_validateOptions(options) {
|
|
if (!options) {
|
|
throw new Error("Missing configuration options");
|
|
}
|
|
|
|
["serverURL", "client_id"].forEach(option => {
|
|
if (!options[option]) {
|
|
throw new Error("Missing '" + option + "' parameter");
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Interface for making remote requests.
|
|
*/
|
|
_Request: RESTRequest,
|
|
|
|
/**
|
|
* Remote request helper
|
|
*
|
|
* @param {String} path
|
|
* Profile server path, i.e "/profile".
|
|
* @param {String} [method]
|
|
* Type of request, i.e "GET".
|
|
* @return Promise
|
|
* Resolves: {Object} Successful response from the Profile server.
|
|
* Rejects: {FxAccountsOAuthGrantClientError} Profile client error.
|
|
* @private
|
|
*/
|
|
async _createRequest(path, method = "POST", params) {
|
|
let profileDataUrl = this.serverURL + path;
|
|
let request = new this._Request(profileDataUrl);
|
|
method = method.toUpperCase();
|
|
|
|
request.setHeader("Accept", "application/json");
|
|
request.setHeader("Content-Type", "application/json");
|
|
|
|
if (method != "POST") {
|
|
throw new FxAccountsOAuthGrantClientError({
|
|
error: ERROR_NETWORK,
|
|
errno: ERRNO_NETWORK,
|
|
code: ERROR_CODE_METHOD_NOT_ALLOWED,
|
|
message: ERROR_MSG_METHOD_NOT_ALLOWED,
|
|
});
|
|
}
|
|
|
|
try {
|
|
await request.post(params);
|
|
} catch (error) {
|
|
throw new FxAccountsOAuthGrantClientError({
|
|
error: ERROR_NETWORK,
|
|
errno: ERRNO_NETWORK,
|
|
message: error.toString(),
|
|
});
|
|
}
|
|
|
|
let body = null;
|
|
try {
|
|
body = JSON.parse(request.response.body);
|
|
} catch (e) {
|
|
throw new FxAccountsOAuthGrantClientError({
|
|
error: ERROR_PARSE,
|
|
errno: ERRNO_PARSE,
|
|
code: request.response.status,
|
|
message: request.response.body,
|
|
});
|
|
}
|
|
|
|
if (request.response.success) {
|
|
return body;
|
|
}
|
|
|
|
if (typeof body.errno === "number") {
|
|
// Offset oauth server errnos to avoid conflict with other FxA server errnos
|
|
body.errno += OAUTH_SERVER_ERRNO_OFFSET;
|
|
} else if (body.errno) {
|
|
body.errno = ERRNO_UNKNOWN_ERROR;
|
|
}
|
|
throw new FxAccountsOAuthGrantClientError(body);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Normalized profile client errors
|
|
* @param {Object} [details]
|
|
* Error details object
|
|
* @param {number} [details.code]
|
|
* Error code
|
|
* @param {number} [details.errno]
|
|
* Error number
|
|
* @param {String} [details.error]
|
|
* Error description
|
|
* @param {String|null} [details.message]
|
|
* Error message
|
|
* @constructor
|
|
*/
|
|
var FxAccountsOAuthGrantClientError = function(details) {
|
|
details = details || {};
|
|
|
|
this.name = "FxAccountsOAuthGrantClientError";
|
|
this.code = details.code || null;
|
|
this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
|
|
this.error = details.error || ERROR_UNKNOWN;
|
|
this.message = details.message || null;
|
|
};
|
|
|
|
/**
|
|
* Returns error object properties
|
|
*
|
|
* @returns {{name: *, code: *, errno: *, error: *, message: *}}
|
|
* @private
|
|
*/
|
|
FxAccountsOAuthGrantClientError.prototype._toStringFields = function() {
|
|
return {
|
|
name: this.name,
|
|
code: this.code,
|
|
errno: this.errno,
|
|
error: this.error,
|
|
message: this.message,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* String representation of a oauth grant client error
|
|
*
|
|
* @returns {String}
|
|
*/
|
|
FxAccountsOAuthGrantClientError.prototype.toString = function() {
|
|
return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
|
|
};
|