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 = `
+
+
+
This item is protected. Enter your master password to continue.
+
+
+
+
+
Incorrect password.
+
+
+
+
+
`;
+
+ 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);
});