Skip to content

Commit 0eb68f8

Browse files
committed
add function to add keys
1 parent eff4956 commit 0eb68f8

File tree

4 files changed

+302
-5
lines changed

4 files changed

+302
-5
lines changed

addKey.html

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Add Key - VaultPass</title>
5+
<meta charset="utf-8" />
6+
<script
7+
type="application/javascript"
8+
src="browser-polyfill.min.js"
9+
></script>
10+
<link rel="stylesheet" href="style.css" />
11+
</head>
12+
13+
<body>
14+
<header class="header">
15+
<div class="header__container">
16+
<h1 class="h1 title">Add Key</h1>
17+
<nav role="navigation" class="menu" aria-label="Main Menu">
18+
<div class="tabs">
19+
<a href="/popup.html" class="tab" data-page="keys">Keys</a>
20+
<a href="/options.html" class="tab" data-page="options">Options</a>
21+
</div>
22+
</nav>
23+
</div>
24+
</header>
25+
26+
<main class="main">
27+
<div role="alert" id="notify" aria-live="assertive"></div>
28+
29+
<form id="addKeyForm">
30+
<div class="form-group">
31+
<label for="secretPath" class="label">
32+
Secret Path:
33+
<span class="text-secondary" style="font-weight: normal">
34+
Select where to store this key
35+
</span>
36+
</label>
37+
<select
38+
id="secretPath"
39+
name="secretPath"
40+
required
41+
aria-required="true"
42+
>
43+
<option value="">-- Select a path --</option>
44+
</select>
45+
</div>
46+
47+
<div class="form-group">
48+
<label for="urlRegex" class="label">
49+
URL Pattern (regex):
50+
<span class="text-secondary" style="font-weight: normal">
51+
e.g., ^example\.com.* (used as KV object name)
52+
</span>
53+
</label>
54+
<input
55+
type="text"
56+
id="urlRegex"
57+
name="urlRegex"
58+
placeholder="^example\.com.*"
59+
required
60+
aria-required="true"
61+
/>
62+
</div>
63+
64+
<div class="form-group">
65+
<label for="username" class="label">Username:</label>
66+
<input
67+
type="text"
68+
id="username"
69+
name="username"
70+
placeholder="Enter username"
71+
required
72+
aria-required="true"
73+
/>
74+
</div>
75+
76+
<div class="form-group">
77+
<label for="password" class="label">Password:</label>
78+
<input
79+
type="password"
80+
id="password"
81+
name="password"
82+
placeholder="Enter password"
83+
required
84+
aria-required="true"
85+
/>
86+
</div>
87+
88+
<div class="form-group">
89+
<label for="title" class="label">
90+
Title (optional):
91+
<span class="text-secondary" style="font-weight: normal">
92+
A friendly name for this key
93+
</span>
94+
</label>
95+
<input
96+
type="text"
97+
id="title"
98+
name="title"
99+
placeholder="e.g., Example Site Login"
100+
/>
101+
</div>
102+
103+
<div class="flex justify-between" style="gap: 1rem">
104+
<button type="button" id="cancelButton" class="button">Cancel</button>
105+
<button type="submit" class="button button--primary">Save Key</button>
106+
</div>
107+
</form>
108+
</main>
109+
110+
<script src="Notify.js"></script>
111+
<script src="common.js"></script>
112+
<script src="addKey.js"></script>
113+
</body>
114+
</html>

addKey.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/* global Notify storePathComponents */
2+
3+
const notify = new Notify(document.querySelector('#notify'));
4+
const addKeyForm = document.getElementById('addKeyForm');
5+
const cancelButton = document.getElementById('cancelButton');
6+
const secretPathSelect = document.getElementById('secretPath');
7+
8+
async function mainLoaded() {
9+
// Load available secret paths
10+
await loadSecretPaths();
11+
12+
// Set up event listeners
13+
addKeyForm.addEventListener('submit', handleFormSubmit);
14+
cancelButton.addEventListener('click', handleCancel);
15+
}
16+
17+
async function loadSecretPaths() {
18+
try {
19+
const secrets = (await browser.storage.sync.get('secrets')).secrets;
20+
21+
if (!secrets || secrets.length === 0) {
22+
notify.error(
23+
'No secret paths configured. Please go to Options and configure at least one secret path first.'
24+
);
25+
return;
26+
}
27+
28+
// Populate the dropdown
29+
secrets.forEach((secret) => {
30+
const option = document.createElement('option');
31+
option.value = secret;
32+
option.textContent = secret;
33+
secretPathSelect.appendChild(option);
34+
});
35+
36+
// If there's only one path, auto-select it
37+
if (secrets.length === 1) {
38+
secretPathSelect.value = secrets[0];
39+
}
40+
} catch (err) {
41+
notify.error(`Error loading secret paths: ${err.message}`);
42+
}
43+
}
44+
45+
async function handleFormSubmit(event) {
46+
event.preventDefault();
47+
48+
// Clear any existing messages
49+
notify.clear();
50+
51+
// Scroll to top to show messages
52+
window.scrollTo({ top: 0, behavior: 'smooth' });
53+
54+
const secretPath = document.getElementById('secretPath').value.trim();
55+
const urlRegex = document.getElementById('urlRegex').value.trim();
56+
const username = document.getElementById('username').value.trim();
57+
const password = document.getElementById('password').value.trim();
58+
const title = document.getElementById('title').value.trim();
59+
60+
// Validate inputs
61+
if (!secretPath) {
62+
notify.error('Please select a secret path.');
63+
return;
64+
}
65+
66+
if (!urlRegex || !username || !password) {
67+
notify.error('URL pattern, username, and password are required.');
68+
return;
69+
}
70+
71+
// Check if URL pattern starts with http:// or https://
72+
if (urlRegex.startsWith('http://') || urlRegex.startsWith('https://')) {
73+
notify.error(
74+
'URL pattern cannot start with http:// or https://. Please use a regex pattern instead (e.g., ^https://example\\.com.*).'
75+
);
76+
return;
77+
}
78+
79+
// Validate regex pattern
80+
try {
81+
new RegExp(urlRegex);
82+
} catch {
83+
notify.error('Invalid URL regex pattern. Please check your pattern.');
84+
return;
85+
}
86+
87+
// Double-encode the URL regex to use as the key name in Vault
88+
// First encoding: a/b -> a%2Fb
89+
// Second encoding: a%2Fb -> a%252Fb
90+
// Vault will decode once: a%252Fb -> a%2Fb (stored in Vault)
91+
const encodedKeyName = encodeURIComponent(encodeURIComponent(urlRegex));
92+
93+
try {
94+
// Get Vault credentials
95+
const vaultToken = (await browser.storage.local.get('vaultToken'))
96+
.vaultToken;
97+
const vaultServerAddress = (await browser.storage.sync.get('vaultAddress'))
98+
.vaultAddress;
99+
const storePath = (await browser.storage.sync.get('storePath')).storePath;
100+
101+
if (!vaultToken || !vaultServerAddress) {
102+
notify.error(
103+
'Not authenticated with Vault. Please go to Options and login first.'
104+
);
105+
return;
106+
}
107+
108+
const storeComponents = storePathComponents(storePath);
109+
110+
// Create the data object to save
111+
const keyData = {
112+
username: username,
113+
password: password,
114+
};
115+
116+
if (title) {
117+
keyData.title = title;
118+
}
119+
120+
// Save to Vault using the encoded key name
121+
// The key name is already encoded earlier in the function
122+
const vaultUrl = `${vaultServerAddress}/v1/${storeComponents.root}/data/${storeComponents.subPath}/${secretPath}${encodedKeyName}`;
123+
124+
const response = await fetch(vaultUrl, {
125+
method: 'POST',
126+
headers: {
127+
'X-Vault-Token': vaultToken,
128+
'Content-Type': 'application/json',
129+
},
130+
body: JSON.stringify({ data: keyData }),
131+
});
132+
133+
if (!response.ok) {
134+
const errorText = await response.text();
135+
throw new Error(`Failed to save to Vault: ${errorText}`);
136+
}
137+
138+
notify.success('Key saved successfully to Vault!', { time: 2000 });
139+
140+
// Clear form
141+
addKeyForm.reset();
142+
143+
// Redirect back to popup after a short delay
144+
setTimeout(() => {
145+
window.location.href = '/popup.html';
146+
}, 1500);
147+
} catch (err) {
148+
notify.error(`Error saving key: ${err.message}`);
149+
}
150+
}
151+
152+
function handleCancel() {
153+
window.location.href = '/popup.html';
154+
}
155+
156+
document.addEventListener('DOMContentLoaded', mainLoaded, false);

popup.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ <h1 class="h1 title">VaultPass</h1>
3131

3232
<main class="main">
3333
<div role="alert" id="notify" aria-live="assertive"></div>
34-
<label for="vault-search">Search Secrets:</label>
34+
<div
35+
class="flex justify-between items-center"
36+
style="margin-bottom: 1rem"
37+
>
38+
<label for="vault-search">Search Secrets:</label>
39+
<button
40+
id="addKeyButton"
41+
class="button button--primary"
42+
title="Add new key"
43+
>
44+
+ Add Key
45+
</button>
46+
</div>
3547
<input
3648
type="search"
3749
id="vault-search"

popup.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,25 @@ async function querySecrets(searchString, manualSearch) {
8181
return;
8282
}
8383
for (const element of (await secretsInPath.json()).data.keys) {
84-
const pattern = new RegExp(element);
84+
// Vault returns keys as stored (e.g., a%2Fb)
85+
// Decode it for pattern matching (a%2Fb -> a/b)
86+
const decodedElement = decodeURIComponent(element);
87+
const pattern = new RegExp(decodedElement);
8588
const patternMatches =
86-
pattern.test(searchString) || element.includes(searchString);
89+
pattern.test(searchString) || decodedElement.includes(searchString);
8790
if (patternMatches) {
88-
const urlPath = `${vaultServerAddress}/v1/${storeComponents.root}/data/${storeComponents.subPath}/${secret}${element}`;
91+
// Encode the element again for the URL since Vault will decode it
92+
// element is a%2Fb, we need to send a%252Fb so Vault decodes to a%2Fb
93+
const encodedElement = encodeURIComponent(element);
94+
const urlPath = `${vaultServerAddress}/v1/${storeComponents.root}/data/${storeComponents.subPath}/${secret}${encodedElement}`;
8995
const credentials = await getCredentials(urlPath);
9096
const credentialsSets = extractCredentialsSets(
9197
credentials.data.data
9298
);
9399

94100
for (const item of credentialsSets) {
95-
addCredentialsToList(item, element, resultList);
101+
// Display the decoded element name to the user
102+
addCredentialsToList(item, decodedElement, resultList);
96103

97104
matches++;
98105
}
@@ -138,6 +145,14 @@ const searchHandler = function (e) {
138145

139146
searchInput.addEventListener('keyup', searchHandler);
140147

148+
// Add Key button handler
149+
const addKeyButton = document.getElementById('addKeyButton');
150+
if (addKeyButton) {
151+
addKeyButton.addEventListener('click', function () {
152+
window.location.href = '/addKey.html';
153+
});
154+
}
155+
141156
function extractCredentialsSets(data) {
142157
const keys = Object.keys(data);
143158
const credentials = [];

0 commit comments

Comments
 (0)