From b8e7ce75f7df6d25fe9bd8c97f99cd3e974913c3 Mon Sep 17 00:00:00 2001 From: hermes-explorigin Date: Thu, 27 Aug 2026 01:07:18 +0000 Subject: [PATCH] Make passwords optional for entries - validateEntry no longer requires encryptedPassword; only title is required (schema.js). encryptedPassword documented as optional. - EntryForm: decrypt-guards empty password on edit; stores empty string (no encryption) when the password field is blank; label is 'Password' (no *). - EntryDetail: only renders the Password field when the entry has one; decrypt guards empty. - Tests: schema validation updated (password optional); 143 total pass. --- dist/index.html | 101 ++++++++++++++++-------------- src/components/EntryDetail.svelte | 4 +- src/components/EntryForm.svelte | 6 +- src/lib/models/schema.js | 7 +-- tests/lib/models/schema.test.js | 16 +++-- 5 files changed, 75 insertions(+), 59 deletions(-) diff --git a/dist/index.html b/dist/index.html index 2daa46a..f0d324b 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5017,7 +5017,7 @@ function generateId() { * @property {string} id - Unique identifier * @property {string} title - Display name (e.g. "GitHub", "Gmail") * @property {string} [username] - Login username or email (optional) -* @property {string} encryptedPassword - AES-GCM encrypted password blob (JSON string) +* @property {string} [encryptedPassword] - AES-GCM encrypted password blob (JSON string); optional * @property {string} [url] - Website URL * @property {string} [notes] - Free-form notes * @property {string} [groupId] - Reference to a Group id (empty string = no group) @@ -5031,7 +5031,7 @@ function generateId() { * @param {Object} data * @param {string} data.title * @param {string} [data.username] -* @param {string} data.encryptedPassword - Must already be encrypted +* @param {string} [data.encryptedPassword] - Must already be encrypted (optional; empty string = no password) * @param {string} [data.url] * @param {string} [data.notes] * @param {string} [data.groupId] @@ -5116,7 +5116,6 @@ function createGroup(name, color) { function validateEntry(data) { const errors = []; if (!data.title || !data.title.trim()) errors.push("Title is required"); - if (!data.encryptedPassword) errors.push("Password is required"); return { valid: errors.length === 0, errors @@ -6542,11 +6541,12 @@ var root_4$3 = /* @__PURE__ */ from_html(`
â†Šī¸ Restore `, 1); var root_7$2 = /* @__PURE__ */ from_html(` `, 1); var root_8$2 = /* @__PURE__ */ from_html(`
Username
`); -var root_9$1 = /* @__PURE__ */ from_html(`
URL
`); -var root_10 = /* @__PURE__ */ from_html(`
Notes
`); -var root_11 = /* @__PURE__ */ from_html(``); -var root_12 = /* @__PURE__ */ from_html(``); -var root_5$3 = /* @__PURE__ */ from_html(`

Password
`, 1); +var root_9$1 = /* @__PURE__ */ from_html(`
Password
`); +var root_10 = /* @__PURE__ */ from_html(`
URL
`); +var root_11 = /* @__PURE__ */ from_html(`
Notes
`); +var root_12 = /* @__PURE__ */ from_html(``); +var root_13$1 = /* @__PURE__ */ from_html(``); +var root_5$3 = /* @__PURE__ */ from_html(`

`, 1); var root$4 = /* @__PURE__ */ from_html(`
`); function EntryDetail($$anchor, $$props) { push($$props, true); @@ -6566,7 +6566,7 @@ function EntryDetail($$anchor, $$props) { set(error, ""); try { set(entry, await getEntryById($$props.entryId), true); - if (get(entry) && app$1.encryptionKey) set(decryptedPassword, await decrypt(get(entry).encryptedPassword, app$1.encryptionKey), true); + if (get(entry) && app$1.encryptionKey) set(decryptedPassword, get(entry).encryptedPassword ? await decrypt(get(entry).encryptedPassword, app$1.encryptionKey) : "", true); } catch (e) { set(error, "Failed to load entry: " + e.message); } @@ -6698,20 +6698,33 @@ function EntryDetail($$anchor, $$props) { if_block(node_3, ($$render) => { if (get(entry).username) $$render(consequent_5); }); - var div_11 = sibling(node_3, 2); - var div_12 = sibling(child(div_11), 2); - var span_1 = child(div_12); - var text_5 = child(span_1, true); - reset(span_1); - var button_5 = sibling(span_1, 2); - var text_6 = child(button_5, true); - reset(button_5); - var button_6 = sibling(button_5, 2); - reset(div_12); - reset(div_11); - var node_4 = sibling(div_11, 2); + var node_4 = sibling(node_3, 2); var consequent_6 = ($$anchor) => { - var div_13 = root_9$1(); + var div_11 = root_9$1(); + var div_12 = sibling(child(div_11), 2); + var span_1 = child(div_12); + var text_5 = child(span_1, true); + reset(span_1); + var button_5 = sibling(span_1, 2); + var text_6 = child(button_5, true); + reset(button_5); + var button_6 = sibling(button_5, 2); + reset(div_12); + reset(div_11); + template_effect(() => { + set_text(text_5, get(passwordVisible) ? get(decryptedPassword) : "â€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸ"); + set_text(text_6, get(passwordVisible) ? "🙈" : "👁"); + }); + delegated("click", button_5, () => set(passwordVisible, !get(passwordVisible))); + delegated("click", button_6, () => copyToClipboard(get(decryptedPassword), "Password")); + append($$anchor, div_11); + }; + if_block(node_4, ($$render) => { + if (get(entry).encryptedPassword) $$render(consequent_6); + }); + var node_5 = sibling(node_4, 2); + var consequent_7 = ($$anchor) => { + var div_13 = root_10(); var div_14 = sibling(child(div_13), 2); var a = child(div_14); var text_7 = child(a, true); @@ -6726,12 +6739,12 @@ function EntryDetail($$anchor, $$props) { delegated("click", button_7, () => copyToClipboard(get(entry).url, "URL")); append($$anchor, div_13); }; - if_block(node_4, ($$render) => { - if (get(entry).url) $$render(consequent_6); + if_block(node_5, ($$render) => { + if (get(entry).url) $$render(consequent_7); }); - var node_5 = sibling(node_4, 2); - var consequent_7 = ($$anchor) => { - var div_15 = root_10(); + var node_6 = sibling(node_5, 2); + var consequent_8 = ($$anchor) => { + var div_15 = root_11(); var div_16 = sibling(child(div_15), 2); var text_8 = child(div_16, true); reset(div_16); @@ -6739,8 +6752,8 @@ function EntryDetail($$anchor, $$props) { template_effect(() => set_text(text_8, get(entry).notes)); append($$anchor, div_15); }; - if_block(node_5, ($$render) => { - if (get(entry).notes) $$render(consequent_7); + if_block(node_6, ($$render) => { + if (get(entry).notes) $$render(consequent_8); }); reset(div_8); var div_17 = sibling(div_8, 2); @@ -6752,9 +6765,9 @@ function EntryDetail($$anchor, $$props) { reset(span_3); reset(div_17); reset(div_5); - var node_6 = sibling(div_5, 2); - var consequent_8 = ($$anchor) => { - var div_18 = root_11(); + var node_7 = sibling(div_5, 2); + var consequent_9 = ($$anchor) => { + var div_18 = root_12(); var div_19 = child(div_18); var p = sibling(child(div_19), 2); var strong = sibling(child(p)); @@ -6781,12 +6794,12 @@ function EntryDetail($$anchor, $$props) { delegated("click", button_9, () => set(showDeleteConfirm, false)); append($$anchor, div_18); }; - if_block(node_6, ($$render) => { - if (get(showDeleteConfirm)) $$render(consequent_8); + if_block(node_7, ($$render) => { + if (get(showDeleteConfirm)) $$render(consequent_9); }); - var node_7 = sibling(node_6, 2); - var consequent_9 = ($$anchor) => { - var div_21 = root_12(); + var node_8 = sibling(node_7, 2); + var consequent_10 = ($$anchor) => { + var div_21 = root_13$1(); var div_22 = child(div_21); var p_1 = sibling(child(div_22), 2); var strong_1 = sibling(child(p_1)); @@ -6813,18 +6826,14 @@ function EntryDetail($$anchor, $$props) { delegated("click", button_11, () => set(showPermanentDeleteConfirm, false)); append($$anchor, div_21); }; - if_block(node_7, ($$render) => { - if (get(showPermanentDeleteConfirm)) $$render(consequent_9); + if_block(node_8, ($$render) => { + if (get(showPermanentDeleteConfirm)) $$render(consequent_10); }); template_effect(($0, $1) => { set_text(text_3, get(entry).title); - set_text(text_5, get(passwordVisible) ? get(decryptedPassword) : "â€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸâ€ĸ"); - set_text(text_6, get(passwordVisible) ? "🙈" : "👁"); set_text(text_9, `Created: ${$0 ?? ""}`); set_text(text_10, `Updated: ${$1 ?? ""}`); }, [() => new Date(get(entry).createdAt).toLocaleString(), () => new Date(get(entry).updatedAt).toLocaleString()]); - delegated("click", button_5, () => set(passwordVisible, !get(passwordVisible))); - delegated("click", button_6, () => copyToClipboard(get(decryptedPassword), "Password")); append($$anchor, fragment); }; if_block(node_1, ($$render) => { @@ -6845,7 +6854,7 @@ var root_3$2 = /* @__PURE__ */ from_html(`
`); var root_4$2 = /* @__PURE__ */ from_html(`
`); var root_7$1 = /* @__PURE__ */ from_html(``); -var root_2$2 = /* @__PURE__ */ from_html(`
`, 1); +var root_2$2 = /* @__PURE__ */ from_html(`
`, 1); var root$3 = /* @__PURE__ */ from_html(`
`); function EntryForm($$anchor, $$props) { push($$props, true); @@ -6872,7 +6881,7 @@ function EntryForm($$anchor, $$props) { if (entry) { set(title, entry.title, true); set(username, entry.username, true); - set(password, await decrypt(entry.encryptedPassword, app$1.encryptionKey), true); + set(password, entry.encryptedPassword ? await decrypt(entry.encryptedPassword, app$1.encryptionKey) : "", true); set(url, entry.url || "", true); set(notes, entry.notes || "", true); set(groupId, entry.groupId || "", true); @@ -6902,7 +6911,7 @@ function EntryForm($$anchor, $$props) { set(saving, false); return; } - const encryptedPassword = await encrypt(get(password), app$1.encryptionKey); + const encryptedPassword = get(password) ? await encrypt(get(password), app$1.encryptionKey) : ""; if (get(isEdit)) await updateEntry(updateEntry$1(await getEntryById($$props.entryId), { title: get(title), username: get(username), diff --git a/src/components/EntryDetail.svelte b/src/components/EntryDetail.svelte index 2209a60..65a17d1 100644 --- a/src/components/EntryDetail.svelte +++ b/src/components/EntryDetail.svelte @@ -24,7 +24,7 @@ try { entry = await getEntryById(entryId) if (entry && app.encryptionKey) { - decryptedPassword = await decrypt(entry.encryptedPassword, app.encryptionKey) + decryptedPassword = entry.encryptedPassword ? await decrypt(entry.encryptedPassword, app.encryptionKey) : '' } } catch (e) { error = 'Failed to load entry: ' + e.message @@ -130,6 +130,7 @@
{/if} + {#if entry.encryptedPassword}
Password
@@ -140,6 +141,7 @@
+ {/if} {#if entry.url}
diff --git a/src/components/EntryForm.svelte b/src/components/EntryForm.svelte index a226270..84852ac 100644 --- a/src/components/EntryForm.svelte +++ b/src/components/EntryForm.svelte @@ -33,7 +33,7 @@ if (entry) { title = entry.title username = entry.username - password = await decrypt(entry.encryptedPassword, app.encryptionKey) + password = entry.encryptedPassword ? await decrypt(entry.encryptedPassword, app.encryptionKey) : '' url = entry.url || '' notes = entry.notes || '' groupId = entry.groupId || '' @@ -66,7 +66,7 @@ return } - const encryptedPassword = await encrypt(password, app.encryptionKey) + const encryptedPassword = password ? await encrypt(password, app.encryptionKey) : '' if (isEdit) { const existing = await getEntryById(entryId) @@ -128,7 +128,7 @@
- +
{ expect(result.errors).toContain('Title is required') }) - it('should fail with missing encryptedPassword', () => { + it('should pass with no encryptedPassword (password optional)', () => { const result = validateEntry({ title: 'GitHub' }) - expect(result.valid).toBe(false) - expect(result.errors).toContain('Password is required') + expect(result.valid).toBe(true) + expect(result.errors).toEqual([]) }) - it('should report multiple errors', () => { + it('should pass with an empty password string', () => { + const result = validateEntry({ title: 'GitHub', encryptedPassword: '' }) + expect(result.valid).toBe(true) + }) + + it('should report only the title error when password is missing (not required)', () => { const result = validateEntry({ title: '' }) expect(result.valid).toBe(false) - expect(result.errors.length).toBe(2) + expect(result.errors.length).toBe(1) + expect(result.errors).toContain('Title is required') }) })