From 5d0acf696c01286737dc84963b0031406ea0ef42 Mon Sep 17 00:00:00 2001 From: Nguyen Ngo Date: Thu, 7 May 2026 14:15:35 -0400 Subject: [PATCH] 05/07: updated extension after updating Advanced Settings to functioning --- extension/content/content.js | 22 +++++- extension/popup/popup.css | 49 +++++++++++++ extension/popup/popup.js | 135 +++++++++++++++++++++++++++++------ 3 files changed, 184 insertions(+), 22 deletions(-) diff --git a/extension/content/content.js b/extension/content/content.js index ee9a110..c1882af 100644 --- a/extension/content/content.js +++ b/extension/content/content.js @@ -167,7 +167,7 @@ el.dispatchEvent(new Event("change", { bubbles: true })); } - function doAutofill(username, password) { + function doAutofill(username, password, autologin) { const pwFields = visiblePasswordFields(); if (!pwFields.length) return; const pwField = pwFields[0]; @@ -180,6 +180,24 @@ el.style.outline = ""; }, 1500); }); + // Autologin: submit the form automatically after filling. + if (autologin) { + const form = pwField.closest("form"); + if (form) { + setTimeout(function () { + // Prefer clicking a visible submit button so site-specific submit + // handlers (React, Vue, etc.) fire correctly. + var submitBtn = form.querySelector( + '[type="submit"]:not([disabled])', + ); + if (submitBtn) { + submitBtn.click(); + } else { + form.submit(); + } + }, 400); + } + } } // ── Icon button (fixed-position, outside the DOM tree of the field) ─────────── @@ -1105,7 +1123,7 @@ chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) { if (msg.type === "DO_AUTOFILL") { - doAutofill(msg.username, msg.password); + doAutofill(msg.username, msg.password, !!msg.autologin); sendResponse({ ok: true }); } if (msg.type === "VAULT_UPDATED") { diff --git a/extension/popup/popup.css b/extension/popup/popup.css index dfba097..f52dfed 100644 --- a/extension/popup/popup.css +++ b/extension/popup/popup.css @@ -1045,3 +1045,52 @@ body { font-size: 11px; margin-left: 4px; } + +/* ── Master-password reprompt overlay ────────────────────────────── */ +.reprompt-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 99999; + padding: 16px; +} + +.reprompt-card { + background: #fff; + border-radius: 12px; + padding: 18px 16px 14px; + width: 100%; + max-width: 290px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); +} + +.reprompt-header { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: 700; + color: #1a1a2e; + margin-bottom: 6px; +} + +.reprompt-desc { + font-size: 11px; + color: #6b7280; + margin-bottom: 12px; + line-height: 1.45; +} + +.reprompt-actions { + display: flex; + gap: 8px; + margin-top: 10px; +} + +.reprompt-actions .btn-primary, +.reprompt-actions .btn-ghost { + margin-top: 0; +} diff --git a/extension/popup/popup.js b/extension/popup/popup.js index 28dd127..017dfa9 100644 --- a/extension/popup/popup.js +++ b/extension/popup/popup.js @@ -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 = ` +
+
+ + + + + Master password required +
+

This item is protected. Enter your master password to continue.

+
+ + +
+ +
+ + +
+
`; + + 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: '', - action: () => { + action: async () => { chrome.tabs.create({ url: item.plain.url }); flyout.remove(); }, @@ -897,7 +984,11 @@ function renderList() { ? { label: "Copy username", icon: '', - 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: '', - 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 + "" + escHtml(mi.label) + ""; - row.addEventListener("click", (e) => { + row.addEventListener("click", async (e) => { e.stopPropagation(); - mi.action(); + await mi.action(); }); flyout.appendChild(row); });