From 5f8ce4b5debc4fba9db873b9ae82511965d14762 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Fri, 29 May 2026 14:27:46 -0400 Subject: [PATCH] 05/29 Update shared items displayed on Grantee items 4 --- app/static/js/vault.js | 62 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/app/static/js/vault.js b/app/static/js/vault.js index a910243..684d959 100644 --- a/app/static/js/vault.js +++ b/app/static/js/vault.js @@ -142,6 +142,29 @@ const Vault = (() => { "user_handle", ]); + // Fields that are internal metadata and should never be shown in the detail + // view or shared with recipients. + const INTERNAL_PLAIN_FIELDS = new Set([ + "password_history", + "password_changed_at", + "autofill", + "autologin", + "reprompt", + ]); + + /** + * Strip internal/owner-only fields from a plain object before encrypting it + * for a recipient. Recipients should only see credential data, not owner + * preferences or history. + */ + function prepareSharePlain(plain) { + const out = {}; + for (const [k, v] of Object.entries(plain)) { + if (!INTERNAL_PLAIN_FIELDS.has(k)) out[k] = v; + } + return out; + } + // ── API helpers ─────────────────────────────────────────────────────────── async function apiFetch(path, options = {}) { @@ -414,7 +437,7 @@ const Vault = (() => { SharingSession.getKey(), recipPub, ); - const { enc_data, iv } = await SharingCrypto.encryptForShare(sharedKey, plainData); + const { enc_data, iv } = await SharingCrypto.encryptForShare(sharedKey, prepareSharePlain(plainData)); const { enc_name, iv_name } = await SharingCrypto.encryptName(sharedKey, itemName); return { share_id: s.id, enc_data, iv, enc_name, iv_name }; }), @@ -720,11 +743,19 @@ const Vault = (() => { copyToClipboard(secret || "", "Copied to clipboard"); }); } + async function openFreshSharedDetail() { + // Re-fetch shared items so the grantee always sees the owner's latest + // version, not a stale in-memory snapshot. + await loadSharedItemsForVault(); + const fresh = _sharedItems.find((si) => si._shareId === item._shareId); + const target = fresh || item; + showSharedItemDetails(target.name, target.item_type, target.plain); + } li.querySelector('[data-action="view"]').addEventListener("click", (e) => { e.stopPropagation(); - showSharedItemDetails(item.name, item.item_type, item.plain); + openFreshSharedDetail(); }); - li.addEventListener("click", () => showSharedItemDetails(item.name, item.item_type, item.plain)); + li.addEventListener("click", () => openFreshSharedDetail()); return li; } @@ -2220,12 +2251,31 @@ const Vault = (() => { } function renderDetailFields(container, plain) { + const DATE_FIELDS = new Set(["password_changed_at"]); container.innerHTML = Object.entries(plain) - .filter(([, v]) => v) + .filter(([k, v]) => { + if (INTERNAL_PLAIN_FIELDS.has(k)) return false; // strip internal metadata + if (k === "tags") return Array.isArray(v) && v.length > 0; + return !!v; + }) .map(([k, v]) => { const label = FIELD_LABELS[k] || k.replace(/_/g, " "); - const val = String(v); + // Tags → badge chips, no copy button + if (k === "tags") { + const badges = (v || []) + .map((t) => `${escHtml(t)}`) + .join(" "); + return `
+ ${escHtml(label)} +
${badges}
+
`; + } + // Dates → human-readable + let val = String(v); + if (DATE_FIELDS.has(k)) { + try { val = new Date(v).toLocaleString(); } catch { /* keep raw */ } + } const sensitive = SENSITIVE_FIELDS.has(k); const valHtml = sensitive ? ` @@ -2313,7 +2363,7 @@ const Vault = (() => { const { enc_data, iv } = await SharingCrypto.encryptForShare( sharedKey, - item.plain, + prepareSharePlain(item.plain), ); // Encrypt the display name with the same ECDH shared key so the server