Skip to content
Draft
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
75 changes: 75 additions & 0 deletions add.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vault for Chrome: Add secret</title>
<script
type="application/javascript"
src="browser-polyfill.min.js"
></script>
<link rel="stylesheet" href="style.css" />
</head>

<body>
<header class="header">
<div class="header__container">
<h1 class="h1 title">VaultPass</h1>
<nav role="navigation" class="menu" aria-label="Main Menu">
<ul class="menu__links">
<li>
<a href="/popup.html" class="link link--alt">
Keys
</a>
</li>
<li>
<a href="/add.html" class="link link--alt link--current" aria-current="page">
Add
</a>
</li>
<li>
<a href="/options.html" class="link link--alt">
Options
</a >
</li>
</ul>
</nav>
</div>
</header>

<main class="main">
<div role="alert" id="notify" aria-live="assertive"></div>
<div id="add">
<label for="vault-search">Add secret:</label>
<label class="label">
Directory:
<input type="text" class="input" list="dirsList" id="dirBox" />
<datalist id="dirsList"></datalist>
</label>
<label class="label">
Pattern matching url:
<input type="text" class="input" name="url" id="urlBox" />
</label>
<label class="label">
Username:
<input type="text" class="input" name="login" id="loginBox" />
</label>
<label class="label">
Password:
<input type="password" class="input" name="pass" id="passBox" />
</label>
<input type="submit" class="button" id="showPasswordButton" value="Show Password" />
<input
type="submit"
class="button button--primary"
value="Add login to vault"
id="addButton"
/>
</div>
</main>

<script src="Notify.js"></script>
<script src="common.js"></script>
<script src="add.js"></script>
</body>
</html>
126 changes: 126 additions & 0 deletions add.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* eslint-disable no-console */
/* global browser Notify storePathComponents */

const notify = new Notify(document.querySelector('#notify'));

async function mainLoaded() {
const tabs = await browser.tabs.query({
active: true,
currentWindow: true
});
for (let tabIndex = 0; tabIndex < tabs.length; tabIndex++) {
const tab = tabs[tabIndex];
if (tab.url) {
currentTabId = tab.id;
currentUrl = tab.url;
break;
}
}

document.getElementById('addButton').addEventListener('click', addButtonClick, false);
document.getElementById('showPasswordButton').addEventListener('click', showPasswordClick, false);

const vaultServer = document.getElementById('urlBox');
vaultServer.value = new URL(currentUrl).host;

try {
await populateDirectorySelection();
} catch (err) {
notify.clear().error(err.message);
return;
}

let secretList = (await browser.storage.sync.get('secrets')).secrets || [];
if (secretList) {
try {
await querySecretsCallback(currentUrl, secretList[0], function(element, credentialsSets) {
if (credentialsSets) {
const c = credentialsSets[0];
document.getElementById('urlBox').value = element;
}
});
} catch (err) {
notify.clear().error(err.message);
return
}
}
}

/**
* populate the choose list of secrets directories when adding
*/
async function populateDirectorySelection(vaultServerAddress, vaultToken, policies, storePath) {
const fetchListOfSecretDirs = await vaultApiCall('LIST', 'metadata', '', 'Fetching secrets directories');

let activeSecrets = (await browser.storage.sync.get('secrets')).secrets || [];
const availableSecrets = (await fetchListOfSecretDirs.json()).data.keys;
activeSecrets = activeSecrets.filter((x) => availableSecrets.indexOf(x) !== -1);

const dirsList = document.getElementById('dirsList');
var first = 1;
for (const secret of activeSecrets) {
var option = document.createElement('option');
option.value = secret;
if (first) {
first = 0;
option.selected = true;
const dirBox = document.getElementById('dirBox')
dirBox.placeholder = secret;
dirBox.value = secret;
}
dirsList.appendChild(option);
}
}

async function addButtonClick() {
const dirBox = document.getElementById('dirBox').value;
const urlBox = document.getElementById('urlBox').value;
const loginBox = document.getElementById('loginBox').value;
const passBox = document.getElementById('passBox').value;
// verify input not empty. TODO: verify correct URL format.
if (urlBox.includes("/")) {
notify.error("Bad input, url has slash")
return
}
if (dirBox.length == 0 || urlBox.length == 0 || loginBox.length == 0 ||
passBox.length == 0) {
notify.error("Bad input, field is empty")
return
}
// get current value if exists
const passpath = dirBox + urlBox;
const resp = await vaultApiCall("GET", 'data', passpath, '')
const respjson = resp.ok ? await resp.json() : {};
const data = resp.ok ? respjson.data : {};
const cas = resp.ok ? respjson.data.metadata.version : 0;
const cur = resp.ok ? respjson.data.data : {};
const userkey = `username-vaultpass-${loginBox}`;
cur[userkey] = loginBox;
cur[`password-vaultpass-${passBox}`] = passBox;
const postdata = {
'data': cur,
'options': {
'cas': cas,
},
};
const postdatajson = JSON.stringify(postdata);
//notify.error(`cur=${cur} cas=${cas} data=${postdatajson}`);
const resp2 =
await vaultApiCall("POST", 'data', passpath,
`could not update value with ${postdatajson}`, postdata);

document.getElementById('loginBox').value = "";
document.getElementById('passBox').value = "";
notify.success(`Added entry ${userkey} to ${passpath}`);
}

function showPasswordClick() {
var x = document.getElementById("passBox");
if (x.type === "password") {
x.type = "text";
} else {
x.type = "password";
}
}

document.addEventListener('DOMContentLoaded', mainLoaded, false);
114 changes: 106 additions & 8 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,119 @@
/* global browser chrome */

function storePathComponents(storePath) {
let path = 'secret/vaultPass';
if (storePath && storePath.length > 0) {
path = storePath;
}
const path = storePath && storePath.length > 0 ? storePath : 'secret/vaultPass';
const pathComponents = path.split('/');
const storeRoot = pathComponents[0];
const storeSubPath =
pathComponents.length > 0 ? pathComponents.slice(1).join('/') : '';

const storeSubPath = pathComponents.length > 0 ? pathComponents.slice(1).join('/') : '';
return {
root: storeRoot,
subPath: storeSubPath,
subPath: storeSubPath ? '/' + storeSubPath : '',
};
}

/**
* Make a call to vault api.
* @param string method GET or POST or LIST etc.
* @param midpath The middle of the vault path. Basically "metadata" or "data".
* @param path The suffix of the path to query from vault.
* @param string error if set, will error this if not ok.
* @param dict body if set, will add it to POST it
*/
async function vaultApiCall(method, midpath, path = "", error = "", body = undefined) {
const vaultToken = (await browser.storage.local.get('vaultToken')).vaultToken;
const vaultServerAddress = (await browser.storage.sync.get('vaultAddress')).vaultAddress;
const storePath = (await browser.storage.sync.get('storePath')).storePath;
const storeComponents = storePathComponents(storePath);
if (path) {
// make sure path has leading slash.
path = "/" + path.replace(/^\/*/, "");
}
const url = `${vaultServerAddress}/v1/${storeComponents.root}/${midpath}${storeComponents.subPath}${path}`;
const res = await fetch(url, {
method: method,
headers: {
'X-Vault-Token': vaultToken,
'Content-Type': 'application/json',
},
body: body === undefined ? undefined : JSON.stringify(body),
});
if (error && (!res.ok || res.status != 200)) {
const apiResponse = await res.json();
const msg = `ERROR: ${error}. Calling ${url} failed with status=${
res.status}. ${apiResponse.errors.join('. ')}`
notify.error(msg);
throw msg;
}
return res;
}

/**
* From data returned from vault in data extract the credentials.
*/
function extractCredentialsSets(data) {
const keys = Object.keys(data);
const credentials = [];
for (const key of keys) {
if (key.startsWith('username')) {
const suffix = key.substring(8);
const passwordField = 'password' + suffix;
if (data[passwordField]) {
credentials.push({
username: data[key],
password: data['password' + suffix],
title: data.hasOwnProperty('title' + suffix)
? data['title' + suffix]
: data.hasOwnProperty('title')
? data['title']
: '',
comment: data.hasOwnProperty('comment' + suffix)
? data['comment' + suffix]
: data.hasOwnProperty('comment')
? data['comment']
: '',
});
}
}
}
return credentials;
}

/**
* small wrapper around vault api call to get the secrets stored in kv in vault at urlpath
*/
async function getCredentials(urlPath) {
const result = await vaultApiCall("GET", "data", urlPath, "getting credentials")
return await result.json();
}

/**
* makes a query to get all secrets
* @param string searchString The saerched string, most probably URL.
* @param string secret
* @param function(string, credentials[]) callback Callback to call with the url and credentials.
*/
async function querySecretsCallback(searchString, secret, callback) {
const secretsInPath = await vaultApiCall("LIST", "metadata", `${secret}`)
if (!secretsInPath.ok) {
if (secretsInPath.status !== 404) {
notify.error(`Unable to read ${secret}... Try re-login`, {
removeOption: true,
});
}
return;
}
for (const element of (await secretsInPath.json()).data.keys) {
const pattern = new RegExp(element);
const patternMatches = pattern.test(searchString) || element.includes(searchString);
if (patternMatches) {
const credentials = await getCredentials(`${secret}${element}`);
const credentialsSets = extractCredentialsSets(credentials.data.data);
callback(element, credentialsSets);
notify.clear();
}
}
}

if (!browser.browserAction) {
browser.browserAction = chrome.browserAction ?? chrome.action;
}
15 changes: 10 additions & 5 deletions options.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ <h1 class="h1 title">VaultPass</h1>
<nav role="navigation" class="menu" aria-label="Main Menu">
<ul class="menu__links">
<li>
<a href="/popup.html" class="link link--alt" aria-current="page"
>Keys
<a href="/popup.html" class="link link--alt">
Keys
</a>
</li>
<li>
<a href="/options.html" class="link link--alt link--current"
>Options</a
>
<a href="/add.html" class="link link--alt">
Add
</a>
</li>
<li>
<a href="/options.html" class="link link--alt link--current" aria-current="page">
Options
</a >
</li>
</ul>
</nav>
Expand Down
Loading