05/07: updated extension after updating Advanced Settings to functioning
This commit is contained in:
@@ -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") {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+115
-20
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user