05/19 Enhance codes

This commit is contained in:
2026-05-19 09:31:33 -04:00
parent 5ede6326c8
commit f95461aee2
3 changed files with 151 additions and 99 deletions
+29 -16
View File
@@ -943,24 +943,22 @@ const Vault = (() => {
</div>`; </div>`;
sectionsEl.innerHTML = ""; sectionsEl.innerHTML = "";
const makeSection = (title, icon, items, desc) => { const makeSection = (title, icon, items, desc, renderItem) => {
if (!items.length) return; if (!items.length) return;
const sec = document.createElement("div"); const sec = document.createElement("div");
sec.className = "sec-section"; sec.className = "sec-section";
const defaultRender = (i) => `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(i.plain?.username || "")}</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Edit</button>
</li>`;
const renderFn = renderItem || defaultRender;
sec.innerHTML = ` sec.innerHTML = `
<div class="sec-section-header"><span class="sec-section-icon">${icon}</span> <div class="sec-section-header"><span class="sec-section-icon">${icon}</span>
<div><div class="sec-section-title">${title} (${items.length})</div><div class="sec-section-desc">${desc}</div></div> <div><div class="sec-section-title">${title} (${items.length})</div><div class="sec-section-desc">${desc}</div></div>
</div> </div>
<ul class="sec-item-list"> <ul class="sec-item-list">
${items ${items.map(renderFn).join("")}
.map(
(i) => `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(i.plain?.username || "")}</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Edit</button>
</li>`,
)
.join("")}
</ul>`; </ul>`;
sec.querySelectorAll("[data-sec-edit]").forEach((btn) => { sec.querySelectorAll("[data-sec-edit]").forEach((btn) => {
btn.addEventListener("click", () => { btn.addEventListener("click", () => {
@@ -995,6 +993,20 @@ const Vault = (() => {
"🕐", "🕐",
old, old,
"Weak or reused passwords not changed in over 180 days.", "Weak or reused passwords not changed in over 180 days.",
(i) => {
const ref = i.plain?.password_changed_at || i.created_at;
const daysAgo = ref
? Math.floor((Date.now() - new Date(ref).getTime()) / 86400000)
: null;
const ageLabel = daysAgo !== null
? `Last changed ${daysAgo} day${daysAgo !== 1 ? "s" : ""} ago`
: "Age unknown";
return `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(ageLabel)}</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Edit</button>
</li>`;
},
); );
// ── Missing 2FA warning ────────────────────────────────────────────────── // ── Missing 2FA warning ──────────────────────────────────────────────────
@@ -1423,6 +1435,9 @@ const Vault = (() => {
payload = _importRows; payload = _importRows;
} else { } else {
// Plaintext CSV rows — encrypt each one now. // Plaintext CSV rows — encrypt each one now.
// Use the import timestamp as password_changed_at — best available
// approximation since CSV exports don't carry a change date.
const importedAt = new Date().toISOString();
payload = await Promise.all( payload = await Promise.all(
_importRows.map(async (row) => { _importRows.map(async (row) => {
const plain = { const plain = {
@@ -1430,6 +1445,7 @@ const Vault = (() => {
username: row.username || "", username: row.username || "",
password: row.password, password: row.password,
notes: row.notes || "", notes: row.notes || "",
password_changed_at: importedAt,
}; };
const { enc_data, iv } = await Crypto.encryptItem( const { enc_data, iv } = await Crypto.encryptItem(
vaultKey, vaultKey,
@@ -1607,8 +1623,7 @@ const Vault = (() => {
<span class="share-name">${escHtml(s.item_name)}</span> <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)}</span>
</div> </div>
${ ${!s.accepted
!s.accepted
? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>` ? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>`
: `<button class="btn-secondary btn-sm" : `<button class="btn-secondary btn-sm"
data-view-share="${s.id}" data-view-share="${s.id}"
@@ -2155,8 +2170,7 @@ const Vault = (() => {
<span class="em-vault-chevron">▸</span> <span class="em-vault-chevron">▸</span>
</button> </button>
<div class="em-vault-item-body hidden" id="em-vault-body-${idx}"> <div class="em-vault-item-body hidden" id="em-vault-body-${idx}">
${ ${hasFields
hasFields
? '<div class="detail-body em-detail-body"></div>' ? '<div class="detail-body em-detail-body"></div>'
: '<p class="vault-empty">Could not decrypt this item.</p>' : '<p class="vault-empty">Could not decrypt this item.</p>'
} }
@@ -2751,8 +2765,7 @@ const Vault = (() => {
<h3 style="margin:0 0 8px;font-size:16px;color:#111827;"> <h3 style="margin:0 0 8px;font-size:16px;color:#111827;">
${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"} ${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"}
</h3> </h3>
${ ${isFirstTime
isFirstTime
? `<p style="font-size:13px;color:#374151;margin-bottom:12px;"> ? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
These codes let you sign in if you lose access to your authenticator app. 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> <strong>Save them now — they will not be shown again.</strong>
+16 -2
View File
@@ -932,6 +932,7 @@ body {
background: #e0f2fe; background: #e0f2fe;
color: #0369a1; color: #0369a1;
} }
/* ── Three-dot flyout menu ───────────────────────────────────────────────── */ /* ── Three-dot flyout menu ───────────────────────────────────────────────── */
.pk-flyout { .pk-flyout {
@@ -972,9 +973,20 @@ body {
color: #6b7280; color: #6b7280;
} }
.pk-flyout-info {
padding: 6px 12px 7px;
font-size: 11px;
color: #9ca3af;
border-top: 1px solid #f3f4f6;
margin-top: 2px;
user-select: none;
}
/* ── Collapsible folder groups ────────────────────────────────────────────── */ /* ── Collapsible folder groups ────────────────────────────────────────────── */
.pk-group { margin-bottom: 2px; } .pk-group {
margin-bottom: 2px;
}
.pk-group-header { .pk-group-header {
display: flex; display: flex;
@@ -988,7 +1000,9 @@ body {
transition: background 0.1s; transition: background 0.1s;
} }
.pk-group-header:hover { background: #f0f4ff; } .pk-group-header:hover {
background: #f0f4ff;
}
.pk-group-name { .pk-group-name {
font-size: 12px; font-size: 12px;
+25
View File
@@ -1131,6 +1131,26 @@ function renderList() {
: null, : null,
].filter(Boolean); ].filter(Boolean);
// Add a non-action info row showing when the password was last changed.
// Only shown for password items that have password_changed_at set.
if (item.item_type === "password" && item.plain?.password_changed_at) {
const daysAgo = Math.floor(
(Date.now() - new Date(item.plain.password_changed_at).getTime()) /
86400000,
);
const ageText =
daysAgo === 0
? "Changed today"
: daysAgo === 1
? "Changed yesterday"
: `Changed ${daysAgo} days ago`;
const infoRow = document.createElement("div");
infoRow.className = "pk-flyout-info";
infoRow.textContent = ageText;
// Append after action rows are added.
flyout._ageInfoRow = infoRow;
}
menuItems.forEach((mi) => { menuItems.forEach((mi) => {
const row = document.createElement("button"); const row = document.createElement("button");
row.className = "pk-flyout-item"; row.className = "pk-flyout-item";
@@ -1142,6 +1162,11 @@ function renderList() {
flyout.appendChild(row); flyout.appendChild(row);
}); });
// Append age info row if present.
if (flyout._ageInfoRow) {
flyout.appendChild(flyout._ageInfoRow);
}
// Position flyout using fixed coords so it escapes vault-list overflow clipping. // Position flyout using fixed coords so it escapes vault-list overflow clipping.
const appEl = document.getElementById("app"); const appEl = document.getElementById("app");
appEl.appendChild(flyout); appEl.appendChild(flyout);