Bug 1267760 - Send push public key and auth secret when registering/updating a device. r=kitcambridge
MozReview-Commit-ID: BVwaijvSsLL
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -291,16 +291,20 @@ function copyObjectProperties(from, to,
if (desc.set) {
desc.set = desc.set.bind(thisArg);
}
Object.defineProperty(to, prop, desc);
}
}
+function urlsafeBase64Encode(key) {
+ return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false });
+}
+
/**
* The public API's constructor.
*/
this.FxAccounts = function (mockInternal) {
let internal = new FxAccountsInternal();
let external = {};
// Copy all public properties to the 'external' object.
@@ -350,17 +354,17 @@ function FxAccountsInternal() {
*/
FxAccountsInternal.prototype = {
// The timeout (in ms) we use to poll for a verified mail for the first 2 mins.
VERIFICATION_POLL_TIMEOUT_INITIAL: 15000, // 15 seconds
// And how often we poll after the first 2 mins.
VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 30000, // 30 seconds.
// The current version of the device registration, we use this to re-register
// devices after we update what we send on device registration.
- DEVICE_REGISTRATION_VERSION: 1,
+ DEVICE_REGISTRATION_VERSION: 2,
_fxAccountsClient: null,
// All significant initialization should be done in this initialize() method,
// as it's called after this object has been mocked for tests.
initialize() {
this.currentTimer = null;
this.currentAccountState = this.newAccountState();
@@ -1508,16 +1512,22 @@ FxAccountsInternal.prototype = {
return this.fxaPushService.registerPushEndpoint().then(subscription => {
const deviceName = this._getDeviceName();
let deviceOptions = {};
// if we were able to obtain a subscription
if (subscription && subscription.endpoint) {
deviceOptions.pushCallback = subscription.endpoint;
+ let publicKey = subscription.getKey('p256dh');
+ let authKey = subscription.getKey('auth');
+ if (publicKey && authKey) {
+ deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey);
+ deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
+ }
}
if (signedInUser.deviceId) {
log.debug("updating existing device details");
return this.fxAccountsClient.updateDevice(
signedInUser.sessionToken, signedInUser.deviceId, deviceName, deviceOptions);
}
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -362,16 +362,20 @@ this.FxAccountsClient.prototype = {
* @param name
* Device name
* @param type
* Device type (mobile|desktop)
* @param [options]
* Extra device options
* @param [options.pushCallback]
* `pushCallback` push endpoint callback
+ * @param [options.pushPublicKey]
+ * `pushPublicKey` push public key (URLSafe Base64 string)
+ * @param [options.pushAuthKey]
+ * `pushAuthKey` push auth secret (URLSafe Base64 string)
* @return Promise
* Resolves to an object:
* {
* id: Device identifier
* createdAt: Creation time (milliseconds since epoch)
* name: Name of device
* type: Type of device (mobile|desktop)
* }
@@ -380,16 +384,20 @@ this.FxAccountsClient.prototype = {
let path = "/account/device";
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
let body = { name, type };
if (options.pushCallback) {
body.pushCallback = options.pushCallback;
}
+ if (options.pushPublicKey && options.pushAuthKey) {
+ body.pushPublicKey = options.pushPublicKey;
+ body.pushAuthKey = options.pushAuthKey;
+ }
return this._request(path, "POST", creds, body);
},
/**
* Update the session or name for an existing device
*
* @method updateDevice
@@ -398,31 +406,39 @@ this.FxAccountsClient.prototype = {
* @param id
* Device identifier
* @param name
* Device name
* @param [options]
* Extra device options
* @param [options.pushCallback]
* `pushCallback` push endpoint callback
+ * @param [options.pushPublicKey]
+ * `pushPublicKey` push public key (URLSafe Base64 string)
+ * @param [options.pushAuthKey]
+ * `pushAuthKey` push auth secret (URLSafe Base64 string)
* @return Promise
* Resolves to an object:
* {
* id: Device identifier
* name: Device name
* }
*/
updateDevice(sessionTokenHex, id, name, options = {}) {
let path = "/account/device";
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
let body = { id, name };
if (options.pushCallback) {
body.pushCallback = options.pushCallback;
}
+ if (options.pushPublicKey && options.pushAuthKey) {
+ body.pushPublicKey = options.pushPublicKey;
+ body.pushAuthKey = options.pushAuthKey;
+ }
return this._request(path, "POST", creds, body);
},
/**
* Delete a device and its associated session token, signing the user
* out of the server.
*
--- a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
@@ -11,16 +11,19 @@ Cu.import("resource://gre/modules/FxAcco
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Log.jsm");
initTestLogging("Trace");
var log = Log.repository.getLogger("Services.FxAccounts.test");
log.level = Log.Level.Debug;
+const BOGUS_PUBLICKEY = "BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc";
+const BOGUS_AUTHKEY = "GSsIiaD2Mr83iPqwFNK4rw";
+
Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123");
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", "http://example.com/v1");
Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "http://accounts.example.com/");
@@ -101,17 +104,22 @@ function MockFxAccounts(device = {}) {
_getDeviceName() {
return device.name || "mock device name";
},
fxAccountsClient: new MockFxAccountsClient(device),
fxaPushService: {
registerPushEndpoint() {
return new Promise((resolve) => {
resolve({
- endpoint: "http://mochi.test:8888"
+ endpoint: "http://mochi.test:8888",
+ getKey: function(type) {
+ return ChromeUtils.base64URLDecode(
+ type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
+ { padding: "ignore" });
+ }
});
});
},
},
DEVICE_REGISTRATION_VERSION
});
}
@@ -157,16 +165,18 @@ add_task(function* test_updateDeviceRegi
do_check_eq(spy.updateDevice.count, 0);
do_check_eq(spy.getDeviceList.count, 0);
do_check_eq(spy.registerDevice.count, 1);
do_check_eq(spy.registerDevice.args[0].length, 4);
do_check_eq(spy.registerDevice.args[0][0], credentials.sessionToken);
do_check_eq(spy.registerDevice.args[0][1], deviceName);
do_check_eq(spy.registerDevice.args[0][2], "desktop");
do_check_eq(spy.registerDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+ do_check_eq(spy.registerDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+ do_check_eq(spy.registerDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
const state = fxa.internal.currentAccountState;
const data = yield state.getUserAccountData();
do_check_eq(data.deviceId, "newly-generated device id");
do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
});
@@ -208,16 +218,18 @@ add_task(function* test_updateDeviceRegi
do_check_eq(spy.registerDevice.count, 0);
do_check_eq(spy.getDeviceList.count, 0);
do_check_eq(spy.updateDevice.count, 1);
do_check_eq(spy.updateDevice.args[0].length, 4);
do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
do_check_eq(spy.updateDevice.args[0][2], deviceName);
do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+ do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+ do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
const state = fxa.internal.currentAccountState;
const data = yield state.getUserAccountData();
do_check_eq(data.deviceId, credentials.deviceId);
do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
});
@@ -265,16 +277,18 @@ add_task(function* test_updateDeviceRegi
do_check_eq(spy.getDeviceList.count, 0);
do_check_eq(spy.registerDevice.count, 0);
do_check_eq(spy.updateDevice.count, 1);
do_check_eq(spy.updateDevice.args[0].length, 4);
do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
do_check_eq(spy.updateDevice.args[0][2], deviceName);
do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+ do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+ do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
const state = fxa.internal.currentAccountState;
const data = yield state.getUserAccountData();
do_check_null(data.deviceId);
do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
});
@@ -328,16 +342,18 @@ add_task(function* test_updateDeviceRegi
do_check_eq(result, credentials.deviceId);
do_check_eq(spy.registerDevice.count, 0);
do_check_eq(spy.updateDevice.count, 1);
do_check_eq(spy.updateDevice.args[0].length, 4);
do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
do_check_eq(spy.updateDevice.args[0][2], deviceName);
do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
+ do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
+ do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
do_check_eq(spy.getDeviceList.count, 1);
do_check_eq(spy.getDeviceList.args[0].length, 1);
do_check_eq(spy.getDeviceList.args[0][0], credentials.sessionToken);
do_check_true(spy.getDeviceList.time >= spy.updateDevice.time);
const state = fxa.internal.currentAccountState;
const data = yield state.getUserAccountData();