Updates to make Journals more secure

This commit is contained in:
Scott Idem
2025-05-02 13:01:49 -04:00
parent 8f515e034b
commit 0b61596833
4 changed files with 134 additions and 190 deletions

View File

@@ -71,6 +71,7 @@ if (browser) {
</svelte:head>
{#if $ae_loc.user_id && $ae_loc.person_id && $ae_loc.trusted_access}
<!-- These are needed: h-full overflow-auto -->
<div class="ae_journals h-full max-h-full overflow-auto flex flex-col gap-1">
@@ -192,3 +193,11 @@ if (browser) {
</section>
</div>
{:else}
<section class="main_content flex-grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">
<p class="text-center">
You are not logged in as a user. You must be signed in to access the journals module.
</p>
</section>
{/if}

View File

@@ -155,6 +155,7 @@ if (browser) {
</svelte:head>
{#if $ae_loc.person_id == $lq__journal_obj?.person_id}
<!-- Svelte Page for a Journal ID page -->
<!-- <section
class="
@@ -178,7 +179,9 @@ if (browser) {
lq__journal_entry_obj_li={lq__journal_entry_obj_li}
/>
{:else}
<section class="main_content flex-grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">
<p>No journal entry available to show.</p>
</section>
{/if}
@@ -230,3 +233,11 @@ if (browser) {
{/snippet}
</Modal>
{:else}
<section class="main_content flex-grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">
<p class="text-center">
You must be logged in as the owner to view this Journal.
</p>
</section>
{/if}

View File

@@ -86,6 +86,7 @@ let lq__journal_entry_obj = $derived(liveQuery(async () => {
</svelte:head> -->
{#if $ae_loc.person_id == $lq__journal_obj?.person_id}
<section
class="
ae_journals__journal_entry
@@ -111,6 +112,7 @@ let lq__journal_entry_obj = $derived(liveQuery(async () => {
Back to Journal Entries
</a> -->
{#if $lq__journal_entry_obj}
<Journal_entry_view
lq__journal_obj={lq__journal_obj}
@@ -121,123 +123,12 @@ let lq__journal_entry_obj = $derived(liveQuery(async () => {
</section>
{:else}
<!-- Modal: Journal edit ID -->
<Modal
title="{$lq__journal_entry_obj?.name} - {$lq__journal_entry_obj?.id}"
bind:open={$journals_sess.show__modal_edit__journal_entry_id}
autoclose={false}
placement="top-center"
size="xl"
class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative flex flex-col mx-auto w-full divide-y"
>
<section class="main_content flex-grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">
<p class="text-center">
You must be logged in as the owner to view this Journal Entry.
</p>
</section>
{#snippet header()}
<div class="flex flex-row items-center justify-between w-full">
<h3 class="text-lg font-semibold">
{#if $ae_loc.trusted_access}
<!-- <div class="ae_options"> -->
<button
onclick={() => {
// const url = new URL(location);
// url.searchParams.set('event_id', $lq__journal_entry_obj?.event_id_random);
// history.pushState({}, '', url);
$journals_sess.show__modal_view__journal_entry_id = $journals_slct.journal_entry_id;
$journals_sess.show__modal_edit__journal_entry_id = false;
}}
class="novi_btn btn btn-sm variant-ghost-warning hover:variant-filled-warning transition"
title={`View meeting: ${$lq__journal_entry_obj?.name}`}
>
<span class="fas fa-eye m-1"></span> View
</button>
<!-- </div> -->
{/if}
<span class="text-sm text-gray-500">
Edit Journal Entry:
</span>
{$lq__journal_entry_obj?.name}
</h3>
</div>
{/snippet}
<!-- <Journal_obj_id_edit
lq__journal_entry_obj={lq__journal_entry_obj}
/> -->
<!-- <svelte:fragment slot="footer">
<div class="text-center w-full">
<button
type="button"
on:click={() => {
console.log('Close modal');
$journals_sess.recovery_meetings.show__modal_edit__journal_entry_id = false;
}}
class="btn btn-sm variant-soft-warning hover:variant-ghost-warning"
>
<span class="fas fa-times mx-1"></span>
Close
</button>
</div>
</svelte:fragment> -->
</Modal>
<!-- Modal: Journal Content edit ID -->
<!-- <Modal
bind:open={$journals_sess.show__modal_edit__journal_entry_id}
title="{$lq__journal_entry_obj?.name ?? 'New Journal Content'} - {$lq__journal_entry_obj?.id ?? 'Not Saved Yet'}"
autoclose={false}
placement="top-center"
size="xl"
class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative flex flex-col mx-auto w-full divide-y"
>
{#snippet header()}
<div class="flex flex-row items-center justify-between w-full">
<h3 class="text-lg font-semibold">
{#if $ae_loc.trusted_access}
<button
onclick={() => {
// const url = new URL(location);
// url.searchParams.set('event_id', $lq__journal_entry_obj?.event_id_random);
// history.pushState({}, '', url);
$journals_sess.show__modal_view__journal_entry_id = $journals_slct.journal_entry_id;
$journals_sess.show__modal_edit__journal_entry_id = false;
}}
class="novi_btn btn btn-sm variant-ghost-warning hover:variant-filled-warning transition"
title={`View meeting: ${$lq__journal_entry_obj?.name}`}
>
<span class="fas fa-eye m-1"></span> View
</button>
{/if}
<span class="text-sm text-gray-500">
Edit Journal Content:
</span>
{$lq__journal_entry_obj?.name ?? 'New Journal Content'}
</h3>
</div>
{/snippet}
</Modal> -->
<!-- Modal: Journal Content ID media player -->
<!-- {#if $journals_slct.journal_entry_id && $journals_sess.show__modal_view__journal_entry_id}
<Modal_media_player
lq__journal_entry_obj={lq__journal_entry_obj}
/>
{/if} -->
{/if}

View File

@@ -10,7 +10,7 @@ import {
Eye, EyeOff,
Flag, FlagOff, FileX, Fingerprint,
Globe, Group,
LockKeyholeOpen,
LockKeyhole, LockKeyholeOpen,
MessageSquareWarning, Minus,
NotebookPen, NotebookText, NotepadTextDashed,
Pencil, PenLine, Plus,
@@ -192,9 +192,11 @@ async function update_journal_entry() {
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;
console.log('TEST: Decrypting the content before saving it...');
await handle_decrypt_content();
data_kv.content = decrypted_content; // tmp_entry_obj.content
decrypted_content = '';
// return false;
} else {
console.log('TEST: Saving content without encryption', tmp_entry_obj?.content);
// Clear content_encrypted again. Just in case.
@@ -307,39 +309,10 @@ $effect(() => {
$effect(async () => {
if (tmp_entry_obj?.content_encrypted && trigger_decrypt) {
trigger_decrypt = false;
log_lvl = 1;
let combined_data = tmp_entry_obj?.content_encrypted;
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)));
if (log_lvl) {
console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`);
}
handle_decrypt_content();
let decrypted: string|null = null;
try {
decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
} catch (error) {
console.error('Error decrypting content:', error);
alert('Failed to decrypt content. Please check the passcode.');
return;
}
// let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
// decrypted_content = 'XXX '+decrypted+' XXX';
if (!decrypted) {
alert('Failed to decrypt content. Please check the passcode.');
return;
}
decrypted_content = decrypted;
if (log_lvl) {
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;
}
// if (tmp_entry_obj?.content) {
@@ -359,6 +332,44 @@ $effect(async () => {
// console.log('IV:', iv);
});
async function handle_decrypt_content() {
log_lvl = 1;
if (log_lvl) {
console.log('TEST: handle_decrypt_content');
}
let combined_data = tmp_entry_obj?.content_encrypted;
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)));
if (log_lvl) {
console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_content}`);
}
let decrypted: string|null = null;
try {
decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
} catch (error) {
console.error('Error decrypting content:', error);
alert('Failed to decrypt content. Please check the passcode.');
return;
}
// let decrypted = await ae_util.decrypt_content(encrypted_base64_content, encryption_iv, journal_key);
// decrypted_content = 'XXX '+decrypted+' XXX';
if (!decrypted) {
alert('Failed to decrypt content. Please check the passcode.');
return;
}
decrypted_content = decrypted;
if (log_lvl) {
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;
}
</script>
@@ -697,63 +708,85 @@ $effect(async () => {
{/if}
</button>
<span class="flex flex-row flex-wrap gap-0.5 items-center justify-center">
<!-- Only show this private toggle if not private or in edit mode. -->
<button
type="button"
onclick={() => {
if ($ae_loc.edit_mode) {
if (tmp_entry_obj?.content_encrypted && tmp_entry_obj?.private) {
alert('You must decrypt the content before saving it!!');
// Allow for toggle between private (encrypted) and not,
// and if private, allow for decryption.
if (!$lq__journal_entry_obj?.private && tmp_entry_obj?.content) {
// Handle converting to private (encrypted) content
if (confirm('Are you sure you want to encrypt and resave this journal entry?')) {
// Setting private to true will cause the update_journal_entry() function to encrypt the content.
tmp_entry_obj.private = true;
update_journal_entry();
} else {
return false;
}
} else if ($lq__journal_entry_obj?.private && tmp_entry_obj?.content_encrypted) {
// Handle converting to not private (decrypted) content
if (confirm('Are you sure you want to decrypt and resave this journal entry unencrypted?')) {
// tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
// update_journal_entry();
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;
// Setting private to false will cause the update_journal_entry() function to decrypt the content. This will also copy it to tmp_entry_obj.content.
tmp_entry_obj.private = false;
// handle_decrypt_content();
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;
} else {
return false;
}
}
}}
ondblclick={() => {
// tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
// update_journal_entry();
}}
class:hidden={(tmp_entry_obj?.private && !$ae_loc.edit_mode)}
class="btn-icon-sm"
title="Toggle private visibility of this journal entry"
>
{#if $lq__journal_entry_obj?.private && decrypted_content}
<LockKeyholeOpen strokeWidth="2.5" color="red" />
{:else if $lq__journal_entry_obj?.private && $ae_loc.edit_mode}
<Fingerprint strokeWidth="2.5" color="red" />
{:else if $lq__journal_entry_obj?.private}
{#if $lq__journal_entry_obj?.private}
<Fingerprint strokeWidth="2.5" color="green" />
<!-- Private (Encrypted) -->
{:else}
<Fingerprint strokeWidth="1" color="gray" />
<!-- Not Private -->
{/if}
</button>
<!-- Only show this encryption toggle if entry is private. -->
<button
type="button"
onclick={() => {
if (tmp_entry_obj?.private && decrypted_content) {
decrypted_content = '';
// tmp_entry_obj.content = null;
} else if (tmp_entry_obj?.private && tmp_entry_obj?.content_encrypted) {
if (confirm('Are you sure you want to decrypt the content to view/edit?')) {
trigger_decrypt = true;
} else {
return false;
}
}
}}
class:hidden={!$lq__journal_entry_obj?.private}
class="btn-icon-sm"
title="Toggle viewing/editing of private encrypted content"
>
{#if $lq__journal_entry_obj?.private && decrypted_content}
<LockKeyholeOpen strokeWidth="2.5" color="red" />
{:else if $lq__journal_entry_obj?.private && tmp_entry_obj?.content_encrypted}
<LockKeyhole strokeWidth="2.5" color="green" />
<!-- <Fingerprint strokeWidth="2.5" color="green" /> -->
<!-- {:else if $lq__journal_entry_obj?.private}
<Fingerprint strokeWidth="2.5" color="green" />
{:else}
<Fingerprint strokeWidth="1" color="gray" /> -->
{/if}
</button>
</span>
<button
type="button"
onclick={() => {
@@ -1304,4 +1337,4 @@ $effect(async () => {
{/if}
</section>
</section>