Implement usage log, document usage metadata (#108)
This commit is contained in:
11
README.md
11
README.md
@@ -23,6 +23,7 @@ In order to use Browserpass you must also install a [companion native messaging
|
|||||||
- [Modal HTTP authentication](#modal-http-authentication)
|
- [Modal HTTP authentication](#modal-http-authentication)
|
||||||
- [Password store locations](password-store-locations)
|
- [Password store locations](password-store-locations)
|
||||||
- [Options](#options)
|
- [Options](#options)
|
||||||
|
- [Usage data](#usage-data)
|
||||||
- [Security](#security)
|
- [Security](#security)
|
||||||
- [Privacy](#privacy)
|
- [Privacy](#privacy)
|
||||||
- [Requested permissions](#requested-permissions)
|
- [Requested permissions](#requested-permissions)
|
||||||
@@ -215,6 +216,12 @@ Browserpass allows configuring certain settings in different places places using
|
|||||||
- Custom store locations - badge background color (aka `bgColor`)
|
- Custom store locations - badge background color (aka `bgColor`)
|
||||||
- Custom store locations - badge text color (aka `color`)
|
- Custom store locations - badge text color (aka `color`)
|
||||||
|
|
||||||
|
## Usage data
|
||||||
|
|
||||||
|
Browserpass keeps metadata of recently used credentials in local storage and Indexed DB of the background page. This is first and foremost internal data to make Browserpass function properly, used for example to implement the [Password matching and sorting](#password-matching-and-sorting) algorithm, but nevertheless you might find it useful to explore using your browser's devtools. For example, if you are considering to rotate all passwords that you used in the past month (e.g. if you just found out that you had a malicious app installed for several weeks), you can retrieve such list from Indexed DB quite easily (open an issue if you need help).
|
||||||
|
|
||||||
|
For details on how we treat your data and how to remove it, consult [Security](#security) and [Privacy](#privacy) sections.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
Browserpass aims to protect your passwords and computer from malicious or fraudulent websites.
|
Browserpass aims to protect your passwords and computer from malicious or fraudulent websites.
|
||||||
@@ -229,6 +236,10 @@ Browserpass aims to protect your passwords and computer from malicious or fraudu
|
|||||||
|
|
||||||
Browserpass does not send any telemetry data. All metadata that is collected in order for the extension to function correctly is stored _only_ in local storage, and never leaves your browser.
|
Browserpass does not send any telemetry data. All metadata that is collected in order for the extension to function correctly is stored _only_ in local storage, and never leaves your browser.
|
||||||
|
|
||||||
|
This data is not synchronized between your computers, and upon removing Browserpass extension all the data will be automatically purged by your browser.
|
||||||
|
|
||||||
|
In order to remove all metadata, use the "Clear usage data" button in the extension options page or do it using your browser's devtools.
|
||||||
|
|
||||||
## Requested permissions
|
## Requested permissions
|
||||||
|
|
||||||
Browserpass extension requests the following permissions:
|
Browserpass extension requests the following permissions:
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
require("chrome-extension-async");
|
require("chrome-extension-async");
|
||||||
var TldJS = require("tldjs");
|
var TldJS = require("tldjs");
|
||||||
var sha1 = require("sha1");
|
var sha1 = require("sha1");
|
||||||
|
var idb = require("idb");
|
||||||
|
|
||||||
// native application id
|
// native application id
|
||||||
var appID = "com.github.browserpass.native";
|
var appID = "com.github.browserpass.native";
|
||||||
@@ -163,7 +164,7 @@ function copyToClipboard(text) {
|
|||||||
* @param bool remove Remove this item from recent history
|
* @param bool remove Remove this item from recent history
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function saveRecent(settings, login, remove = false) {
|
async function saveRecent(settings, login, remove = false) {
|
||||||
var ignoreInterval = 60000; // 60 seconds - don't increment counter twice within this window
|
var ignoreInterval = 60000; // 60 seconds - don't increment counter twice within this window
|
||||||
|
|
||||||
// save store timestamp
|
// save store timestamp
|
||||||
@@ -183,6 +184,15 @@ function saveRecent(settings, login, remove = false) {
|
|||||||
if (!login.inCurrentDomain && login.recent.count === 1) {
|
if (!login.inCurrentDomain && login.recent.count === 1) {
|
||||||
updateMatchingPasswordsCount(settings.tab.id);
|
updateMatchingPasswordsCount(settings.tab.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save to usage log
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
const db = await idb.openDB("browserpass", DB_VERSION, {
|
||||||
|
upgrade(db) {
|
||||||
|
db.createObjectStore("log", { keyPath: "time" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await db.add("log", { time: Date.now(), host: settings.host, login: login.login });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -624,7 +634,7 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
case "copyPassword":
|
case "copyPassword":
|
||||||
try {
|
try {
|
||||||
copyToClipboard(message.login.fields.secret);
|
copyToClipboard(message.login.fields.secret);
|
||||||
saveRecent(settings, message.login);
|
await saveRecent(settings, message.login);
|
||||||
sendResponse({ status: "ok" });
|
sendResponse({ status: "ok" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendResponse({
|
sendResponse({
|
||||||
@@ -636,7 +646,7 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
case "copyUsername":
|
case "copyUsername":
|
||||||
try {
|
try {
|
||||||
copyToClipboard(message.login.fields.login);
|
copyToClipboard(message.login.fields.login);
|
||||||
saveRecent(settings, message.login);
|
await saveRecent(settings, message.login);
|
||||||
sendResponse({ status: "ok" });
|
sendResponse({ status: "ok" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendResponse({
|
sendResponse({
|
||||||
@@ -689,7 +699,7 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
|
|
||||||
// dispatch initial fill request
|
// dispatch initial fill request
|
||||||
var filledFields = await fillFields(settings, message.login, fields);
|
var filledFields = await fillFields(settings, message.login, fields);
|
||||||
saveRecent(settings, message.login);
|
await saveRecent(settings, message.login);
|
||||||
|
|
||||||
// no need to check filledFields, because fillFields() already throws an error if empty
|
// no need to check filledFields, because fillFields() already throws an error if empty
|
||||||
sendResponse({ status: "ok", filledFields: filledFields });
|
sendResponse({ status: "ok", filledFields: filledFields });
|
||||||
@@ -707,6 +717,17 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "clearUsageData":
|
||||||
|
try {
|
||||||
|
await clearUsageData();
|
||||||
|
sendResponse({ status: "ok" });
|
||||||
|
} catch (e) {
|
||||||
|
sendResponse({
|
||||||
|
status: "error",
|
||||||
|
message: e.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
sendResponse({
|
sendResponse({
|
||||||
status: "error",
|
status: "error",
|
||||||
@@ -859,6 +880,27 @@ async function receiveMessage(message, sender, sendResponse) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear usage data
|
||||||
|
*
|
||||||
|
* @since 3.0.10
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
async function clearUsageData() {
|
||||||
|
// clear local storage
|
||||||
|
localStorage.removeItem("foreignFills");
|
||||||
|
localStorage.removeItem("recent");
|
||||||
|
Object.keys(localStorage).forEach(key => {
|
||||||
|
if (key.startsWith("recent:")) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// clear Indexed DB
|
||||||
|
await idb.deleteDB("browserpass");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save settings if they are valid
|
* Save settings if they are valid
|
||||||
*
|
*
|
||||||
|
@@ -9,9 +9,10 @@ var m = require("mithril");
|
|||||||
*
|
*
|
||||||
* @param object settings Settings object
|
* @param object settings Settings object
|
||||||
* @param function saveSettings Function to save settings
|
* @param function saveSettings Function to save settings
|
||||||
|
* @param function clearUsageData Function to clear usage data
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function Interface(settings, saveSettings) {
|
function Interface(settings, saveSettings, clearUsageData) {
|
||||||
// public methods
|
// public methods
|
||||||
this.attach = attach;
|
this.attach = attach;
|
||||||
this.view = view;
|
this.view = view;
|
||||||
@@ -20,6 +21,7 @@ function Interface(settings, saveSettings) {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.saveSettings = saveSettings;
|
this.saveSettings = saveSettings;
|
||||||
this.saveEnabled = false;
|
this.saveEnabled = false;
|
||||||
|
this.clearUsageData = clearUsageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,6 +99,24 @@ function view(ctl, params) {
|
|||||||
"Save"
|
"Save"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
nodes.push(
|
||||||
|
m(
|
||||||
|
"button.clearUsageData",
|
||||||
|
{
|
||||||
|
onclick: async () => {
|
||||||
|
try {
|
||||||
|
await this.clearUsageData();
|
||||||
|
this.error = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
this.error = e;
|
||||||
|
}
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Clear usage data"
|
||||||
|
)
|
||||||
|
);
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -67,6 +67,20 @@ async function saveSettings(settings) {
|
|||||||
return await getSettings();
|
return await getSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear usage data
|
||||||
|
*
|
||||||
|
* @since 3.0.10
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
async function clearUsageData() {
|
||||||
|
var response = await chrome.runtime.sendMessage({ action: "clearUsageData" });
|
||||||
|
if (response.status != "ok") {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the main options logic
|
* Run the main options logic
|
||||||
*
|
*
|
||||||
@@ -76,7 +90,7 @@ async function saveSettings(settings) {
|
|||||||
*/
|
*/
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
var options = new Interface(await getSettings(), saveSettings);
|
var options = new Interface(await getSettings(), saveSettings, clearUsageData);
|
||||||
options.attach(document.body);
|
options.attach(document.body);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
|
@@ -90,8 +90,8 @@ h3:first-child {
|
|||||||
|
|
||||||
.add-store {
|
.add-store {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 12px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
color: rgb(17, 85, 204);
|
color: rgb(17, 85, 204);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
@@ -104,7 +104,7 @@ h3:first-child {
|
|||||||
|
|
||||||
.save {
|
.save {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
@-moz-document url-prefix() {
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chrome-extension-async": "^3.3.2",
|
"chrome-extension-async": "^3.3.2",
|
||||||
"fuzzysort": "^1.1.4",
|
"fuzzysort": "^1.1.4",
|
||||||
|
"idb": "^4.0.3",
|
||||||
"mithril": "^1.1.0",
|
"mithril": "^1.1.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"sha1": "^1.1.1",
|
"sha1": "^1.1.1",
|
||||||
|
@@ -696,6 +696,11 @@ https-browserify@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||||
|
|
||||||
|
idb@^4.0.3:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/idb/-/idb-4.0.3.tgz#b454a51ba0c45038a9a7be6898ca996aa74f9a37"
|
||||||
|
integrity sha512-moRlNNe0Gsvp4jAwz5cJ7scjyNTVA/cESKGCobULaljfaKZ970y8NDNCseHdMY+YxNXH58Z1V+7tTyf0GZyKqw==
|
||||||
|
|
||||||
ieee754@^1.1.4:
|
ieee754@^1.1.4:
|
||||||
version "1.1.12"
|
version "1.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||||
|
Reference in New Issue
Block a user