05/19 Enhance codes 2
This commit is contained in:
+82
-47
@@ -471,8 +471,8 @@ const Vault = (() => {
|
||||
_sortOrder === "folder"
|
||||
? Object.keys(groups).sort()
|
||||
: ["(No folder)", ..._folders.map((f) => f.name)].filter(
|
||||
(k) => groups[k],
|
||||
);
|
||||
(k) => groups[k],
|
||||
);
|
||||
Object.keys(groups).forEach((k) => {
|
||||
if (!keys.includes(k)) keys.push(k);
|
||||
});
|
||||
@@ -540,7 +540,7 @@ const Vault = (() => {
|
||||
case "card":
|
||||
subText = item.plain.card_number
|
||||
? "•••• " +
|
||||
String(item.plain.card_number).replace(/\s/g, "").slice(-4)
|
||||
String(item.plain.card_number).replace(/\s/g, "").slice(-4)
|
||||
: "";
|
||||
break;
|
||||
case "bank":
|
||||
@@ -920,9 +920,9 @@ const Vault = (() => {
|
||||
0,
|
||||
Math.round(
|
||||
100 -
|
||||
(weak.length / total) * 40 -
|
||||
(reused.length / total) * 30 -
|
||||
(old.length / total) * 15,
|
||||
(weak.length / total) * 40 -
|
||||
(reused.length / total) * 30 -
|
||||
(old.length / total) * 15,
|
||||
),
|
||||
);
|
||||
const cls =
|
||||
@@ -1072,16 +1072,16 @@ const Vault = (() => {
|
||||
</div>
|
||||
<ul class="sec-item-list">
|
||||
${breached
|
||||
.map((i) => {
|
||||
const count =
|
||||
hibpResults.find((r) => r.item.id === i.id)?.count || 0;
|
||||
return `<li class="sec-item">
|
||||
.map((i) => {
|
||||
const count =
|
||||
hibpResults.find((r) => r.item.id === i.id)?.count || 0;
|
||||
return `<li class="sec-item">
|
||||
<span class="sec-item-name">${escHtml(i.name)}</span>
|
||||
<span class="sec-item-sub">${escHtml(i.plain?.username || "")} — seen ${count.toLocaleString()} time${count !== 1 ? "s" : ""} in breaches</span>
|
||||
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Change</button>
|
||||
</li>`;
|
||||
})
|
||||
.join("")}
|
||||
})
|
||||
.join("")}
|
||||
</ul>`;
|
||||
hibpSection.querySelectorAll("[data-sec-edit]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
@@ -1577,12 +1577,21 @@ const Vault = (() => {
|
||||
// name for shares created before the enc_name migration.
|
||||
const vaultItem = _items.find((i) => i.id === s.item_id);
|
||||
const displayName = vaultItem?.name || s.item_name;
|
||||
const expiryLabel = (() => {
|
||||
if (!s.expires_at) return "";
|
||||
const daysLeft = Math.ceil(
|
||||
(new Date(s.expires_at).getTime() - Date.now()) / 86400000,
|
||||
);
|
||||
if (daysLeft <= 0) return " · <span class=\"badge badge-danger\">Expired</span>";
|
||||
if (daysLeft <= 3) return ` · <span class="badge badge-warn">Expires in ${daysLeft}d</span>`;
|
||||
return ` · Expires ${new Date(s.expires_at).toLocaleDateString()}`;
|
||||
})();
|
||||
return `
|
||||
<li class="share-item">
|
||||
<div class="share-icon">${itemIcon(s.item_type)}</div>
|
||||
<div class="share-info">
|
||||
<span class="share-name">${escHtml(displayName)}</span>
|
||||
<span class="share-meta">Shared with ${escHtml(s.recipient_email)} · ${s.accepted ? "✅ Accepted" : "⏳ Pending"}</span>
|
||||
<span class="share-meta">Shared with ${escHtml(s.recipient_email)} · ${s.accepted ? "✅ Accepted" : "⏳ Pending"}${expiryLabel}</span>
|
||||
</div>
|
||||
<button class="btn-icon" title="Revoke share" data-revoke="${s.id}">🗑️</button>
|
||||
</li>`;
|
||||
@@ -1616,14 +1625,26 @@ const Vault = (() => {
|
||||
}
|
||||
ul.innerHTML = shares
|
||||
.map(
|
||||
(s) => `
|
||||
(s) => {
|
||||
// Expiry label for pending (unaccepted) items.
|
||||
const expiryLabel = (() => {
|
||||
if (!s.expires_at || s.accepted) return "";
|
||||
const daysLeft = Math.ceil(
|
||||
(new Date(s.expires_at).getTime() - Date.now()) / 86400000,
|
||||
);
|
||||
if (daysLeft <= 3)
|
||||
return ` · <span class="badge badge-warn">Expires in ${daysLeft}d</span>`;
|
||||
return ` · Expires ${new Date(s.expires_at).toLocaleDateString()}`;
|
||||
})();
|
||||
return `
|
||||
<li class="share-item">
|
||||
<div class="share-icon">${itemIcon(s.item_type)}</div>
|
||||
<div class="share-info">
|
||||
<span class="share-name">${escHtml(s.item_name)}</span>
|
||||
<span class="share-meta">From ${escHtml(s.owner_email)}</span>
|
||||
<span class="share-meta">From ${escHtml(s.owner_email)}${expiryLabel}</span>
|
||||
</div>
|
||||
${!s.accepted
|
||||
${
|
||||
!s.accepted
|
||||
? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>`
|
||||
: `<button class="btn-secondary btn-sm"
|
||||
data-view-share="${s.id}"
|
||||
@@ -1634,8 +1655,9 @@ const Vault = (() => {
|
||||
data-iv-name="${escHtml(s.iv_name || "")}"
|
||||
data-name="${escHtml(s.item_name)}"
|
||||
data-type="${escHtml(s.item_type || "")}">View</button>`
|
||||
}
|
||||
</li>`,
|
||||
}
|
||||
</li>`;
|
||||
},
|
||||
)
|
||||
.join("");
|
||||
|
||||
@@ -1814,6 +1836,11 @@ const Vault = (() => {
|
||||
item_type: item.item_type,
|
||||
enc_name,
|
||||
iv_name,
|
||||
expires_days: (() => {
|
||||
const sel = document.getElementById("share-expiry-select");
|
||||
const v = sel ? parseInt(sel.value) : NaN;
|
||||
return isNaN(v) || v === 0 ? null : v;
|
||||
})(),
|
||||
}),
|
||||
});
|
||||
if (!res) return;
|
||||
@@ -2170,9 +2197,10 @@ const Vault = (() => {
|
||||
<span class="em-vault-chevron">▸</span>
|
||||
</button>
|
||||
<div class="em-vault-item-body hidden" id="em-vault-body-${idx}">
|
||||
${hasFields
|
||||
? '<div class="detail-body em-detail-body"></div>'
|
||||
: '<p class="vault-empty">Could not decrypt this item.</p>'
|
||||
${
|
||||
hasFields
|
||||
? '<div class="detail-body em-detail-body"></div>'
|
||||
: '<p class="vault-empty">Could not decrypt this item.</p>'
|
||||
}
|
||||
</div>
|
||||
</li>`;
|
||||
@@ -2501,8 +2529,8 @@ const Vault = (() => {
|
||||
const typeLabel = transports.includes("internal")
|
||||
? "📱 Device"
|
||||
: transports.some((t) => ["usb", "nfc", "ble", "smart-card"].includes(t))
|
||||
? "🔑 Security key"
|
||||
: "🔑 Passkey";
|
||||
? "🔑 Security key"
|
||||
: "🔑 Passkey";
|
||||
return `<div class="passkey-item" data-cred-id="${c.id}">
|
||||
<div class="passkey-info">
|
||||
<span class="passkey-name">${escHtml(c.name)}</span>
|
||||
@@ -2765,15 +2793,16 @@ const Vault = (() => {
|
||||
<h3 style="margin:0 0 8px;font-size:16px;color:#111827;">
|
||||
${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"}
|
||||
</h3>
|
||||
${isFirstTime
|
||||
? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
||||
${
|
||||
isFirstTime
|
||||
? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
||||
These codes let you sign in if you lose access to your authenticator app.
|
||||
<strong>Save them now — they will not be shown again.</strong>
|
||||
</p>`
|
||||
: `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
||||
: `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
||||
Your previous codes have been invalidated. Save these new codes securely.
|
||||
</p>`
|
||||
}
|
||||
}
|
||||
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:12px;display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px;">
|
||||
${codesHtml}
|
||||
</div>
|
||||
@@ -3282,23 +3311,29 @@ const Vault = (() => {
|
||||
const pool = !_activeFilter
|
||||
? _items
|
||||
: _activeFilter.type === "itemType"
|
||||
? _items.filter((i) => i.item_type === _activeFilter.value)
|
||||
: _activeFilter.type === "folder"
|
||||
? _items.filter((i) => i.folder_id === _activeFilter.value)
|
||||
: _activeFilter.type === "tag"
|
||||
? _items.filter((i) =>
|
||||
(i.plain?.tags || []).includes(_activeFilter.value),
|
||||
)
|
||||
: _items;
|
||||
? _items.filter((i) => i.item_type === _activeFilter.value)
|
||||
: _activeFilter.type === "folder"
|
||||
? _items.filter((i) => i.folder_id === _activeFilter.value)
|
||||
: _activeFilter.type === "tag"
|
||||
? _items.filter((i) =>
|
||||
(i.plain?.tags || []).includes(_activeFilter.value),
|
||||
)
|
||||
: _items;
|
||||
renderItemList(
|
||||
pool.filter(
|
||||
(item) =>
|
||||
pool.filter((item) => {
|
||||
const p = item.plain || {};
|
||||
return (
|
||||
item.name.toLowerCase().includes(q) ||
|
||||
(item.plain?.username || "").toLowerCase().includes(q) ||
|
||||
(item.plain?.url || "").toLowerCase().includes(q) ||
|
||||
(item.plain?.note_body || "").toLowerCase().includes(q) ||
|
||||
(item.plain?.rp_id || "").toLowerCase().includes(q),
|
||||
),
|
||||
(p.username || "").toLowerCase().includes(q) ||
|
||||
(p.url || "").toLowerCase().includes(q) ||
|
||||
(p.note_body || "").toLowerCase().includes(q) ||
|
||||
(p.rp_id || "").toLowerCase().includes(q) ||
|
||||
(p.notes || "").toLowerCase().includes(q) ||
|
||||
(p.cardholder_name || "").toLowerCase().includes(q) ||
|
||||
(p.email || "").toLowerCase().includes(q) ||
|
||||
(p.tags || []).some((t) => t.toLowerCase().includes(q))
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3440,7 +3475,7 @@ const Vault = (() => {
|
||||
if (
|
||||
(mode === "add" &&
|
||||
(document.getElementById("field-type").value || "password") ===
|
||||
"password") ||
|
||||
"password") ||
|
||||
(mode === "edit" && (item?.item_type || "password") === "password")
|
||||
) {
|
||||
initPasswordFieldEnhancements();
|
||||
@@ -3697,7 +3732,7 @@ const Vault = (() => {
|
||||
"X-CSRFToken": csrfToken(),
|
||||
},
|
||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||
}).catch(() => { });
|
||||
}).catch(() => {});
|
||||
VaultSession.clear();
|
||||
SharingSession.clear();
|
||||
sessionStorage.removeItem("access_token");
|
||||
@@ -3802,11 +3837,11 @@ const Vault = (() => {
|
||||
// Auto-clear clipboard after 30 seconds — industry-standard hygiene.
|
||||
if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer);
|
||||
_clipboardClearTimer = setTimeout(() => {
|
||||
navigator.clipboard.writeText("").catch(() => { });
|
||||
navigator.clipboard.writeText("").catch(() => {});
|
||||
_clipboardClearTimer = null;
|
||||
}, 30_000);
|
||||
})
|
||||
.catch(() => { });
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function escHtml(str) {
|
||||
@@ -4425,4 +4460,4 @@ const Vault = (() => {
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||
|
||||
Reference in New Issue
Block a user