diff options
Diffstat (limited to 'browser/extensions/screenshots/background/auth.js')
-rw-r--r-- | browser/extensions/screenshots/background/auth.js | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/browser/extensions/screenshots/background/auth.js b/browser/extensions/screenshots/background/auth.js new file mode 100644 index 0000000000..f6cfd0f9aa --- /dev/null +++ b/browser/extensions/screenshots/background/auth.js @@ -0,0 +1,224 @@ +/* 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/. */ + +/* globals log */ +/* globals main, makeUuid, deviceInfo, analytics, catcher, buildSettings, communication */ + +"use strict"; + +this.auth = (function() { + const exports = {}; + + let registrationInfo; + let initialized = false; + let authHeader = null; + let sentryPublicDSN = null; + let abTests = {}; + let accountId = null; + + const fetchStoredInfo = catcher.watchPromise( + browser.storage.local.get(["registrationInfo", "abTests"]).then((result) => { + if (result.abTests) { + abTests = result.abTests; + } + if (result.registrationInfo) { + registrationInfo = result.registrationInfo; + } + })); + + function getRegistrationInfo() { + if (!registrationInfo) { + registrationInfo = generateRegistrationInfo(); + log.info("Generating new device authentication ID", registrationInfo); + browser.storage.local.set({registrationInfo}); + } + return registrationInfo; + } + + exports.getDeviceId = function() { + return registrationInfo && registrationInfo.deviceId; + }; + + function generateRegistrationInfo() { + const info = { + deviceId: `anon${makeUuid()}`, + secret: makeUuid(), + registered: false, + }; + return info; + } + + function register() { + return new Promise((resolve, reject) => { + const registerUrl = main.getBackend() + "/api/register"; + // TODO: replace xhr with Fetch #2261 + const req = new XMLHttpRequest(); + req.open("POST", registerUrl); + req.setRequestHeader("content-type", "application/json"); + req.onload = catcher.watchFunction(() => { + if (req.status === 200) { + log.info("Registered login"); + initialized = true; + saveAuthInfo(JSON.parse(req.responseText)); + resolve(true); + analytics.sendEvent("registered"); + } else { + analytics.sendEvent("register-failed", `bad-response-${req.status}`); + log.warn("Error in response:", req.responseText); + const exc = new Error("Bad response: " + req.status); + exc.popupMessage = "LOGIN_ERROR"; + reject(exc); + } + }); + req.onerror = catcher.watchFunction(() => { + analytics.sendEvent("register-failed", "connection-error"); + const exc = new Error("Error contacting server"); + exc.popupMessage = "LOGIN_CONNECTION_ERROR"; + reject(exc); + }); + req.send(JSON.stringify({ + deviceId: registrationInfo.deviceId, + secret: registrationInfo.secret, + deviceInfo: JSON.stringify(deviceInfo()), + })); + }); + } + + function login(options) { + const { ownershipCheck, noRegister } = options || {}; + return new Promise((resolve, reject) => { + return fetchStoredInfo.then(() => { + const registrationInfo = getRegistrationInfo(); + const loginUrl = main.getBackend() + "/api/login"; + // TODO: replace xhr with Fetch #2261 + const req = new XMLHttpRequest(); + req.open("POST", loginUrl); + req.onload = catcher.watchFunction(() => { + if (req.status === 404) { + if (noRegister) { + resolve(false); + } else { + resolve(register()); + } + } else if (req.status >= 300) { + log.warn("Error in response:", req.responseText); + const exc = new Error("Could not log in: " + req.status); + exc.popupMessage = "LOGIN_ERROR"; + analytics.sendEvent("login-failed", `bad-response-${req.status}`); + reject(exc); + } else if (req.status === 0) { + const error = new Error("Could not log in, server unavailable"); + error.popupMessage = "LOGIN_CONNECTION_ERROR"; + analytics.sendEvent("login-failed", "connection-error"); + reject(error); + } else { + initialized = true; + const jsonResponse = JSON.parse(req.responseText); + log.info("Screenshots logged in"); + analytics.sendEvent("login"); + saveAuthInfo(jsonResponse); + if (ownershipCheck) { + resolve({isOwner: jsonResponse.isOwner}); + } else { + resolve(true); + } + } + }); + req.onerror = catcher.watchFunction(() => { + analytics.sendEvent("login-failed", "connection-error"); + const exc = new Error("Connection failed"); + exc.url = loginUrl; + exc.popupMessage = "CONNECTION_ERROR"; + reject(exc); + }); + req.setRequestHeader("content-type", "application/json"); + req.send(JSON.stringify({ + deviceId: registrationInfo.deviceId, + secret: registrationInfo.secret, + deviceInfo: JSON.stringify(deviceInfo()), + ownershipCheck, + })); + }); + }); + } + + function saveAuthInfo(responseJson) { + accountId = responseJson.accountId; + if (responseJson.sentryPublicDSN) { + sentryPublicDSN = responseJson.sentryPublicDSN; + } + if (responseJson.authHeader) { + authHeader = responseJson.authHeader; + if (!registrationInfo.registered) { + registrationInfo.registered = true; + catcher.watchPromise(browser.storage.local.set({registrationInfo})); + } + } + if (responseJson.abTests) { + abTests = responseJson.abTests; + catcher.watchPromise(browser.storage.local.set({abTests})); + } + } + + exports.maybeLogin = function() { + if (!registrationInfo) { + return Promise.resolve(); + } + + return exports.authHeaders(); + }; + + exports.authHeaders = function() { + let initPromise = Promise.resolve(); + if (!initialized) { + initPromise = login(); + } + return initPromise.then(() => { + if (authHeader) { + return {"x-screenshots-auth": authHeader}; + } + log.warn("No auth header available"); + return {}; + }); + }; + + exports.getSentryPublicDSN = function() { + return sentryPublicDSN || buildSettings.defaultSentryDsn; + }; + + exports.getAbTests = function() { + return abTests; + }; + + exports.isRegistered = function() { + return registrationInfo && registrationInfo.registered; + }; + + communication.register("getAuthInfo", (sender, ownershipCheck) => { + return fetchStoredInfo.then(() => { + // If a device id was never generated, report back accordingly. + if (!registrationInfo) { + return null; + } + + return exports.authHeaders().then((authHeaders) => { + let info = registrationInfo; + if (info.registered) { + return login({ownershipCheck}).then((result) => { + return { + isOwner: result && result.isOwner, + deviceId: registrationInfo.deviceId, + accountId, + authHeaders, + }; + }); + } + info = Object.assign({authHeaders}, info); + return info; + }); + }); +}); + + return exports; +})(); |