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
+20 -2
View File
@@ -167,7 +167,7 @@
el.dispatchEvent(new Event("change", { bubbles: true })); el.dispatchEvent(new Event("change", { bubbles: true }));
} }
function doAutofill(username, password) { function doAutofill(username, password, autologin) {
const pwFields = visiblePasswordFields(); const pwFields = visiblePasswordFields();
if (!pwFields.length) return; if (!pwFields.length) return;
const pwField = pwFields[0]; const pwField = pwFields[0];
@@ -180,6 +180,24 @@
el.style.outline = ""; el.style.outline = "";
}, 1500); }, 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) ─────────── // ── Icon button (fixed-position, outside the DOM tree of the field) ───────────
@@ -1105,7 +1123,7 @@
chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) { chrome.runtime.onMessage.addListener(function (msg, _sender, sendResponse) {
if (msg.type === "DO_AUTOFILL") { if (msg.type === "DO_AUTOFILL") {
doAutofill(msg.username, msg.password); doAutofill(msg.username, msg.password, !!msg.autologin);
sendResponse({ ok: true }); sendResponse({ ok: true });
} }
if (msg.type === "VAULT_UPDATED") { if (msg.type === "VAULT_UPDATED") {
+49
View File
@@ -1045,3 +1045,52 @@ body {
font-size: 11px; font-size: 11px;
margin-left: 4px; 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;
}
+115 -20
View File
@@ -77,6 +77,81 @@ function _copyWithAutoClear(text) {
}, 30_000); }, 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 ──────────────────────────────────────────────────────────── // ── Avatar helpers ────────────────────────────────────────────────────────────
const AVATAR_COLORS = [ const AVATAR_COLORS = [
@@ -694,7 +769,8 @@ function renderList() {
const canFill = const canFill =
item.item_type === "password" && item.item_type === "password" &&
item.plain?.username && 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 canCopy = item.item_type === "password" && item.plain?.password;
const hasTotp = const hasTotp =
item.item_type === "password" && item.item_type === "password" &&
@@ -799,16 +875,19 @@ function renderList() {
// Copy password // Copy password
listEl.querySelectorAll("[data-copy-pass]").forEach((btn) => listEl.querySelectorAll("[data-copy-pass]").forEach((btn) =>
btn.addEventListener("click", (e) => { btn.addEventListener("click", async (e) => {
e.stopPropagation(); e.stopPropagation();
const item = _items.find((i) => i.id === parseInt(btn.dataset.copyPass)); const item = _items.find((i) => i.id === parseInt(btn.dataset.copyPass));
if (item?.plain?.password) { if (!item?.plain?.password) return;
_copyWithAutoClear(item.plain.password); if (item.plain?.reprompt) {
btn.title = "Copied!"; const ok = await _repromptMasterPassword();
setTimeout(() => { if (!ok) return;
btn.title = "Copy password";
}, 1500);
} }
_copyWithAutoClear(item.plain.password);
btn.title = "Copied!";
setTimeout(() => {
btn.title = "Copy password";
}, 1500);
}), }),
); );
@@ -837,6 +916,10 @@ function renderList() {
e.stopPropagation(); e.stopPropagation();
const item = _items.find((i) => i.id === parseInt(btn.dataset.autofill)); const item = _items.find((i) => i.id === parseInt(btn.dataset.autofill));
if (!item?.plain) return; if (!item?.plain) return;
if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword();
if (!ok) return;
}
const [tab] = await chrome.tabs.query({ const [tab] = await chrome.tabs.query({
active: true, active: true,
currentWindow: true, currentWindow: true,
@@ -847,6 +930,7 @@ function renderList() {
type: "DO_AUTOFILL", type: "DO_AUTOFILL",
username: item.plain.username || "", username: item.plain.username || "",
password: item.plain.password || "", password: item.plain.password || "",
autologin: !!item.plain.autologin,
}) })
.catch(() => {}); .catch(() => {});
} }
@@ -856,16 +940,19 @@ function renderList() {
// Copy username (dedicated button) // Copy username (dedicated button)
listEl.querySelectorAll("[data-copy-user]").forEach((btn) => listEl.querySelectorAll("[data-copy-user]").forEach((btn) =>
btn.addEventListener("click", (e) => { btn.addEventListener("click", async (e) => {
e.stopPropagation(); e.stopPropagation();
const item = _items.find((i) => i.id === parseInt(btn.dataset.copyUser)); const item = _items.find((i) => i.id === parseInt(btn.dataset.copyUser));
if (item?.plain?.username) { if (!item?.plain?.username) return;
_copyWithAutoClear(item.plain.username); if (item.plain?.reprompt) {
btn.title = "Copied!"; const ok = await _repromptMasterPassword();
setTimeout(() => { if (!ok) return;
btn.title = "Copy username";
}, 1500);
} }
_copyWithAutoClear(item.plain.username);
btn.title = "Copied!";
setTimeout(() => {
btn.title = "Copy username";
}, 1500);
}), }),
); );
@@ -887,7 +974,7 @@ function renderList() {
? { ? {
label: "Open URL", 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>', 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 }); chrome.tabs.create({ url: item.plain.url });
flyout.remove(); flyout.remove();
}, },
@@ -897,7 +984,11 @@ function renderList() {
? { ? {
label: "Copy username", 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>', 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); _copyWithAutoClear(item.plain.username);
flyout.remove(); flyout.remove();
}, },
@@ -907,7 +998,11 @@ function renderList() {
? { ? {
label: "Copy password", 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>', 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); _copyWithAutoClear(item.plain.password);
flyout.remove(); flyout.remove();
}, },
@@ -919,9 +1014,9 @@ function renderList() {
const row = document.createElement("button"); const row = document.createElement("button");
row.className = "pk-flyout-item"; row.className = "pk-flyout-item";
row.innerHTML = mi.icon + "<span>" + escHtml(mi.label) + "</span>"; row.innerHTML = mi.icon + "<span>" + escHtml(mi.label) + "</span>";
row.addEventListener("click", (e) => { row.addEventListener("click", async (e) => {
e.stopPropagation(); e.stopPropagation();
mi.action(); await mi.action();
}); });
flyout.appendChild(row); flyout.appendChild(row);
}); });