Updates to make Journals more secure
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user