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 file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { Log } from "resource://gre/modules/Log.sys.mjs";
7 import { RESTRequest } from "resource://services-common/rest.sys.mjs";
8 import { CommonUtils } from "resource://services-common/utils.sys.mjs";
9 import { Credentials } from "resource://gre/modules/Credentials.sys.mjs";
13 ChromeUtils.defineESModuleGetters(lazy, {
14 CryptoUtils: "resource://services-crypto/utils.sys.mjs",
18 * Single-use HAWK-authenticated HTTP requests to RESTish resources.
21 * (String) URI for the RESTRequest constructor
24 * (Object) Optional credentials for computing HAWK authentication
28 * (Object) Optional object to be converted to JSON payload
31 * (Object) Optional extra params for HAWK header computation.
32 * Valid properties are:
34 * now: <current time in milliseconds>,
35 * localtimeOffsetMsec: <local clock offset vs server>,
36 * headers: <An object with header/value pairs to be sent
37 * as headers on the request>
39 * extra.localtimeOffsetMsec is the value in milliseconds that must be added to
40 * the local clock to make it agree with the server's clock. For instance, if
41 * the local clock is two minutes ahead of the server, the time offset in
42 * milliseconds will be -120000.
45 export var HAWKAuthenticatedRESTRequest = function HawkAuthenticatedRESTRequest(
50 RESTRequest.call(this, uri);
52 this.credentials = credentials;
53 this.now = extra.now || Date.now();
54 this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
56 "local time, offset: " + this.now + ", " + this.localtimeOffsetMsec
58 this.extraHeaders = extra.headers || {};
61 this._intl = getIntl();
64 HAWKAuthenticatedRESTRequest.prototype = {
65 async dispatch(method, data) {
66 let contentType = "text/plain";
67 if (method == "POST" || method == "PUT" || method == "PATCH") {
68 contentType = "application/json";
70 if (this.credentials) {
73 localtimeOffsetMsec: this.localtimeOffsetMsec,
74 credentials: this.credentials,
75 payload: (data && JSON.stringify(data)) || "",
78 let header = await lazy.CryptoUtils.computeHAWK(
83 this.setHeader("Authorization", header.field);
86 for (let header in this.extraHeaders) {
87 this.setHeader(header, this.extraHeaders[header]);
90 this.setHeader("Content-Type", contentType);
92 this.setHeader("Accept-Language", this._intl.accept_languages);
94 return super.dispatch(method, data);
98 Object.setPrototypeOf(
99 HAWKAuthenticatedRESTRequest.prototype,
100 RESTRequest.prototype
104 * Generic function to derive Hawk credentials.
106 * Hawk credentials are derived using shared secrets, which depend on the token
110 * The current session token encoded in hex
112 * A context for the credentials. A protocol version will be prepended
113 * to the context, see Credentials.keyWord for more information.
115 * The size in bytes of the expected derived buffer,
116 * defaults to 3 * 32.
117 * @return credentials
120 * id: the Hawk id (from the first 32 bytes derived)
121 * key: the Hawk key (from bytes 32 to 64)
122 * extra: size - 64 extra bytes (if size > 64)
125 export async function deriveHawkCredentials(tokenHex, context, size = 96) {
126 let token = CommonUtils.hexToBytes(tokenHex);
127 let out = await lazy.CryptoUtils.hkdfLegacy(
130 Credentials.keyWord(context),
135 key: out.slice(32, 64),
136 id: CommonUtils.bytesAsHex(out.slice(0, 32)),
139 result.extra = out.slice(64);
145 // With hawk request, we send the user's accepted-languages with each request.
146 // To keep the number of times we read this pref at a minimum, maintain the
147 // preference in a stateful object that notices and updates itself when the
150 // We won't actually query the pref until the first time we need it
152 this._everRead = false;
158 Services.prefs.addObserver("intl.accept_languages", this);
162 Services.prefs.removeObserver("intl.accept_languages", this);
165 observe(subject, topic, data) {
170 this._everRead = true;
172 this._accepted = Services.prefs.getComplexValue(
173 "intl.accept_languages",
174 Ci.nsIPrefLocalizedString
177 let log = Log.repository.getLogger("Services.Common.RESTRequest");
178 log.error("Error reading intl.accept_languages pref", err);
182 get accept_languages() {
183 if (!this._everRead) {
186 return this._accepted;
190 // Singleton getter for Intl, creating an instance only when we first need it.