05/07: updated extension after updating Advanced Settings to functioning
This commit is contained in:
+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