Updates to make Journals more secure
This commit is contained in:
@@ -71,6 +71,7 @@ if (browser) {
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
|
||||||
|
{#if $ae_loc.user_id && $ae_loc.person_id && $ae_loc.trusted_access}
|
||||||
<!-- These are needed: h-full overflow-auto -->
|
<!-- These are needed: h-full overflow-auto -->
|
||||||
<div class="ae_journals h-full max-h-full overflow-auto flex flex-col gap-1">
|
<div class="ae_journals h-full max-h-full overflow-auto flex flex-col gap-1">
|
||||||
|
|
||||||
@@ -192,3 +193,11 @@ if (browser) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</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}
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ if (browser) {
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
|
||||||
|
{#if $ae_loc.person_id == $lq__journal_obj?.person_id}
|
||||||
<!-- Svelte Page for a Journal ID page -->
|
<!-- Svelte Page for a Journal ID page -->
|
||||||
<!-- <section
|
<!-- <section
|
||||||
class="
|
class="
|
||||||
@@ -178,7 +179,9 @@ if (browser) {
|
|||||||
lq__journal_entry_obj_li={lq__journal_entry_obj_li}
|
lq__journal_entry_obj_li={lq__journal_entry_obj_li}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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>
|
<p>No journal entry available to show.</p>
|
||||||
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
@@ -230,3 +233,11 @@ if (browser) {
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
</Modal>
|
</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}
|
||||||
@@ -86,6 +86,7 @@ let lq__journal_entry_obj = $derived(liveQuery(async () => {
|
|||||||
</svelte:head> -->
|
</svelte:head> -->
|
||||||
|
|
||||||
|
|
||||||
|
{#if $ae_loc.person_id == $lq__journal_obj?.person_id}
|
||||||
<section
|
<section
|
||||||
class="
|
class="
|
||||||
ae_journals__journal_entry
|
ae_journals__journal_entry
|
||||||
@@ -111,6 +112,7 @@ let lq__journal_entry_obj = $derived(liveQuery(async () => {
|
|||||||
Back to Journal Entries
|
Back to Journal Entries
|
||||||
</a> -->
|
</a> -->
|
||||||
|
|
||||||
|
|
||||||
{#if $lq__journal_entry_obj}
|
{#if $lq__journal_entry_obj}
|
||||||
<Journal_entry_view
|
<Journal_entry_view
|
||||||
lq__journal_obj={lq__journal_obj}
|
lq__journal_obj={lq__journal_obj}
|
||||||
@@ -121,123 +123,12 @@ let lq__journal_entry_obj = $derived(liveQuery(async () => {
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{:else}
|
||||||
|
|
||||||
<!-- Modal: Journal edit ID -->
|
<section class="main_content flex-grow px-1 md:px-2 pb-28 flex flex-col gap-1 items-center">
|
||||||
<Modal
|
<p class="text-center">
|
||||||
title="{$lq__journal_entry_obj?.name} - {$lq__journal_entry_obj?.id}"
|
You must be logged in as the owner to view this Journal Entry.
|
||||||
bind:open={$journals_sess.show__modal_edit__journal_entry_id}
|
</p>
|
||||||
autoclose={false}
|
</section>
|
||||||
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()}
|
{/if}
|
||||||
|
|
||||||
<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} -->
|
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Eye, EyeOff,
|
Eye, EyeOff,
|
||||||
Flag, FlagOff, FileX, Fingerprint,
|
Flag, FlagOff, FileX, Fingerprint,
|
||||||
Globe, Group,
|
Globe, Group,
|
||||||
LockKeyholeOpen,
|
LockKeyhole, LockKeyholeOpen,
|
||||||
MessageSquareWarning, Minus,
|
MessageSquareWarning, Minus,
|
||||||
NotebookPen, NotebookText, NotepadTextDashed,
|
NotebookPen, NotebookText, NotepadTextDashed,
|
||||||
Pencil, PenLine, Plus,
|
Pencil, PenLine, Plus,
|
||||||
@@ -192,9 +192,11 @@ async function update_journal_entry() {
|
|||||||
data_kv.content = content;
|
data_kv.content = content;
|
||||||
decrypted_content = '';
|
decrypted_content = '';
|
||||||
} else if (tmp_entry_obj?.content_encrypted && !tmp_entry_obj?.private) {
|
} else if (tmp_entry_obj?.content_encrypted && !tmp_entry_obj?.private) {
|
||||||
// alert('You must decrypt the content before saving it.');
|
console.log('TEST: Decrypting the content before saving it...');
|
||||||
console.log('TEST: You must decrypt the content before saving it.');
|
await handle_decrypt_content();
|
||||||
return false;
|
data_kv.content = decrypted_content; // tmp_entry_obj.content
|
||||||
|
decrypted_content = '';
|
||||||
|
// return false;
|
||||||
} else {
|
} else {
|
||||||
console.log('TEST: Saving content without encryption', tmp_entry_obj?.content);
|
console.log('TEST: Saving content without encryption', tmp_entry_obj?.content);
|
||||||
// Clear content_encrypted again. Just in case.
|
// Clear content_encrypted again. Just in case.
|
||||||
@@ -307,39 +309,10 @@ $effect(() => {
|
|||||||
$effect(async () => {
|
$effect(async () => {
|
||||||
if (tmp_entry_obj?.content_encrypted && trigger_decrypt) {
|
if (tmp_entry_obj?.content_encrypted && trigger_decrypt) {
|
||||||
trigger_decrypt = false;
|
trigger_decrypt = false;
|
||||||
log_lvl = 1;
|
|
||||||
|
|
||||||
let combined_data = tmp_entry_obj?.content_encrypted;
|
handle_decrypt_content();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (tmp_entry_obj?.content) {
|
// if (tmp_entry_obj?.content) {
|
||||||
@@ -359,6 +332,44 @@ $effect(async () => {
|
|||||||
// console.log('IV:', iv);
|
// 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>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@@ -697,63 +708,85 @@ $effect(async () => {
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if ($ae_loc.edit_mode) {
|
// Allow for toggle between private (encrypted) and not,
|
||||||
if (tmp_entry_obj?.content_encrypted && tmp_entry_obj?.private) {
|
// and if private, allow for decryption.
|
||||||
alert('You must decrypt the content before saving it!!');
|
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;
|
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) {
|
// 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.
|
||||||
if (confirm('Not private and decrypt?')) {
|
tmp_entry_obj.private = false;
|
||||||
tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
|
// handle_decrypt_content();
|
||||||
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
update_journal_entry();
|
|
||||||
// }
|
|
||||||
// update_journal_entry();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tmp_entry_obj.private = !$lq__journal_entry_obj?.private;
|
|
||||||
update_journal_entry();
|
update_journal_entry();
|
||||||
}
|
} else {
|
||||||
} else {
|
return false;
|
||||||
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={() => {
|
class:hidden={(tmp_entry_obj?.private && !$ae_loc.edit_mode)}
|
||||||
// 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 && decrypted_content}
|
{#if $lq__journal_entry_obj?.private}
|
||||||
<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}
|
|
||||||
<Fingerprint strokeWidth="2.5" color="green" />
|
<Fingerprint strokeWidth="2.5" color="green" />
|
||||||
|
<!-- Private (Encrypted) -->
|
||||||
{:else}
|
{:else}
|
||||||
<Fingerprint strokeWidth="1" color="gray" />
|
<Fingerprint strokeWidth="1" color="gray" />
|
||||||
|
<!-- Not Private -->
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</button>
|
</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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user