Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Automatically add passwords to password store #143

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/background-interject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module.exports = { extractFormData: extractFormData };

const USERNAME_KEYS = ["username", "user", "login"];
const EMAIL_KEYS = ["email", "e-mail"];
const PASSWORD_KEYS = ["password", "passwd", "pass", "secret"];

function findFormDataEntries(formData, keys) {
let found = [];

for (key in formData) {
// strip keys of the form user[x]
let strippedKey = key;
let match = /\[.*\]/.exec(key);
if (match) {
strippedKey = key.substring(match.index + 1, match.index + match[0].length - 1);
}

if (keys.includes(strippedKey)) {
let entry = formData[key];
if (entry != "") found.push(entry[0]);
}
}

return found;
}

function checkPasswords(passwords) {
if (passwords.length < 2) return true;

let compare = passwords[0];

for (let password of passwords) {
if (password != compare) return false;
}

return true;
}

function extractFormData(formData) {
let passwords = findFormDataEntries(formData, PASSWORD_KEYS);
let usernames = findFormDataEntries(formData, USERNAME_KEYS);
let emails = findFormDataEntries(formData, EMAIL_KEYS);

if (!checkPasswords(passwords)) return null;

if (passwords.length === 0) return null;

if (usernames.length === 0 && emails.length === 0) return null;

let credentials = { password: passwords[0] };
if (usernames.length > 0) {
credentials.login = usernames[0];

if (emails.length > 0) {
credentials.email = emails[0];
}
} else {
credentials.login = emails[0];
credentials.email = "";
}

return credentials;
}
85 changes: 84 additions & 1 deletion src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require("chrome-extension-async");
var TldJS = require("tldjs");
var sha1 = require("sha1");
var idb = require("idb");
var Interject = require("./background-interject.js");

// native application id
var appID = "com.github.browserpass.native";
Expand Down Expand Up @@ -51,8 +52,53 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {

chrome.runtime.onInstalled.addListener(onExtensionInstalled);

let recentCredentials = {};
let dismissedCredentials = new Set();

chrome.webRequest.onBeforeRequest.addListener(
async function(details) {
if (details.method == "POST") {
if (!details.requestBody) {
return;
}
let formData = details.requestBody.formData;
if (!formData) {
return;
}

let credentials = Interject.extractFormData(formData);
if (!credentials) return;

let currentDomain = new URL(details.url).hostname;
credentials.domain = currentDomain;

let path = currentDomain + "/" + credentials.login + ".gpg";
credentials.path = path;

if (await fileExists(path)) return;

let id = hashCredentials(credentials);
if (recentCredentials.hasOwnProperty(id)) return;
if (dismissedCredentials.has(id)) return;

credentials.path = path;
recentCredentials[id] = credentials;
}
},
{ urls: ["<all_urls>"] },
["requestBody"]
);

//----------------------------------- Function definitions ----------------------------------//

async function fileExists(path) {
let settings = await getFullSettings();
let response = await hostAction(settings, "exists", {
file: path
});

return response.data.exists;
}
/**
* Get the deepest available domain component of a path
*
Expand Down Expand Up @@ -600,6 +646,13 @@ async function handleMessage(settings, message, sendResponse) {

// route action
switch (message.action) {
case "listCredentials":
sendResponse({
status: "ok",
credentials: Object.values(recentCredentials)
});
// recentCredentials = [];
break;
case "getSettings":
sendResponse({
status: "ok",
Expand Down Expand Up @@ -705,7 +758,7 @@ async function handleMessage(settings, message, sendResponse) {
sendResponse({ status: "ok", filledFields: filledFields });
} catch (e) {
try {
sendResponse({
await sendResponse({
status: "error",
message: e.toString()
});
Expand All @@ -728,6 +781,32 @@ async function handleMessage(settings, message, sendResponse) {
});
}
break;
case "create":
try {
response = await hostAction(settings, "create", {
storeID: message.storeID,
credentials: message.credentials,
file: message.credentials.path
});
if (response.status != "ok") {
throw new Error(JSON.stringify(response));
}
let id = hashCredentials(message.credentials);
delete recentCredentials[id];
sendResponse({ status: "ok" });
} catch (e) {
sendResponse({
status: "error",
message: "Unable to create password file" + e.toString()
});
}
break;
case "dismiss":
let id = hashCredentials(message.credentials);
dismissedCredentials.add(id);
delete recentCredentials[id];
sendResponse({ status: "ok" });
break;
default:
sendResponse({
status: "error",
Expand Down Expand Up @@ -880,6 +959,10 @@ async function receiveMessage(message, sender, sendResponse) {
}
}

function hashCredentials(credentials) {
return credentials.login + credentials.email + credentials.password;
}

/**
* Clear usage data
*
Expand Down
1 change: 0 additions & 1 deletion src/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@
if (!request.allowNoSecret && !find(PASSWORD_FIELDS, loginForm)) {
return result;
}

// ensure the origin is the same, or ask the user for permissions to continue
if (window.location.origin !== request.origin) {
if (!request.allowForeign || request.foreignFills[window.location.origin] === false) {
Expand Down
98 changes: 98 additions & 0 deletions src/popup/add-interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
module.exports = AddInterface;
var m = require("mithril");

function AddInterface(credentials, settings) {
// public methods
this.attach = attach;
this.view = view;

// fields
this.settings = settings;
this.credentials = credentials;
this.dismissCredential = dismissCredential;
}

function attach(element) {
m.mount(element, this);
}

function view(ctl, params) {
let credentials = this.credentials.length > 0 ? this.credentials[0] : undefined;

var nodes = [];

nodes.push(m("div.label.title", "Save credentials to store?"));

nodes.push(m("div.label", "Store:"));
let select = m(
"select",
Object.values(this.settings.stores).map(store =>
m("option", { storeID: store.id }, store.name)
)
);
nodes.push(select);
nodes.push(m("div.label", "Path:"));
nodes.push(
m("input.credential", {
type: "text",
value: credentials ? credentials.path : ""
})
);

nodes.push(m("div.label", "Username:"));
nodes.push(
m("input.credential", {
type: "text",
value: credentials ? credentials.login : ""
})
);

nodes.push(m("div.label", "Password:"));
nodes.push(
m("input.credential", {
type: "password",
value: credentials ? credentials.password : ""
})
);

if (credentials.email) {
nodes.push(m("div.label", "E-Mail:"));
nodes.push(
m("input.credential", {
type: "text",
value: credentials ? credentials.email : ""
})
);
}

nodes.push(
m("div.buttons", [
m(
"button.storeButton",
{
onclick: async function(e) {
let storeID = select.dom.selectedOptions[0].getAttribute("storeID");
await credentials.doAction("create", storeID);
}
},
"Yes"
),
m(
"button.storeButton",
{
onclick: async function(e) {
await credentials.doAction("dismiss");
}
},
"No"
)
])
);

return nodes;
}

function dismissCredential() {
this.credentials.shift();
return this.credentials.length;
}
40 changes: 40 additions & 0 deletions src/popup/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require("chrome-extension-async");
var TldJS = require("tldjs");
var sha1 = require("sha1");
var Interface = require("./interface");
var AddInterface = require("./add-interface");

run();

Expand Down Expand Up @@ -81,6 +82,24 @@ async function run() {
throw new Error("Unable to retrieve current tab information");
}

response = await chrome.runtime.sendMessage({ action: "listCredentials" });
if (response.status != "ok") {
throw new Error(response.message);
}

if (response.credentials.length > 0) {
var popup = new AddInterface(response.credentials, settings);
for (let credential of response.credentials) {
credential.doAction = withCredential.bind({
settings: settings,
credentials: credential,
interface: popup
});
}
popup.attach(document.body);
return;
}

// get list of logins
response = await chrome.runtime.sendMessage({ action: "listFiles" });
if (response.status != "ok") {
Expand Down Expand Up @@ -172,3 +191,24 @@ async function withLogin(action) {
handleError(e);
}
}

async function withCredential(action, storeID) {
const credentials = JSON.parse(JSON.stringify(this.credentials));
let response = await chrome.runtime.sendMessage({
action: action,
credentials: credentials,
storeID: storeID
});

switch (action) {
case "dismiss":
window.close();
break;
case "create":
if (response.status != "ok") handleError(Error(response.message));

let credentialsLeft = this.interface.dismissCredential();
if (credentialsLeft === 0) window.close();
break;
}
}
Loading