05/07: updated extension after updating Advanced Settings to functioning

This commit is contained in:
2026-05-07 14:15:35 -04:00
parent f71032096b
commit 5d0acf696c
3 changed files with 184 additions and 22 deletions
+115 -20
View File
@@ -77,6 +77,81 @@ function _copyWithAutoClear(text) {
}, 30_000);
}
/**
* Show an in-popup master-password re-prompt overlay.
* Resolves true if the entered password matches the current vault key,
* false if the user cancels or enters a wrong password.
*/
async function _repromptMasterPassword() {
return new Promise((resolve) => {
// Build the overlay element.
const overlay = document.createElement("div");
overlay.id = "pk-reprompt-overlay";
overlay.className = "reprompt-overlay";
overlay.innerHTML = `
<div class="reprompt-card">
<div class="reprompt-header">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<rect x="3" y="10" width="18" height="12" rx="2" stroke="#c0392b" stroke-width="1.8"/>
<path d="M8 10V7a4 4 0 018 0v3" stroke="#c0392b" stroke-width="1.8" stroke-linecap="round"/>
</svg>
<span>Master password required</span>
</div>
<p class="reprompt-desc">This item is protected. Enter your master password to continue.</p>
<div class="form-group">
<label for="reprompt-pw">Master Password</label>
<input type="password" id="reprompt-pw" placeholder="Master password" autocomplete="current-password"/>
</div>
<p id="reprompt-error" class="pk-error hidden">Incorrect password.</p>
<div class="reprompt-actions">
<button id="reprompt-confirm" class="btn-primary btn-sm">Confirm</button>
<button id="reprompt-cancel" class="btn-ghost btn-sm">Cancel</button>
</div>
</div>`;
document.getElementById("app").appendChild(overlay);
const input = overlay.querySelector("#reprompt-pw");
const errEl = overlay.querySelector("#reprompt-error");
input.focus();
async function attempt() {
errEl.classList.add("hidden");
const pw = input.value;
if (!pw) { errEl.textContent = "Enter your master password."; errEl.classList.remove("hidden"); return; }
try {
let { enc_key_salt } = await chrome.storage.session.get("enc_key_salt");
if (!enc_key_salt) {
const local = await chrome.storage.local.get("enc_key_salt");
enc_key_salt = local.enc_key_salt;
}
if (!enc_key_salt) { errEl.textContent = "Session expired. Please log in again."; errEl.classList.remove("hidden"); return; }
// Derive a candidate key and compare its JWK to the stored key.
const candidate = await ExtCrypto.deriveVaultKey(pw, enc_key_salt);
const candidateJwk = await ExtCrypto.exportVaultKey(candidate);
const { vault_key_jwk } = await chrome.storage.session.get("vault_key_jwk");
if (!vault_key_jwk || JSON.stringify(candidateJwk) !== JSON.stringify(vault_key_jwk)) {
throw new Error("mismatch");
}
cleanup(true);
} catch {
errEl.textContent = "Incorrect password.";
errEl.classList.remove("hidden");
input.value = "";
input.focus();
}
}
function cleanup(result) {
overlay.remove();
resolve(result);
}
overlay.querySelector("#reprompt-confirm").addEventListener("click", attempt);
overlay.querySelector("#reprompt-cancel").addEventListener("click", () => cleanup(false));
input.addEventListener("keydown", (e) => { if (e.key === "Enter") attempt(); if (e.key === "Escape") cleanup(false); });
});
}
// ── Avatar helpers ────────────────────────────────────────────────────────────
const AVATAR_COLORS = [
@@ -694,7 +769,8 @@ function renderList() {
const canFill =
item.item_type === "password" &&
item.plain?.username &&
item.plain?.password;
item.plain?.password &&
item.plain?.autofill !== false; // respect Advanced Setting
const canCopy = item.item_type === "password" && item.plain?.password;
const hasTotp =
item.item_type === "password" &&
@@ -799,16 +875,19 @@ function renderList() {
// Copy password
listEl.querySelectorAll("[data-copy-pass]").forEach((btn) =>
btn.addEventListener("click", (e) => {
btn.addEventListener("click", async (e) => {
e.stopPropagation();
const item = _items.find((i) => i.id === parseInt(btn.dataset.copyPass));
if (item?.plain?.password) {
_copyWithAutoClear(item.plain.password);
btn.title = "Copied!";
setTimeout(() => {
btn.title = "Copy password";
}, 1500);
if (!item?.plain?.password) return;
if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword();
if (!ok) return;
}
_copyWithAutoClear(item.plain.password);
btn.title = "Copied!";
setTimeout(() => {
btn.title = "Copy password";
}, 1500);
}),
);
@@ -837,6 +916,10 @@ function renderList() {
e.stopPropagation();
const item = _items.find((i) => i.id === parseInt(btn.dataset.autofill));
if (!item?.plain) return;
if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword();
if (!ok) return;
}
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true,
@@ -847,6 +930,7 @@ function renderList() {
type: "DO_AUTOFILL",
username: item.plain.username || "",
password: item.plain.password || "",
autologin: !!item.plain.autologin,
})
.catch(() => {});
}
@@ -856,16 +940,19 @@ function renderList() {
// Copy username (dedicated button)
listEl.querySelectorAll("[data-copy-user]").forEach((btn) =>
btn.addEventListener("click", (e) => {
btn.addEventListener("click", async (e) => {
e.stopPropagation();
const item = _items.find((i) => i.id === parseInt(btn.dataset.copyUser));
if (item?.plain?.username) {
_copyWithAutoClear(item.plain.username);
btn.title = "Copied!";
setTimeout(() => {
btn.title = "Copy username";
}, 1500);
if (!item?.plain?.username) return;
if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword();
if (!ok) return;
}
_copyWithAutoClear(item.plain.username);
btn.title = "Copied!";
setTimeout(() => {
btn.title = "Copy username";
}, 1500);
}),
);
@@ -887,7 +974,7 @@ function renderList() {
? {
label: "Open URL",
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M15 3h6v6M10 14L21 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
action: () => {
action: async () => {
chrome.tabs.create({ url: item.plain.url });
flyout.remove();
},
@@ -897,7 +984,11 @@ function renderList() {
? {
label: "Copy username",
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
action: () => {
action: async () => {
if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword();
if (!ok) return;
}
_copyWithAutoClear(item.plain.username);
flyout.remove();
},
@@ -907,7 +998,11 @@ function renderList() {
? {
label: "Copy password",
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><rect x="3" y="10" width="18" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10V7a4 4 0 018 0v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
action: () => {
action: async () => {
if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword();
if (!ok) return;
}
_copyWithAutoClear(item.plain.password);
flyout.remove();
},
@@ -919,9 +1014,9 @@ function renderList() {
const row = document.createElement("button");
row.className = "pk-flyout-item";
row.innerHTML = mi.icon + "<span>" + escHtml(mi.label) + "</span>";
row.addEventListener("click", (e) => {
row.addEventListener("click", async (e) => {
e.stopPropagation();
mi.action();
await mi.action();
});
flyout.appendChild(row);
});