Now with actual encryption!

This commit is contained in:
Scott Idem
2025-04-28 17:18:29 -04:00
parent 8c3f05a2ed
commit 12167a3bc6
2 changed files with 198 additions and 80 deletions

View File

@@ -342,10 +342,20 @@ export async function db_save_ae_obj_li__journal_entry(
let content = obj.content ?? ''; let content = obj.content ?? '';
// remove the most common zerowidth characters from the start of the file // remove the most common zerowidth characters from the start of the file
let content_cleaned: string = content.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,""); let content_cleaned: null|string = null;
let content_md_html: null|string = await marked.parse(content_cleaned ?? '') ?? null; let content_md_html: null|string = null; // await marked.parse(content_cleaned ?? '') ?? null;
// let content_md_html_alt: null|string = await marked.parse(content_cleaned ?? '', { gfm: false }) ?? null; // let content_md_html_alt: null|string = await marked.parse(content_cleaned ?? '', { gfm: false }) ?? null;
if (obj.content_encrypted) {
// In theory "content" should be null if "content_encrypted" has a value.
content = null; // obj.content_encrypted;
content_cleaned = null;
content_md_html = null;
} else {
content_cleaned = content.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/,"");
content_md_html = await marked.parse(content_cleaned ?? '') ?? null;
}
let obj_record = { let obj_record = {
id: obj.journal_entry_id_random, id: obj.journal_entry_id_random,
journal_entry_id: obj.journal_entry_id_random, journal_entry_id: obj.journal_entry_id_random,

View File

@@ -12,7 +12,7 @@ import {
Globe, Group, Globe, Group,
MessageSquareWarning, Minus, MessageSquareWarning, Minus,
NotebookPen, NotebookText, NotepadTextDashed, NotebookPen, NotebookText, NotepadTextDashed,
Pencil, Plus, Pencil, PenLine, Plus,
RemoveFormatting, RemoveFormatting,
Search, Search,
Shapes, Share2, ShieldCheck, ShieldMinus, Siren, Skull, Shapes, Share2, ShieldCheck, ShieldMinus, Siren, Skull,
@@ -127,7 +127,9 @@ $effect(() => {
console.log('TEST: tmp_entry_obj and orig_entry_obj available; marking tmp_entry_obj as changed'); console.log('TEST: tmp_entry_obj and orig_entry_obj available; marking tmp_entry_obj as changed');
// tmp_entry_obj_changed = JSON.stringify(tmp_entry_obj) != JSON.stringify($lq__journal_entry_obj); // tmp_entry_obj_changed = JSON.stringify(tmp_entry_obj) != JSON.stringify($lq__journal_entry_obj);
// tmp_entry_obj_changed = JSON.stringify(tmp_entry_obj) != JSON.stringify(orig_entry_obj); // tmp_entry_obj_changed = JSON.stringify(tmp_entry_obj) != JSON.stringify(orig_entry_obj);
tmp_entry_obj_changed = true; // if (!decrypted_content) {
tmp_entry_obj_changed = true;
// }
} else { } else {
console.log('TEST: tmp_entry_obj == orig_entry_obj'); console.log('TEST: tmp_entry_obj == orig_entry_obj');
tmp_entry_obj_changed = false; tmp_entry_obj_changed = false;
@@ -158,6 +160,7 @@ async function update_journal_entry() {
alert_msg: tmp_entry_obj?.alert_msg, alert_msg: tmp_entry_obj?.alert_msg,
category_code: tmp_entry_obj?.category_code, category_code: tmp_entry_obj?.category_code,
content: tmp_entry_obj?.content, content: tmp_entry_obj?.content,
content_encrypted: null, // This should only be generated below.
group: tmp_entry_obj?.group, group: tmp_entry_obj?.group,
archive_on: tmp_entry_obj?.archive_on, archive_on: tmp_entry_obj?.archive_on,
name: tmp_entry_obj?.name, name: tmp_entry_obj?.name,
@@ -165,7 +168,8 @@ async function update_journal_entry() {
}; };
if (tmp_entry_obj?.content && tmp_entry_obj?.private) { if (tmp_entry_obj?.content && tmp_entry_obj?.private) {
content = $lq__journal_entry_obj?.content; console.log('TEST: Saving encrypted content', tmp_entry_obj?.content);
content = tmp_entry_obj?.content;
// Encrypt the content // Encrypt the content
let encrypted_base64 = await ae_util.encrypt_content(content, test_key); let encrypted_base64 = await ae_util.encrypt_content(content, test_key);
@@ -177,6 +181,24 @@ async function update_journal_entry() {
const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_content; const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_content;
data_kv.content_encrypted = combined_data; data_kv.content_encrypted = combined_data;
data_kv.content = null;
decrypted_content = '';
} else if (decrypted_content && !tmp_entry_obj?.private) {
console.log('TEST: Saving decrypted content', decrypted_content);
content = decrypted_content;
data_kv.content = content;
decrypted_content = '';
} else if (tmp_entry_obj?.content_encrypted && !tmp_entry_obj?.private) {
// alert('You must decrypt the content before saving it.');
console.log('TEST: You must decrypt the content before saving it.');
return false;
} else {
console.log('TEST: Saving content without encryption', tmp_entry_obj?.content);
// Clear content_encrypted again. Just in case.
// data_kv.content_encrypted = null;
decrypted_content = '';
// tmp_entry_obj.content_encrypted = null;
} }
// Call API to save the content // Call API to save the content
@@ -252,14 +274,14 @@ async function change_journal_id() {
// return decodedContent; // return decodedContent;
// } // }
// Example usage // // Example usage
(async () => { // (async () => {
const keyData = "my-secret-key"; // const keyData = "my-secret-key";
const content = "This is my test content to encrypt and decrypt."; // const content = "This is my test content to encrypt and decrypt.";
const { base64, iv } = await ae_util.encrypt_content(content, keyData); // const { base64, iv } = await ae_util.encrypt_content(content, keyData);
await ae_util.decrypt_content(base64, iv, keyData); // await ae_util.decrypt_content(base64, iv, keyData);
})(); // })();
const test_key = "my-secret-key"; const test_key = "my-secret-key";
let content = "This is my test content to encrypt and decrypt."; let content = "This is my test content to encrypt and decrypt.";
@@ -270,9 +292,9 @@ let trigger_decrypt: boolean = $state(false);
$effect(async () => { $effect(async () => {
if (tmp_entry_obj?.content_encrypted && trigger_decrypt) { if (tmp_entry_obj?.content_encrypted && trigger_decrypt) {
trigger_decrypt = false;
log_lvl = 1; log_lvl = 1;
trigger_decrypt = false;
let combined_data = tmp_entry_obj?.content_encrypted; let combined_data = tmp_entry_obj?.content_encrypted;
let [encryption_iv_hex, encrypted_base64_content] = combined_data.split(':'); let [encryption_iv_hex, encrypted_base64_content] = combined_data.split(':');
encryption_iv = new Uint8Array(encryption_iv_hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); encryption_iv = new Uint8Array(encryption_iv_hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
@@ -281,10 +303,14 @@ $effect(async () => {
} }
let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, test_key); let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, test_key);
// decrypted_content = 'XXX '+decrypted+' XXX';
decrypted_content = decrypted; decrypted_content = decrypted;
if (log_lvl) { if (log_lvl) {
console.log('Decrypted content:', decrypted_content); console.log('Decrypted content:', decrypted_content);
} }
tmp_entry_obj.content = decrypted_content;
// orig_entry_obj.content = decrypted_content;
// tmp_entry_obj_changed = false;
// tmp_entry_obj.content_encrypted = null; // tmp_entry_obj.content_encrypted = null;
} }
@@ -332,12 +358,17 @@ $effect(async () => {
} else { } else {
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = true; $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = true;
} }
// if ($ae_loc.trusted_access && !$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]) {
// trigger_decrypt = true;
// }
}} }}
class="btn btn-icon-sm inline-block" class="btn btn-icon-sm inline-block"
title="Toggle edit mode for this journal entry" title="Toggle edit mode for this journal entry"
> >
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]} {#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]}
<Pencil strokeWidth="2.5" color="blue" /> <!-- <Pencil strokeWidth="2.5" color="blue" /> -->
<PenLine strokeWidth="2.5" color="blue" />
{:else} {:else}
<Pencil strokeWidth="1" color="gray" /> <Pencil strokeWidth="1" color="gray" />
{/if} {/if}
@@ -643,13 +674,50 @@ $effect(async () => {
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
tmp_entry_obj.private = !$lq__journal_entry_obj?.private; if ($ae_loc.edit_mode) {
update_journal_entry(); if (tmp_entry_obj?.content_encrypted && tmp_entry_obj?.private) {
alert('You must decrypt the content before saving it!!');
return false;
}
if (tmp_entry_obj.private) {
if (confirm('Not private and decrypt?')) {
tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
// } else {
update_journal_entry();
// }
// update_journal_entry();
}
} else {
tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
update_journal_entry();
}
} else {
if (tmp_entry_obj?.content_encrypted && !decrypted_content) {
trigger_decrypt = true;
// } else if (tmp_entry_obj?.content_encrypted && decrypted_content && tmp_entry_obj_changed) {
// // This is ok because it has not really changed?
// // decrypted_content = '';
// alert('This journal entry has changed.');
} else if (tmp_entry_obj?.content_encrypted && decrypted_content) {
decrypted_content = '';
tmp_entry_obj.content = null;
}
}
}}
ondblclick={() => {
// tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
// update_journal_entry();
}} }}
class="btn-icon-sm" class="btn-icon-sm"
title="Toggle private visibility of this journal entry" title="Toggle private visibility of this journal entry"
> >
{#if $lq__journal_entry_obj?.private} {#if $lq__journal_entry_obj?.private && decrypted_content}
<Fingerprint strokeWidth="2.5" color="red" />
{:else if $lq__journal_entry_obj?.private}
<Fingerprint strokeWidth="2.5" color="green" /> <Fingerprint strokeWidth="2.5" color="green" />
{:else} {:else}
<Fingerprint strokeWidth="1" color="gray" /> <Fingerprint strokeWidth="1" color="gray" />
@@ -757,6 +825,7 @@ $effect(async () => {
</div> </div>
{#if (!$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id])}
<!-- svelte-ignore a11y_no_noninteractive_tabindex --> <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<div <div
ondblclick={() => { ondblclick={() => {
@@ -765,15 +834,10 @@ $effect(async () => {
$journals_loc.entry.edit = !$journals_loc.entry.edit; $journals_loc.entry.edit = !$journals_loc.entry.edit;
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = $journals_loc.entry.edit; $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = $journals_loc.entry.edit;
} }
if ($ae_loc.trusted_access && $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]) {
trigger_decrypt = true;
}
}} }}
role={$ae_loc.edit_mode ? 'button' : 'article'} role={$ae_loc.edit_mode ? 'button' : 'article'}
tabindex={$ae_loc.edit_mode ? 0 : -1} tabindex={$ae_loc.edit_mode ? 0 : -1}
class:hidden={!$ae_loc.trusted_access || $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]}
class=" class="
flex-grow flex-grow
flex flex-col items-center justify-center flex flex-col items-center justify-center
@@ -805,7 +869,15 @@ $effect(async () => {
" "
id="rendered_journal_entry_content_{$lq__journal_entry_obj?.journal_entry_id}" id="rendered_journal_entry_content_{$lq__journal_entry_obj?.journal_entry_id}"
> >
{@html decrypted_content ?? $lq__journal_entry_obj?.content_md_html} {#if decrypted_content?.length}
{@html decrypted_content}
{:else if $lq__journal_entry_obj?.content_encrypted}
<span class="text-sm text-gray-500 break-all">
{@html $lq__journal_entry_obj?.content_encrypted}
</span>
{:else}
{@html $lq__journal_entry_obj?.content_md_html}
{/if}
</article> </article>
<!-- {@html async () => { <!-- {@html async () => {
const { base64, iv } = await encrypt_content(content, test_key); const { base64, iv } = await encrypt_content(content, test_key);
@@ -820,69 +892,105 @@ $effect(async () => {
<!-- --{@html encrypted_base64_content}-- --> <!-- --{@html encrypted_base64_content}-- -->
<!-- {@html marked.parse($lq__journal_entry_obj?.content)} --> <!-- {@html marked.parse($lq__journal_entry_obj?.content)} -->
</div> </div>
{:else if ($journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id])}
<!-- && !($lq__journal_entry_obj?.content_encrypted && decrypted_content)) -->
<!-- class="flex flex-row flex-wrap gap-1 items-center justify-center w-full max-w-sm" -->
{#if ($lq__journal_entry_obj?.content_encrypted && !decrypted_content)}
<div
class="
flex-grow min-h-48 h-full w-full
p-2
bg-red-100 text-gray-900
dark:bg-red-900 dark:text-gray-100
shadow-lg rounded-lg
border border-red-200 dark:border-red-700
hover:border-red-500 dark:hover:border-red-500
"
>
The entry must be decrypted before it can be edited.
<!-- <span class="text-sm text-gray-500">
<Lock key={0} strokeWidth="2.5" color="red" />
<span class="hidden sm:inline">Encrypted</span>
</span>
<button
type="button"
onclick={() => {
if ($ae_loc.trusted_access) {
trigger_decrypt = true;
}
}}
class="btn btn-sm variant-soft-secondary hover:variant-filled-secondary *:hover:inline lg:text-xs"
title="Decrypt the content of this journal entry"
>
<LockOpen key={1} strokeWidth="2.5" color="green" />
<span class="hidden sm:inline">Decrypt</span>
</button> -->
</div>
{:else}
<div
class="
flex-grow flex flex-col items-center justify-center
w-full max-w-6xl
"
>
<div <textarea
class:hidden={!$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] && $lq__journal_entry_obj?.content?.length} bind:value={tmp_entry_obj.content}
class=" ondblclick={() => {
flex-grow flex flex-col items-center justify-center if ($ae_loc.trusted_access && $ae_loc.edit_mode) {
w-full max-w-6xl // Toggle edit mode
" $journals_loc.entry.edit = !$journals_loc.entry.edit;
> $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = $journals_loc.entry.edit;
}
<textarea }}
bind:value={tmp_entry_obj.content} disabled={tmp_entry_obj.content_encrypted && !decrypted_content}
ondblclick={() => { class:border-orange-200={$ae_loc.edit_mode}
if ($ae_loc.trusted_access && $ae_loc.edit_mode) { class:hover:border-orange-500={$ae_loc.edit_mode}
// Toggle edit mode class="
$journals_loc.entry.edit = !$journals_loc.entry.edit; flex-grow min-h-48 h-full w-full
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = $journals_loc.entry.edit; p-2
} bg-slate-100 text-gray-900
}} dark:bg-slate-900 dark:text-gray-100
class:border-orange-200={$ae_loc.edit_mode} shadow-lg rounded-lg
class:hover:border-orange-500={$ae_loc.edit_mode} border border-gray-200 dark:border-gray-700
class=" hover:border-gray-500 dark:hover:border-gray-500
flex-grow min-h-48 h-full w-full "
p-2
bg-slate-100 text-gray-900
dark:bg-slate-900 dark:text-gray-100
shadow-lg rounded-lg
border border-gray-200 dark:border-gray-700
hover:border-gray-500 dark:hover:border-gray-500
"
placeholder="Edit journal entry content here..." placeholder="Edit journal entry content here..."
></textarea> ></textarea>
<!-- Only enable editing if the user has trusted access --> <!-- Only enable editing if the user has trusted access -->
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
update_journal_entry(); update_journal_entry();
}} }}
disabled={!tmp_entry_obj_changed} disabled={!tmp_entry_obj_changed}
class:variant-filled-error={tmp_entry_obj_changed} class:variant-filled-error={tmp_entry_obj_changed}
class=" class="
btn btn-sm md:btn-md lg:btn-lg btn btn-sm md:btn-md lg:btn-lg
min-w-72 w-full lg:min-w-96 min-w-72 w-full lg:min-w-96
max-w-96 max-w-96
hover:variant-outline-success hover:variant-outline-success
hover:variant-filled-success hover:variant-filled-success
" "
> >
Save Changes? Save Changes?
</button> </button>
<!-- Do a quick check to see if the updated_on timestamp has changed. Specifically, if the entry was updated since the last time it was loaded. --> <!-- Do a quick check to see if the updated_on timestamp has changed. Specifically, if the entry was updated since the last time it was loaded. -->
{#if updated_idb} {#if updated_idb}
<span class="text-sm text-red-500"> <span class="text-sm text-red-500">
WARNING: IDB object has been updated since last load. WARNING: IDB object has been updated since last load.
</span> </span>
{/if}
<!-- Updated: {orig_entry_obj?.updated_on}
Updated obj? {updated_obj} -->
<!-- && $lq__journal_entry_obj?.updated_on !== orig_entry_obj?.updated_on -->
</div>
{/if} {/if}
<!-- Updated: {orig_entry_obj?.updated_on} {/if}
Updated obj? {updated_obj} -->
<!-- && $lq__journal_entry_obj?.updated_on !== orig_entry_obj?.updated_on -->
</div>
<!-- <div> <!-- <div>
{@html test_html} {@html test_html}