05/29 Update shared items displayed on Grantee items 4

This commit is contained in:
2026-05-29 14:27:46 -04:00
parent b614b63ead
commit 5f8ce4b5de
+56 -6
View File
@@ -142,6 +142,29 @@ const Vault = (() => {
"user_handle", "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 ─────────────────────────────────────────────────────────── // ── API helpers ───────────────────────────────────────────────────────────
async function apiFetch(path, options = {}) { async function apiFetch(path, options = {}) {
@@ -414,7 +437,7 @@ const Vault = (() => {
SharingSession.getKey(), SharingSession.getKey(),
recipPub, 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); const { enc_name, iv_name } = await SharingCrypto.encryptName(sharedKey, itemName);
return { share_id: s.id, enc_data, iv, enc_name, iv_name }; return { share_id: s.id, enc_data, iv, enc_name, iv_name };
}), }),
@@ -720,11 +743,19 @@ const Vault = (() => {
copyToClipboard(secret || "", "Copied to clipboard"); 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) => { li.querySelector('[data-action="view"]').addEventListener("click", (e) => {
e.stopPropagation(); 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; return li;
} }
@@ -2220,12 +2251,31 @@ const Vault = (() => {
} }
function renderDetailFields(container, plain) { function renderDetailFields(container, plain) {
const DATE_FIELDS = new Set(["password_changed_at"]);
container.innerHTML = container.innerHTML =
Object.entries(plain) 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]) => { .map(([k, v]) => {
const label = FIELD_LABELS[k] || k.replace(/_/g, " "); 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) => `<span class="item-tag">${escHtml(t)}</span>`)
.join(" ");
return `<div class="detail-field">
<span class="detail-label">${escHtml(label)}</span>
<div class="detail-val-row"><span class="detail-val">${badges}</span></div>
</div>`;
}
// 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 sensitive = SENSITIVE_FIELDS.has(k);
const valHtml = sensitive const valHtml = sensitive
? `<span class="detail-sensitive"> ? `<span class="detail-sensitive">
@@ -2313,7 +2363,7 @@ const Vault = (() => {
const { enc_data, iv } = await SharingCrypto.encryptForShare( const { enc_data, iv } = await SharingCrypto.encryptForShare(
sharedKey, sharedKey,
item.plain, prepareSharePlain(item.plain),
); );
// Encrypt the display name with the same ECDH shared key so the server // Encrypt the display name with the same ECDH shared key so the server