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

Commit

Permalink
Add button to launch URLs & support basic auth (#224, fixes #214, fixes
Browse files Browse the repository at this point in the history
#103)

 - Host app will now return a "url" field
 - Browser plugin has a "launch URL" button for each entry
 - Browser plugin has a new hotkey (g) to launch the URL
 - Supply basic auth credentials if necessary when launching via the extension

Note that basic auth will only work in Firefox >= 54. Older versions don't have the required API to do this.
  • Loading branch information
erayd authored and maximbaz committed Mar 21, 2018
1 parent 31a3cfc commit 604f560
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 13 deletions.
12 changes: 10 additions & 2 deletions browserpass.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Login struct {
Password string `json:"p"`
OTP string `json:"digits"`
OTPLabel string `json:"label"`
URL string `json:"url"`
}

var endianness = binary.LittleEndian
Expand Down Expand Up @@ -212,14 +213,21 @@ func parseLogin(r io.Reader) (*Login, error) {
login.Password = scanner.Text()

// Keep reading file for string in "login:", "username:" or "user:" format (case insensitive).
re := regexp.MustCompile("(?i)^(login|username|user):")
userPattern := regexp.MustCompile("(?i)^(login|username|user):")
urlPattern := regexp.MustCompile("(?i)^(url|link|website|web|site):")
for scanner.Scan() {
line := scanner.Text()
parseTotp(line, login)
replaced := re.ReplaceAllString(line, "")
replaced := userPattern.ReplaceAllString(line, "")
if len(replaced) != len(line) {
login.Username = strings.TrimSpace(replaced)
}
if (login.URL == "") {
replaced = urlPattern.ReplaceAllString(line, "")
if len(replaced) != len(line) {
login.URL = strings.TrimSpace(replaced)
}
}
}

// if an unlabelled OTP is present, label it with the username
Expand Down
38 changes: 38 additions & 0 deletions chrome/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,44 @@ function onMessage(request, sender, sendResponse) {
localStorage.getItem("use_fuzzy_search") != "false";
sendResponse({ use_fuzzy_search: use_fuzzy_search });
}

// spawn a new tab with pre-provided credentials
if (request.action == "launch") {
chrome.tabs.create({url: request.url}, function (tab) {
var authAttempted = false;
chrome.webRequest.onAuthRequired.addListener(
function authListener(requestDetails) {
// only supply credentials if this is the first time for this tab
if (authAttempted) {
return {};
}
authAttempted = true;
// remove event listeners once tab loading is complete
chrome.tabs.onUpdated.addListener(function statusListener(tabId, info) {
if (info.status === "complete") {
chrome.tabs.onUpdated.removeListener(statusListener);
chrome.webRequest.onAuthRequired.removeListener(authListener);
}
});
// ask the user before sending credentials over an insecure connection
if (!requestDetails.url.match(/^https:/i)) {
var message =
"You are about to send login credentials via an insecure connection!\n\n" +
"Are you sure you want to do this? If there is an attacker watching your " +
"network traffic, they may be able to see your username and password.\n\n" +
"URL: " + requestDetails.url
;
if (!confirm(message)) {
return {};
}
}
return {authCredentials: {username: request.username, password: request.password}};
},
{urls: ["*://*/*"], tabId: tab.id},
["blocking"]
);
});
}
}

function onTabUpdated(tabId, changeInfo, tab) {
Expand Down
89 changes: 89 additions & 0 deletions chrome/icon-globe.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: 3 additions & 1 deletion chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"author": "Danny van Kooten",
"homepage_url": "https://github.com/dannyvankooten/browserpass",
"background": {
"persistent": false,
"persistent": true,
"scripts": [
"background.js"
]
Expand All @@ -28,6 +28,8 @@
"nativeMessaging",
"notifications",
"storage",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*"
],
Expand Down
77 changes: 71 additions & 6 deletions chrome/script.browserify.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var m = require("mithril");
var FuzzySort = require("fuzzysort");
var Tldjs = require("tldjs");
var app = "com.dannyvankooten.browserpass";
var activeTab;
var searching = false;
Expand Down Expand Up @@ -45,6 +46,10 @@ function view() {

return m("div.entry", [
m(selector, options, login),
m("button.launch.url", {
onclick: launchURL.bind({ entry: login, what: "url" }),
tabindex: -1
}),
m("button.copy.username", {
onclick: loginToClipboard.bind({ entry: login, what: "username" }),
tabindex: -1
Expand Down Expand Up @@ -76,7 +81,7 @@ function view() {
type: "text",
id: "search-field",
name: "s",
placeholder: "Search passwords..",
placeholder: "Search passwords...",
autocomplete: "off",
autofocus: "on",
oninput: filterLogins
Expand Down Expand Up @@ -150,7 +155,6 @@ function showFilterHint(show=true) {

function submitSearchForm(e) {
e.preventDefault();

if (fillOnSubmit && logins.length > 0) {
// fill using the first result
getLoginData.bind(logins[0])();
Expand Down Expand Up @@ -232,6 +236,50 @@ function getFaviconUrl(domain) {
return null;
}

function launchURL() {
var what = this.what;
var entry = this.entry;
chrome.runtime.sendNativeMessage(
app,
{ action: "get", entry: this.entry },
function(response) {
if (chrome.runtime.lastError) {
error = chrome.runtime.lastError.message;
m.redraw();
return;
}
// get url from login path if not available in the host app response
if (!response.hasOwnProperty("url") || response.url.length == 0) {
var parts = entry.split(/\//).reverse();
for (var i in parts) {
var part = parts[i];
var info = Tldjs.parse(part);
if (info.isValid && info.tldExists && info.domain !== null && info.hostname === part) {
response.url = part;
break;
}
}
}
// if a url is present, then launch a new tab via the background script
if (response.hasOwnProperty("url") && response.url.length > 0) {
var url = response.url.match(/^([a-z]+:)?\/\//i) ? response.url : "http://" + response.url;
chrome.runtime.sendMessage({action: "launch", url: url, username: response.u, password: response.p});
window.close();
return;
}
// no url available
if (!response.hasOwnProperty("url")) {
resetWithError(
"Unable to determine the URL for this entry. If you have defined one in the password file, " +
"your host application must be at least v2.0.14 for this to be usable."
);
} else {
resetWithError("Unable to determine the URL for this entry.");
}
}
);
}

function getLoginData() {
searching = true;
logins = resultLogins = [];
Expand Down Expand Up @@ -298,16 +346,18 @@ function keyHandler(e) {
break;
case "c":
if (e.target.id != "search-field" && e.ctrlKey) {
document.activeElement["nextElementSibling"][
"nextElementSibling"
].click();
document.activeElement.parentNode.querySelector("button.copy.password").click();
}
break;
case "C":
if (e.target.id != "search-field") {
document.activeElement["nextElementSibling"].click();
document.activeElement.parentNode.querySelector("button.copy.username").click();
}
break;
case "g":
if (e.target.id != "search-field") {
document.activeElement.parentNode.querySelector("button.launch.url").click();
}
}
}

Expand Down Expand Up @@ -336,3 +386,18 @@ function oncreate() {
document.getElementById("search-field").focus();
}, 100);
}

function resetWithError(errMsg) {
domain = '';
logins = resultLogins = [];
fillOnSubmit = false;
searching = false;
var filterSearch = document.getElementById("filter-search");
filterSearch.style.display = "none";
filterSearch.textContent = '';
var searchField = document.getElementById("search-field");
searchField.setAttribute("placeholder", "Search passwords...");
error = errMsg;
m.redraw();
searchField.focus();
}
7 changes: 6 additions & 1 deletion chrome/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,17 @@ body {
border-bottom: 1px dotted #ccc;
}

.copy {
.copy, .launch {
width: 32px;
border: 0;
cursor: pointer;
}

.url {
background: no-repeat url('icon-globe.svg') center;
background-size: 16px 16px;
}

.username {
background: no-repeat url('icon-user.svg') center;
background-size: 16px 16px;
Expand Down
4 changes: 3 additions & 1 deletion firefox/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"page": "options.html"
},
"background": {
"persistent": false,
"persistent": true,
"scripts": [
"background.js"
]
Expand All @@ -26,6 +26,8 @@
"nativeMessaging",
"notifications",
"storage",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*"
],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"dependencies": {
"browserify": "^14.4.0",
"mithril": "^1.1.4",
"fuzzysort": "^1.1.0"
"fuzzysort": "^1.1.0",
"tldjs": "^2.3.1"
}
}
8 changes: 7 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"

punycode@^1.3.2:
punycode@^1.3.2, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"

Expand Down Expand Up @@ -800,6 +800,12 @@ timers-browserify@^1.0.1:
dependencies:
process "~0.11.0"

tldjs@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-2.3.1.tgz#cf09c3eb5d7403a9e214b7d65f3cf9651c0ab039"
dependencies:
punycode "^1.4.1"

to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
Expand Down

0 comments on commit 604f560

Please sign in to comment.