Skip to content
This repository has been archived by the owner on Feb 7, 2019. It is now read-only.

Commit

Permalink
0.1.7-alpha (#559)
Browse files Browse the repository at this point in the history
  • Loading branch information
linuxwolf authored Feb 22, 2018
1 parent 86b0113 commit 709ce91
Show file tree
Hide file tree
Showing 17 changed files with 1,703 additions and 541 deletions.
26 changes: 26 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Lockbox Release Notes

## 0.1.7-alpha

_Date: 2018-02-22_

### What's New

* To provide you with a closer brand experience with Firefox, the visual design and interaction of the account actions dropdown in the full editor view better aligns with Firefox's [Photon design](https://design.firefox.com/photon) ([#524](https://github.com/mozilla-lockbox/lockbox-extension/issues/524))

### What's Fixed

* If you use Lockbox in "Guest" mode (not linked to a Firefox Account), it can be unusable when Firefox is next restarted ([#542](https://github.com/mozilla-lockbox/lockbox-extension/issues/542))
* Via Greenkeeper:
* Updated `events` dependency ([#509](https://github.com/mozilla-lockbox/lockbox-extension/pull/509))
* Updated `babel-minify-webpack-plugin` dependency ([#525](https://github.com/mozilla-lockbox/lockbox-extension/pull/525))
* Updated `eslint-plugin-node` dependency ([#518](https://github.com/mozilla-lockbox/lockbox-extension/pull/518))
* Updated `eslint-plugin-mozilla` dependency ([#546](https://github.com/mozilla-lockbox/lockbox-extension/pull/546))

### Known Issues

* Profile information about you is only fetched and updated when you sign in; any changes made to your Firefox Accounts display name or avatar will not be displayed in Lockbox until you sign in again.
* Once you link a Firefox Account to Lockbox, you cannot unlink it from that account.
* Once you link a Firefox Account to Lockbox, signing in with a different account can render Lockbox unusable until you quit and restart Firefox.
* Once you link a Firefox Account to Lockbox, resetting your Firefox Account password through "forgot your password" will render all your logins inaccessible; the only recourse is to reset Lockbox and start over.
* Firefox's default prompt to save logins is only disabled on new installs of this extension; updating Lockbox will not change your current Firefox preferences.


## 0.1.6-alpha

_Date: 2018-02-08_
Expand Down
1,800 changes: 1,373 additions & 427 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"title": "Lockbox",
"name": "lockbox",
"id": "[email protected]",
"version": "0.1.6-alpha",
"version": "0.1.7-alpha",
"main": "dist/bootstrap.js",
"description": "The simple way to store, retrieve and manage website login info",
"author": "Lockbox Team <[email protected]>",
Expand Down Expand Up @@ -53,6 +53,11 @@
"src"
]
},
"greenkeeper": {
"ignore": [
"eslint-plugin-mozilla"
]
},
"dependencies": {
"copy-to-clipboard": "^3.0.8",
"fluent": "^0.4.2",
Expand All @@ -75,7 +80,7 @@
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-minify-webpack-plugin": "^0.2.0",
"babel-minify-webpack-plugin": "^0.3.0",
"babel-plugin-istanbul": "^4.1.5",
"babel-plugin-rewire": "^1.1.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
Expand All @@ -97,14 +102,14 @@
"eslint": "^4.15.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-mozilla": "^0.5.0",
"eslint-plugin-mozilla": "0.6.0",
"eslint-plugin-no-unsanitized": "^2.0.2",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-node": "^6.0.0",
"eslint-plugin-only-warn": "^1.0.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.5.1",
"eslint-plugin-standard": "^3.0.1",
"events": "^1.1.1",
"events": "^2.0.0",
"extract-text-webpack-plugin": "^3.0.2",
"fetch-mock": "^6.0.0-beta.9",
"html-minifier": "^3.5.8",
Expand Down
194 changes: 139 additions & 55 deletions src/webextension/background/accounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ export const APP_KEY_NAME = "https://identity.mozilla.com/apps/lockbox";
export const DEFAULT_AVATAR_PATH = "icons/default-avatar.svg";

export class Account {
constructor({config = DEFAULT_CONFIG, info}) {
constructor({config = DEFAULT_CONFIG, info, storage}) {
// TODO: verify configuration (when there is one)
this.config = config;
this.info = info || undefined;
this.storage = storage;
}

toJSON() {
Expand All @@ -98,6 +99,14 @@ export class Account {
info,
};
}
async save() {
if (this.storage) {
const account = this.toJSON();
await this.storage.set({ account });
}

return this;
}

get mode() {
const info = this.info;
Expand Down Expand Up @@ -126,7 +135,7 @@ export class Account {
let cfg = configs[this.config];

const props = {};
let url, request;
let url;

// request authorization
cfg = {
Expand All @@ -151,52 +160,14 @@ export class Account {
} else {
tokenParams.client_secret = cfg.client_secret;
}
url = cfg.token_endpoint;
request = {
method: "post",
headers: {
"content-type": "application/json",
},
cache: "no-cache",
body: JSON.stringify(tokenParams),
};
const oauthInfo = await fetchFromEndPoint("token", url, request);
// console.log(`oauth info == ${JSON.stringify(oauthInfo)}`);
await this.updateAccessToken(tokenParams, props.appKey);

const keys = new Map();
if (oauthInfo.keys_jwe) {
let bundle = await jose.JWE.createDecrypt(props.appKey).decrypt(oauthInfo.keys_jwe);
bundle = JSON.parse(new TextDecoder().decode(bundle.payload));
const pending = Object.keys(bundle).map(async (name) => {
let key = bundle[name];
key = await jose.JWK.asKey(key);
keys.set(name, key);
});
await Promise.all(pending);
}
// update user info
await this.updateUserInfo();

// retrieve user info
url = cfg.userinfo_endpoint;
request = {
method: "get",
headers: {
authorization: `Bearer ${oauthInfo.access_token}`,
},
cache: "no-cache",
};
const userInfo = await fetchFromEndPoint("userinfo", url, request);
// retain it all
await this.save();

this.info = {
uid: userInfo.uid,
email: userInfo.email,
displayName: userInfo.displayName,
avatar: userInfo.avatar,
access_token: oauthInfo.access_token,
expires_at: (Date.now() / 1000) + oauthInfo.expires_in,
id_token: oauthInfo.id_token,
refresh_token: oauthInfo.refresh_token,
keys,
};
return this;
}

Expand All @@ -216,6 +187,8 @@ export class Account {
}
// XXXX: something server side?

await this.save();

return this;
}

Expand All @@ -228,6 +201,119 @@ export class Account {
avatar: this.avatar,
};
}

async token() {
// always return null if user is GUEST
if (this.mode === GUEST) {
// XXXX: use DataStoreError
throw new Error("AUTH: requires FxA");
}

// check if token present / unexpired / valid
let info = await this.updateUserInfo();
if (!info || !info.access_token) {
// refresh
await this.updateAccessToken();
info = await this.updateUserInfo();
}
await this.save();

if (!info || !info.access_token) {
// XXXX: use DataStoreError
throw new Error("AUTH: no access token");
}

return info.access_token;
}


async updateAccessToken(params, appKey) {
const cfg = configs[this.config];
let info = this.info || {};

if (!params) {
// assume "refresh_token" exchange
if (!info.refresh_token) {
// XXXX: use DataStoreError
throw new Error("AUTH: no refresh token");
}

params = {
grant_type: "refresh_token",
refresh_token: info.refresh_token,
client_id: cfg.client_id,
};
}

const request = {
method: "post",
headers: {
"content-type": "application/json",
},
cache: "no-cache",
body: JSON.stringify(params),
};
const oauthInfo = await fetchFromEndPoint("token", cfg.token_endpoint, request);
let keys = info.keys || new Map();
if (oauthInfo.keys_jwe && appKey) {
// forget previous keys before decrypting new bundle
keys.clear();

let bundle = await jose.JWE.createDecrypt(appKey).decrypt(oauthInfo.keys_jwe);
bundle = JSON.parse(new TextDecoder().decode(bundle.payload));
const pending = Object.keys(bundle).map(async (name) => {
let key = bundle[name];
key = await jose.JWK.asKey(key);
keys.set(name, key);
});
await Promise.all(pending);
}

this.info = info = {
...info,
access_token: oauthInfo.access_token,
expires_at: Math.floor(Date.now() / 1000) + oauthInfo.expires_in,
id_token: oauthInfo.id_token,
refresh_token: oauthInfo.refresh_token,
keys,
};

return info;
}

async updateUserInfo() {
let info = this.info || {};
const { access_token, expires_at } = info;
if (!access_token || !expires_at || Date.now() > (expires_at * 1000)) {
// need a new access token
return null;
}

const url = configs[this.config].userinfo_endpoint;
const request = {
method: "get",
headers: {
"authorization": `Bearer ${access_token}`,
},
cache: "no-cache",
};
try {
const userInfo = await fetchFromEndPoint("userinfo", url, request);
// since it's present ... update user info
this.info = info = {
...info,
uid: userInfo.uid,
email: userInfo.email,
displayName: userInfo.displayName,
avatar: userInfo.avatar,
};
} catch (err) {
return null;
}

return info;
}

}


Expand All @@ -239,23 +325,21 @@ export default function getAccount() {
return account;
}

export function setAccount(config, info) {
account = config ? new Account({ config, info }) : undefined;
}

export async function loadAccount(storage) {
const stored = await storage.get("account");
if (stored && stored.account) {
account = new Account(stored.account);
account = new Account({
...stored.account,
storage,
});
}
return getAccount();
}

export async function saveAccount(storage) {
const account = getAccount().toJSON();
await storage.set({ account });
}

export function setAccount(config, info) {
account = config ? new Account({config, info}) : undefined;
}

export async function openAccount(storage) {
let account;

Expand Down
4 changes: 2 additions & 2 deletions src/webextension/background/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* 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/. */

import openDataStore from "./datastore";
import openDataStore, { DEFAULT_APP_KEY } from "./datastore";
import { openAccount, GUEST } from "./accounts";
import initializeMessagePorts from "./message-ports";
import updateBrowserAction from "./browser-action";

openAccount(browser.storage.local).then(async (account) => {
let datastore = await openDataStore({ salt: account.uid });
if (datastore.initialized && account.mode === GUEST) {
await datastore.unlock();
await datastore.unlock(DEFAULT_APP_KEY);
}

initializeMessagePorts();
Expand Down
4 changes: 0 additions & 4 deletions src/webextension/background/message-ports.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ export default function initializeMessagePorts() {
await datastore.initialize({
appKey: DEFAULT_APP_KEY,
});
// TODO: be more implicit on saving account info
await accounts.saveAccount(browser.storage.local);
await updateBrowserAction({datastore});
if (message.view) {
openView(message.view);
Expand All @@ -80,8 +78,6 @@ export default function initializeMessagePorts() {
await datastore.unlock(DEFAULT_APP_KEY);
}
await datastore.initialize({ appKey, salt, rebase: true });
// FIXME: be more implicit on saving account info
await accounts.saveAccount(browser.storage.local);
await updateBrowserAction({ account, datastore });
telemetry.recordEvent("fxaUpgrade", "accounts");
} catch (err) {
Expand Down
4 changes: 2 additions & 2 deletions src/webextension/icons/account.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/webextension/icons/options.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/webextension/icons/signout.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 709ce91

Please sign in to comment.