From 72fb34e3f16caa57b4d20cc2d19b3b9e138b11c1 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 15 May 2025 13:03:43 -0400 Subject: [PATCH] Work on passcodes and encryption --- src/lib/ae_journals/ae_journals__journal.ts | 12 ++ src/lib/ae_journals/ae_journals_stores.ts | 4 + src/lib/ae_journals/db_journals.ts | 4 + src/lib/ae_utils/ae_utils__crypto.ts | 24 +-- .../journals/[journal_id]/+layout.svelte | 4 +- .../ae_comp__journal_entry_obj_id_view.svelte | 75 ++++--- .../ae_comp__journal_obj_id_view.svelte | 200 +++++++++++++++++- 7 files changed, 273 insertions(+), 50 deletions(-) diff --git a/src/lib/ae_journals/ae_journals__journal.ts b/src/lib/ae_journals/ae_journals__journal.ts index dddc378a..b9cd3dd0 100644 --- a/src/lib/ae_journals/ae_journals__journal.ts +++ b/src/lib/ae_journals/ae_journals__journal.ts @@ -720,6 +720,8 @@ export async function db_save_ae_obj_li__journal( passcode: obj.passcode, // For Journal Entry encryption password passcode_timeout: obj.passcode_timeout, + private_passcode: obj.private_passcode, // Combine with Journal passcode to encrypt and decrypt Entries + auth_key: obj.auth_key, // For Journal authorization without sign in enable: obj.enable, @@ -738,6 +740,8 @@ export async function db_save_ae_obj_li__journal( // tmp_sort_1: `${obj.original_datetime}_${obj.group}_${obj.priority}_${obj.sort}`, // tmp_sort_2: `${obj.group}_${obj.original_datetime}_${obj.priority}_${obj.sort}`, + combined_passcode: `${obj.passcode}:${obj.private_passcode}`, // Combined Journal passcode and Journal private passcode to encrypt and decrypt Entries + // From SQL view journal_entry_count: obj.journal_entry_count, @@ -834,6 +838,8 @@ let properties_to_save = [ 'passcode', // For Journal Entry encryption password 'passcode_timeout', + 'private_passcode', // Combine with Journal passcode to encrypt and decrypt Entries + 'auth_key', // For Journal authorization without sign in 'enable', @@ -852,6 +858,8 @@ let properties_to_save = [ // tmp_sort_1: `${obj.original_datetime}_${obj.group}_${obj.priority}_${obj.sort}`, // tmp_sort_2: `${obj.group}_${obj.original_datetime}_${obj.priority}_${obj.sort}`, + 'combined_passcode', + // From SQL view 'journal_entry_count', @@ -945,6 +953,8 @@ export async function process_ae_obj__journal_props( passcode: obj.passcode, // For Journal Entry encryption password passcode_timeout: obj.passcode_timeout, + private_passcode: obj.private_passcode, // Combine with Journal passcode to encrypt and decrypt Entries + auth_key: obj.auth_key, // For Journal authorization without sign in enable: obj.enable, @@ -963,6 +973,8 @@ export async function process_ae_obj__journal_props( // tmp_sort_1: `${obj.original_datetime}_${obj.group}_${obj.priority}_${obj.sort}`, // tmp_sort_2: `${obj.group}_${obj.original_datetime}_${obj.priority}_${obj.sort}`, + combined_passcode: `${obj.passcode}:${obj.private_passcode}`, // Combined Journal passcode and Journal private passcode to encrypt and decrypt Entries + // From SQL view journal_entry_count: obj.journal_entry_count, diff --git a/src/lib/ae_journals/ae_journals_stores.ts b/src/lib/ae_journals/ae_journals_stores.ts index d738b955..ae378d2f 100644 --- a/src/lib/ae_journals/ae_journals_stores.ts +++ b/src/lib/ae_journals/ae_journals_stores.ts @@ -94,6 +94,10 @@ let journals_session_data_struct: key_val = { tmp_obj: {}, }, + + journal_kv: { + // journal_id: {}, + }, }; // console.log(`AE Stores - App Journals Session Storage Data:`, journals_session_data_struct); export let journals_sess = writable(journals_session_data_struct); diff --git a/src/lib/ae_journals/db_journals.ts b/src/lib/ae_journals/db_journals.ts index 86dc55b5..3bf336a5 100644 --- a/src/lib/ae_journals/db_journals.ts +++ b/src/lib/ae_journals/db_journals.ts @@ -71,6 +71,8 @@ export interface Journal { passcode?: null|string; // For Journal Entry encryption password passcode_timeout?: null|number; // Timeout in seconds + private_passcode?: null|string; // Combine with the Journal passcode for Journal Entry encryption password + auth_key?: null|string; // For Journal authorization without sign in enable: null|boolean; @@ -89,6 +91,8 @@ export interface Journal { tmp_sort_2?: null|string; tmp_sort_3?: null|string; + combined_passcode?: null|string; // For Journal Entry encryption password + // Additional fields for convenience (database views) file_count?: null|number; // Only files directly under a journal journal_file_id_li_json?: null|string; diff --git a/src/lib/ae_utils/ae_utils__crypto.ts b/src/lib/ae_utils/ae_utils__crypto.ts index 34f63bf5..547b6be9 100644 --- a/src/lib/ae_utils/ae_utils__crypto.ts +++ b/src/lib/ae_utils/ae_utils__crypto.ts @@ -91,16 +91,10 @@ export let split_iv_and_base64 = function split_iv_and_base64( if (log_lvl) { console.log(`IV: ${iv}; Encrypted:`, base64); } - - // const [ivBase64, base64] = combined.split(':'); - // const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0)); - // if (log_lvl) { - // console.log(`IV: ${iv}; Encrypted: ${base64}`); - // } return { iv, base64 }; } -// Updated 2025-05-08 +// Updated 2025-05-15 export let decrypt_wrapper = async function decrypt_wrapper( combined: string, keyData: string @@ -110,11 +104,17 @@ export let decrypt_wrapper = async function decrypt_wrapper( return ''; } const { iv, base64 } = split_iv_and_base64(combined); - const decrypted = await decrypt_content(base64, iv, keyData); - if (log_lvl > 1) { - console.log(`IV: ${iv}; Decrypted:`, decrypted); - } else if (log_lvl) { - console.log(`IV: ${iv}`); + let decrypted; + try { + decrypted = await decrypt_content(base64, iv, keyData); + if (log_lvl > 1) { + console.log(`IV: ${iv}; Decrypted:`, decrypted); + } else if (log_lvl) { + console.log(`IV: ${iv}`); + } + } catch (error) { + console.error('Decryption failed:', error); + return ''; } return decrypted; } \ No newline at end of file diff --git a/src/routes/journals/[journal_id]/+layout.svelte b/src/routes/journals/[journal_id]/+layout.svelte index 3bcafd26..b7bd3bfd 100644 --- a/src/routes/journals/[journal_id]/+layout.svelte +++ b/src/routes/journals/[journal_id]/+layout.svelte @@ -195,7 +195,7 @@ async function handle_update_journal() { {:else} - + --> {/if} diff --git a/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte b/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte index 1ffd4eb0..b3eb2dfe 100644 --- a/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte +++ b/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte @@ -588,7 +588,7 @@ async function change_journal_id() { // let journal_key: string = $state( 'my-secret-key-111'); // let journal_key: string = $state($lq__journal_obj?.passcode); let journal_key = $derived(() => { - return $lq__journal_obj?.passcode || 'my-secret-key-111'; + return $lq__journal_obj?.passcode || 'temp-secret-key'; }); // console.log('TEST: journal_key', journal_key); let content = ''; // "This is my test content to encrypt and decrypt."; @@ -599,10 +599,19 @@ let trigger_decrypt: boolean = $state(false); let decrypted_history: string = $state(''); $effect(() => { - if ($lq__journal_obj?.passcode) { - console.log('TEST: lq__journal_obj?.passcode', $lq__journal_obj?.passcode); + if ($journals_sess.journal_kv[$lq__journal_obj?.id]?.journal_passcode_verified) { + journal_key = $lq__journal_obj?.combined_passcode; + } else if ($lq__journal_obj?.passcode) { journal_key = $lq__journal_obj?.passcode; + } else { + 'temp-secret-key'; } + console.log('TEST: journal_key', journal_key); + + // if ($lq__journal_obj?.passcode) { + // console.log('TEST: lq__journal_obj?.passcode', $lq__journal_obj?.passcode); + // journal_key = $lq__journal_obj?.passcode; + // } }); // $effect(async () => { @@ -713,39 +722,39 @@ async function handle_decrypt_string(encrypted_string: string, passcode: string) // return decrypted_string; } -async function handle_encrypt_string(text_string: string, passcode: string) { - // log_lvl = 1; - if (log_lvl) { - console.log('TEST: handle_encrypt_string'); - } - if (!text_string) { - console.log('TEST: No text string provided'); - return ''; - } - if (!passcode) { - console.log('TEST: No journal key provided'); - return false; - } +// async function handle_encrypt_string(text_string: string, passcode: string) { +// // log_lvl = 1; +// if (log_lvl) { +// console.log('TEST: handle_encrypt_string'); +// } +// if (!text_string) { +// console.log('TEST: No text string provided'); +// return ''; +// } +// if (!passcode) { +// console.log('TEST: No journal key provided'); +// return false; +// } - let combined_data_2 = await ae_util.encrypt_wrapper(text_string, passcode); - if (log_lvl) { - console.log('TEST: Encrypted string:', combined_data_2); - } - return combined_data_2; +// let combined_data_2 = await ae_util.encrypt_wrapper(text_string, passcode); +// if (log_lvl) { +// console.log('TEST: Encrypted string:', combined_data_2); +// } +// return combined_data_2; - // Encrypt the string using the journal key - // let encrypted_base64 = await ae_util.encrypt_content(text_string, passcode); - // let encrypted_base64_string = encrypted_base64.base64; - // let encryption_iv = encrypted_base64.iv; - // console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_string}`); +// // Encrypt the string using the journal key +// // let encrypted_base64 = await ae_util.encrypt_content(text_string, passcode); +// // let encrypted_base64_string = encrypted_base64.base64; +// // let encryption_iv = encrypted_base64.iv; +// // console.log(`IV: ${encryption_iv}; Encrypted: ${encrypted_base64_string}`); - // const combined_data = ae_util.combine_iv_and_base64(encrypted_base64_string, encryption_iv); +// // const combined_data = ae_util.combine_iv_and_base64(encrypted_base64_string, encryption_iv); - // Combine the IV and encrypted content - // const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_string; +// // Combine the IV and encrypted content +// // const combined_data = Array.from(encryption_iv).map(byte => byte.toString(16).padStart(2, '0')).join('') + ':' + encrypted_base64_string; - // return combined_data; -} +// // return combined_data; +// } // return new_string and cut_string function handle_cut_string(old_string: string) { @@ -805,7 +814,9 @@ function handle_cut_string(old_string: string) { function handle_marked(text_string: string) { if (!text_string) { - console.log('TEST: No text string provided'); + if (log_lvl) { + console.log('No text string provided'); + } return ''; } // let cleaned_string = text_string.replace(/<[^>]*>/g, ''); // Remove HTML tags diff --git a/src/routes/journals/ae_comp__journal_obj_id_view.svelte b/src/routes/journals/ae_comp__journal_obj_id_view.svelte index fb9ed03e..184d0174 100644 --- a/src/routes/journals/ae_comp__journal_obj_id_view.svelte +++ b/src/routes/journals/ae_comp__journal_obj_id_view.svelte @@ -3,14 +3,15 @@ // *** Import other supporting libraries import { - BookPlus, BookOpenText + BookPlus, BookOpenText, + FilePlus, Menu, Pencil } from '@lucide/svelte'; // *** Import Aether specific variables and functions import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores'; import { journals_loc, journals_sess, journals_slct, journals_trig, journals_prom } from '$lib/ae_journals/ae_journals_stores'; -// import { journals_func } from '$lib/ae_journals/ae_journals_functions'; +import { journals_func } from '$lib/ae_journals/ae_journals_functions'; interface Props { log_lvl?: number; @@ -28,7 +29,56 @@ let { log_lvl = 0, // let ae_trigger: any = null; // let ae_triggers: key_val = {}; -// Reminder: Styling is being done with Tailwind CSS, not Bootstrap. +let show_menu: boolean = $state(false); +let typed_journal_passcode: string = $state(''); +let passcode_timer: any = $state(null); + +$effect(() => { + if (typed_journal_passcode?.length > 4) { + log_lvl = 1; + verify_journal_passcode(); + } + + // We need to set a timeout to force the user to re-enter their private passcode + if ($lq__journal_obj?.id && $journals_sess?.journal_kv[$lq__journal_obj?.id] && $journals_sess?.journal_kv[$lq__journal_obj?.id]?.journal_passcode_verified) { + if (passcode_timer) { + if (log_lvl) { + console.log('Passcode timer already set'); + } + return; + } + console.log('Setting passcode timer'); + passcode_timer = setTimeout(() => { + if (log_lvl) { + console.log('Passcode timer expired'); + } + typed_journal_passcode = ''; + $journals_sess.journal_kv[$lq__journal_obj?.id].journal_passcode_verified = false; + }, 1000 * 60 * 1); // 1 minutes + // }, 1000 * $lq__journal_obj?.passcode_timeout); // 5 minutes + } +}); + +function verify_journal_passcode() { + if (log_lvl) { + console.log(`verify_journal_passcode: typed_journal_passcode = ${typed_journal_passcode} journal private passcode = ${$lq__journal_obj?.private_passcode}`); + } + + if (typed_journal_passcode === $lq__journal_obj?.private_passcode) { + console.log('Matched journal private passcode'); + if (!$journals_sess?.journal_kv[$lq__journal_obj?.id]) { + $journals_sess.journal_kv[$lq__journal_obj?.id] = {}; + } + $journals_sess.journal_kv[$lq__journal_obj?.id] = { + typed_journal_passcode: typed_journal_passcode, + journal_passcode_verified: true + }; + + typed_journal_passcode = ''; + } else { + + } +} @@ -40,7 +90,7 @@ let { log_lvl = 0, " bind:clientHeight={$ae_loc.iframe_height_modal_body}> -
+

{@html $lq__journal_obj?.name ?? 'Loading...'} @@ -53,6 +103,148 @@ let { log_lvl = 0, {/await}

+ + + +
+ + + + + {#if show_menu} +
+
+ + + + Journal ID: {$lq__journal_obj?.id} + + + + + +
+ + + + { + // console.log('HERE'); + // verify_journal_passcode(); + }} + class="input input-sm input-bordered w-full mb-2" + title="Enter private passcode of this journal" + /> + + {#if $journals_sess.journal_kv[$lq__journal_obj?.id]?.journal_passcode_verified} +
+ + Journal passcode verified +
+ {:else} +
+ + Journal passcode not verified {$lq__journal_obj?.private_passcode ?? '??'} +
+ {/if} + + +
+ {/if} +
+ +