Work on passcodes and encryption
This commit is contained in:
@@ -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,
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -195,7 +195,7 @@ async function handle_update_journal() {
|
||||
</a>
|
||||
{:else}
|
||||
<!-- Edit Journal button. Creates a modal to edit the journal. -->
|
||||
<button
|
||||
<!-- <button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$journals_slct.tmp_journal_obj = {
|
||||
@@ -222,7 +222,7 @@ async function handle_update_journal() {
|
||||
<span class="hidden md:inline">
|
||||
Edit Journal
|
||||
</span>
|
||||
</button>
|
||||
</button> -->
|
||||
{/if}
|
||||
|
||||
<!-- Add default journal entry -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -40,7 +90,7 @@ let { log_lvl = 0,
|
||||
"
|
||||
bind:clientHeight={$ae_loc.iframe_height_modal_body}>
|
||||
|
||||
<header class="ae_header journal__header">
|
||||
<header class="ae_header journal__header flex flex-row flex-wrap gap-2 items-center justify-between w-full">
|
||||
<h2 class="journal__name h3 text-center ">
|
||||
<BookOpenText class="inline-block" />
|
||||
{@html $lq__journal_obj?.name ?? 'Loading...'}
|
||||
@@ -53,6 +103,148 @@ let { log_lvl = 0,
|
||||
<!-- done -->
|
||||
{/await}
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
<section class="relative">
|
||||
<!-- Menu Button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (show_menu = !show_menu)}
|
||||
class="btn variant-outline-secondary hover:variant-filled-secondary transition py-1 px-2 w-24 *:hover:inline"
|
||||
title="Toggle menu"
|
||||
>
|
||||
<Menu size="1.5em" class="inline-block" />
|
||||
<span class="hidden md:inline">Menu</span>
|
||||
</button>
|
||||
|
||||
<!-- Menu -->
|
||||
{#if show_menu}
|
||||
<div
|
||||
class="absolute top-12 right-0 bg-white dark:bg-gray-800 shadow-xl rounded-lg p-4 z-50 w-80 space-y-0.5 border border-gray-500"
|
||||
>
|
||||
<div class="flex flex-row flex-wrap gap-1 items-center justify-between">
|
||||
|
||||
<span class="text-sm text-gray-500">
|
||||
<span class="fas fa-info-circle text-blue-500"></span>
|
||||
Journal ID: {$lq__journal_obj?.id}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// $journals_sess.show__modal_new__journal_entry_obj = true;
|
||||
|
||||
let data_kv = {
|
||||
category_code: null,
|
||||
};
|
||||
if ($journals_loc.qry__category_code) {
|
||||
data_kv.category_code = $journals_loc.qry__category_code;
|
||||
}
|
||||
if (log_lvl) {
|
||||
console.log('Creating new journal entry with data_kv:', data_kv);
|
||||
}
|
||||
journals_func.create_ae_obj__journal_entry({
|
||||
api_cfg: $ae_api,
|
||||
journal_id: $lq__journal_obj?.journal_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: log_lvl
|
||||
}).then((results) => {
|
||||
if (log_lvl) {
|
||||
console.log('New journal entry created:', results);
|
||||
}
|
||||
$journals_slct.journal_entry_id = results?.journal_entry_id_random;
|
||||
// $journals_loc.entry.edit = true;
|
||||
$journals_loc.entry.edit_kv[$journals_slct.journal_entry_id] = 'current';
|
||||
// alert(`Journal entry created successfully! ${$journals_slct.journal_entry_id}`);
|
||||
}).catch((error) => {
|
||||
console.error('Error updating journal entry:', error);
|
||||
alert('Failed to update journal entry.');
|
||||
}).finally(() => {
|
||||
if ($journals_slct.journal_entry_id) {
|
||||
goto(`/journals/${$lq__journal_obj?.journal_id}/entry/${$journals_slct.journal_entry_id}`);
|
||||
} else {
|
||||
alert('Failed to create new journal entry.');
|
||||
}
|
||||
});
|
||||
}}
|
||||
class="
|
||||
btn btn-sm
|
||||
variant-ghost-secondary
|
||||
hover:variant-filled-secondary
|
||||
transition
|
||||
"
|
||||
title="Create a new journal entry for this journal: {$lq__journal_obj?.name}"
|
||||
>
|
||||
<FilePlus />
|
||||
<!-- <span class="fas fa-plus m-1"></span> -->
|
||||
<span class="hidden sm:inline">
|
||||
New Journal Entry
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
$journals_slct.tmp_journal_obj = {
|
||||
name: $lq__journal_obj?.name,
|
||||
description: $lq__journal_obj?.description,
|
||||
type_code: $lq__journal_obj?.type_code,
|
||||
passcode: $lq__journal_obj?.passcode,
|
||||
passcode_timeout: $lq__journal_obj?.passcode_timeout,
|
||||
auth_key: $lq__journal_obj?.auth_key,
|
||||
cfg_json: $lq__journal_obj?.cfg_json
|
||||
};
|
||||
$journals_sess.show__modal_edit__journal_obj = true;
|
||||
}}
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
class="
|
||||
btn btn-sm
|
||||
variant-ghost-warning
|
||||
hover:variant-filled-warning
|
||||
transition
|
||||
"
|
||||
title="Edit Journal meta and configuration (name, type, passcode, categories, etc.: {$lq__journal_obj?.name})"
|
||||
>
|
||||
<Pencil />
|
||||
<span class="hidden md:inline">
|
||||
Edit Journal
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Set Journal private_passcode (string) -->
|
||||
<!-- The Journal private_passcode will be used along with the Journal passcode to encrypt the journal entries. -->
|
||||
<input
|
||||
type="text"
|
||||
bind:value={typed_journal_passcode}
|
||||
placeholder="Journal passcode"
|
||||
onchange={() => {
|
||||
// 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}
|
||||
<div class="text-sm text-gray-500">
|
||||
<span class="fas fa-check-circle text-green-500"></span>
|
||||
Journal passcode verified
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">
|
||||
<span class="fas fa-exclamation-circle text-red-500"></span>
|
||||
Journal passcode not verified {$lq__journal_obj?.private_passcode ?? '??'}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<!-- Show Journal description -->
|
||||
|
||||
Reference in New Issue
Block a user