- launcher/+layout.svelte: convert lq__event_session_obj from $derived to $derived.by() so Svelte tracks event_session_id as a dependency; the old pattern read the store inside the Dexie async callback where Svelte's tracking is off, so the liveQuery never updated on session change - ae_events__event_file.ts: fix hardcoded log_lvl: 2 in SWR fire-and-forget background refresh (always-on debug logging on every cache hit) → 0 - e_app_sign_in_out.svelte: lower 6 call-site log levels (1×log_lvl:2, 5×log_lvl:1) to 0; sign-in runs on every page load - element_manage_hosted_file_li.svelte: log_lvl:2 → 0 in refresh call; remove log_lvl=1 assignment + debug block inside click handler; log_lvl:1 → 0 in delete call - AE__Performance_Guidelines.md: add 5 Svelte 5 runes rules covering $derived.by() for reactive liveQuery, liveQuery purity, cheap equality guards ($id+updated_on, ID-join, shallow_equal), untrack() requirement, and log_lvl discipline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
941 lines
44 KiB
Svelte
941 lines
44 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from 'svelte';
|
|
// *** Import Svelte specific
|
|
import { browser } from '$app/environment';
|
|
import { goto, invalidateAll } from '$app/navigation';
|
|
import { Modal } from 'flowbite-svelte';
|
|
|
|
// *** Import other supporting libraries
|
|
import {
|
|
CircleX,
|
|
Eye,
|
|
EyeOff,
|
|
Key,
|
|
LogIn,
|
|
LogOut,
|
|
LockKeyhole,
|
|
Mail,
|
|
MailCheck,
|
|
ShieldUser,
|
|
User,
|
|
UserCheck,
|
|
UserLock
|
|
} from '@lucide/svelte';
|
|
|
|
// *** Import Aether specific variables and functions
|
|
import type { key_val } from '$lib/stores/ae_stores';
|
|
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/stores/ae_stores';
|
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
|
import { core_func } from '$lib/ae_core/ae_core_functions';
|
|
|
|
// *** Setup Svelte properties
|
|
interface Props {
|
|
log_lvl?: number;
|
|
data?: any;
|
|
hidden: null | boolean;
|
|
}
|
|
|
|
let { log_lvl = $bindable(0), data = null, hidden = $bindable(true) }: Props = $props();
|
|
|
|
let url_user_id = $state(untrack(() => data?.url?.searchParams?.get('user_id')));
|
|
let url_user_key = $state(untrack(() => data?.url?.searchParams?.get('user_key'))); // Reminder that "key" is the site's auth key.
|
|
let url_user_username = $state(untrack(() => data?.url?.searchParams?.get('username')));
|
|
let url_user_email = $state(untrack(() => data?.url?.searchParams?.get('user_email')));
|
|
|
|
$effect(() => {
|
|
// NOTE: Sync URL params to state.
|
|
// We use untrack to prevent infinite loops if navigation triggers within this effect.
|
|
// WARNING: Ensure this doesn't clobber user-entered data during background URL updates.
|
|
untrack(() => {
|
|
url_user_id = data?.url?.searchParams?.get('user_id');
|
|
url_user_key = data?.url?.searchParams?.get('user_key');
|
|
url_user_username = data?.url?.searchParams?.get('username');
|
|
url_user_email = data?.url?.searchParams?.get('user_email');
|
|
});
|
|
});
|
|
|
|
let ae_promises: key_val = {};
|
|
|
|
let trigger: null | boolean = $state(null); // Use $state to ensure reactivity
|
|
let user_id: string | null = $state(null); // Use $state to ensure reactivity
|
|
let user_obj: key_val = $state({}); // Use $state to ensure reactivity
|
|
let person_id: string | null = $state(null); // Use $state to ensure reactivity
|
|
let person_obj: key_val = $state({}); // Use $state to ensure reactivity
|
|
|
|
$effect(() => {
|
|
if (user_id && person_id && trigger) {
|
|
// alert(`Ready to sign in person! \n\nuser_id: ${user_id}\nperson_id: ${person_id}\n\nThis is a test alert to confirm the values are set.`);
|
|
trigger = false;
|
|
sign_in();
|
|
}
|
|
});
|
|
|
|
let user_email = $state('scott.idem@gmail.com'); // Used for quick lookup of user by email address
|
|
let new_password = $state('test12345');
|
|
let confirm_password = $state('test12345');
|
|
let is_changing_password = $state(false);
|
|
let show_password_text = $state('password'); // password or text
|
|
|
|
function sign_in() {
|
|
$ae_loc.jwt = user_obj.jwt; // Store the JSON Web Token
|
|
$ae_loc.person_id = person_id; // Set the person_id in the ae_loc store
|
|
$ae_loc.person = person_obj; // Store the full person object for reference
|
|
$ae_loc.user_id = user_id; // Set the user_id in the ae_loc store
|
|
$ae_loc.user = user_obj; // Store the full user object for reference
|
|
|
|
if (user_obj.super) {
|
|
$ae_loc.access_type = 'super';
|
|
} else if (user_obj.manager) {
|
|
$ae_loc.access_type = 'manager';
|
|
} else if (user_obj.administrator) {
|
|
$ae_loc.access_type = 'administrator';
|
|
} else if (user_obj.verified) {
|
|
// They are "trusted" because the user has been verified. External staff usually start with this level.
|
|
$ae_loc.access_type = 'trusted';
|
|
} else if (user_obj.public) {
|
|
$ae_loc.access_type = 'public';
|
|
} else {
|
|
$ae_loc.access_type = 'authenticated';
|
|
}
|
|
$ae_loc.user_access_type = $ae_loc.access_type; // Used to revert back to the user's access type after quick access (temporarily escalate permissions) is turned off.
|
|
|
|
let access_checks_results = ae_util.process_permission_checks($ae_loc.access_type);
|
|
// WARNING: I think this is causing a loop in Svelte or something.
|
|
// Last ten effects were:
|
|
// Uncaught Svelte error: effect_update_depth_exceeded
|
|
// Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
|
|
// https://svelte.dev/e/effect_update_depth_exceeded
|
|
$ae_loc = {
|
|
...$ae_loc,
|
|
...access_checks_results
|
|
};
|
|
// $ae_loc = {...access_checks_results};
|
|
|
|
console.log('Remove the sign in fields from the URL.');
|
|
data.url.searchParams.delete('user_id');
|
|
data.url.searchParams.delete('user_key');
|
|
data.url.searchParams.delete('username');
|
|
data.url.searchParams.delete('user_email');
|
|
data.url.searchParams.delete('valid_email'); // Part of sign in email for possible future use
|
|
|
|
let new_url = data.url.toString();
|
|
|
|
// We need to set browser history and force all load functions to rerun.
|
|
goto(new_url, { replaceState: true, invalidateAll: true });
|
|
}
|
|
|
|
function sign_out() {
|
|
// Clear the session information
|
|
$ae_loc.jwt = null;
|
|
$ae_loc.person_id = null;
|
|
$ae_loc.person = {};
|
|
$ae_loc.user_id = null;
|
|
$ae_loc.user = {};
|
|
|
|
user_id = null; // Clear the user_id
|
|
user_obj = {}; // Clear the user object
|
|
person_id = null; // Clear the person_id
|
|
person_obj = {}; // Clear the person object
|
|
|
|
$ae_loc.access_type = ''; // Clear the access type
|
|
let access_checks_results = ae_util.process_permission_checks('');
|
|
$ae_loc = { ...$ae_loc, ...access_checks_results };
|
|
|
|
$ae_sess.auth__entered_user_id = null;
|
|
$ae_sess.auth__entered_user_key = null;
|
|
// $ae_sess.auth__entered_username = null; // Keeping the username
|
|
$ae_sess.auth__entered_password = null;
|
|
|
|
indexedDB.deleteDatabase('ae_archives_db'); // Archives module
|
|
indexedDB.deleteDatabase('ae_core_db');
|
|
indexedDB.deleteDatabase('ae_events_db'); // Events module
|
|
indexedDB.deleteDatabase('ae_journals_db'); // Journals module
|
|
indexedDB.deleteDatabase('ae_posts_db'); // Posts module
|
|
indexedDB.deleteDatabase('ae_sponsorships_db'); // Sponsorships module
|
|
|
|
// $ae_loc.allow_access = false;
|
|
$ae_loc.authenticated_access = false;
|
|
$ae_loc.edit_mode = false;
|
|
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
|
|
console.log('Remove the sign out fields from the URL.');
|
|
data.url.searchParams.delete('user_id');
|
|
data.url.searchParams.delete('user_key');
|
|
data.url.searchParams.delete('username');
|
|
data.url.searchParams.delete('user_email');
|
|
data.url.searchParams.delete('valid_email'); // Part of sign in email for possible future use
|
|
|
|
let new_url = data.url.toString();
|
|
|
|
// We need to set browser history and force all load functions to rerun.
|
|
// goto(new_url, {replaceState: true, invalidateAll: true});
|
|
|
|
// invalidateAll();
|
|
window.location.reload();
|
|
|
|
console.log('Signed out successfully.');
|
|
}
|
|
|
|
$effect(() => {
|
|
if (browser) {
|
|
untrack(() => {
|
|
if (url_user_id) {
|
|
// Pre-fill the auth__entered_user_id if passed in via the URL.
|
|
$ae_sess.auth__entered_user_id = url_user_id;
|
|
}
|
|
if (url_user_key) {
|
|
// Pre-fill the auth__entered_key if passed in via the URL.
|
|
$ae_sess.auth__entered_user_key = url_user_key;
|
|
}
|
|
if (url_user_username) {
|
|
// Pre-fill the auth__entered_username if passed in via the URL.
|
|
$ae_sess.auth__entered_username = url_user_username;
|
|
}
|
|
if (url_user_email) {
|
|
// Pre-fill the auth__entered_email if passed in via the URL.
|
|
$ae_sess.auth__entered_email = url_user_email;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Use core_func.send_email_auth_ae_obj__user_id
|
|
function handle_send_auth_email({ user_id }: { user_id: string }) {
|
|
console.log('handle_send_auth_email()');
|
|
|
|
console.log($ae_loc.base_url); // URL origin
|
|
console.log($ae_loc.hostname); // URL hostname
|
|
|
|
// This function creates a new auth_key and then sends an email to the user with the new auth key.
|
|
ae_promises.send_email_auth_ae_obj__user_id = core_func.send_email_auth_ae_obj__user_id({
|
|
api_cfg: $ae_api,
|
|
account_id: $slct.account_id,
|
|
user_id: user_id,
|
|
base_url: $ae_loc.base_url,
|
|
log_lvl: 0
|
|
});
|
|
}
|
|
|
|
// Use core_func.qry_ae_obj_li__user_email
|
|
function handle_lookup_user_email({ email }: { email: string }) {
|
|
console.log('handle_lookup_user_email()');
|
|
|
|
// If a user is returned then send the new auth key email
|
|
ae_promises.load__user_obj_li = core_func
|
|
.qry_ae_obj_li__user_email({
|
|
api_cfg: $ae_api,
|
|
account_id: $slct.account_id,
|
|
null_account_id: false,
|
|
email: email,
|
|
log_lvl: 0
|
|
})
|
|
.then((user_response) => {
|
|
if (user_response?.user_id_random) {
|
|
console.log(`User found for email:`, user_response);
|
|
handle_send_auth_email({
|
|
user_id: user_response.user_id_random
|
|
});
|
|
} else if (user_response && user_response.length > 0) {
|
|
console.log(`Multiple users found for email:`, user_response);
|
|
handle_send_auth_email({
|
|
user_id: user_response[0].user_id_random
|
|
});
|
|
} else {
|
|
alert('No user found with that email address.');
|
|
}
|
|
});
|
|
}
|
|
|
|
async function handle_change_password() {
|
|
if (!new_password) {
|
|
alert('Password cannot be empty.');
|
|
return;
|
|
}
|
|
|
|
if (new_password !== confirm_password) {
|
|
alert('Passwords do not match.');
|
|
return;
|
|
}
|
|
|
|
let use_user_id = $ae_loc.user_id;
|
|
let use_password = new_password;
|
|
let wait_for_lookup = true;
|
|
|
|
// Look up the user by email address to get their user ID
|
|
if (user_email) {
|
|
wait_for_lookup = true;
|
|
// ae_promises.load__user_obj_li = await core_func.qry_ae_obj_li__user_email({
|
|
// api_cfg: $ae_api,
|
|
// account_id: $slct.account_id,
|
|
// null_account_id: false,
|
|
// email: user_email,
|
|
// log_lvl: 1
|
|
// }).then((user_response) => {
|
|
// if (!user_response) {
|
|
// // This means a 404 was returned
|
|
// alert('No user found with that email address.');
|
|
// return;
|
|
// } else if (user_response?.user_id_random) {
|
|
// console.log(`User found for email:`, user_response);
|
|
// use_user_id = user_response.user_id_random;
|
|
// } else if (user_response.length > 0) {
|
|
// console.log(`Multiple users found for email:`, user_response);
|
|
// use_user_id = user_response[0].user_id_random;
|
|
// }
|
|
// });
|
|
|
|
ae_promises.load__user_obj_li = await core_func.qry_ae_obj_li__user_email({
|
|
api_cfg: $ae_api,
|
|
account_id: $slct.account_id,
|
|
null_account_id: false,
|
|
email: user_email,
|
|
log_lvl: 0
|
|
});
|
|
|
|
if (!ae_promises.load__user_obj_li) {
|
|
// This means a 404 was returned
|
|
alert('No user found with that email address.');
|
|
return;
|
|
} else if (ae_promises.load__user_obj_li?.user_id_random) {
|
|
console.log(`User found for email:`, ae_promises.load__user_obj_li);
|
|
use_user_id = ae_promises.load__user_obj_li.user_id_random;
|
|
} else if (ae_promises.load__user_obj_li.length > 0) {
|
|
console.log(`Multiple users found for email:`, ae_promises.load__user_obj_li);
|
|
use_user_id = ae_promises.load__user_obj_li[0].user_id_random;
|
|
}
|
|
} else {
|
|
wait_for_lookup = false;
|
|
}
|
|
|
|
is_changing_password = true;
|
|
|
|
if (user_email && wait_for_lookup) {
|
|
console.log('Waiting for user lookup to complete...');
|
|
// await ae_promises.load__user_obj_li;
|
|
}
|
|
|
|
try {
|
|
ae_promises.change_password = await core_func.auth_ae_obj__user_id_change_password({
|
|
api_cfg: $ae_api,
|
|
account_id: $ae_loc.account_id,
|
|
user_id: use_user_id,
|
|
password: use_password,
|
|
log_lvl: log_lvl
|
|
});
|
|
// await core_func.auth_ae_obj__user_id_change_password({
|
|
// api_cfg: $ae_api,
|
|
// account_id: $ae_loc.account_id,
|
|
// user_id: use_user_id,
|
|
// password: use_password,
|
|
// log_lvl: log_lvl
|
|
// });
|
|
// alert('Password changed successfully.');
|
|
// $ae_sess.show__modal_change_password = false;
|
|
} catch (error) {
|
|
console.error('Error changing password:', error);
|
|
alert('Unexpected Response. Failed to change password.');
|
|
} finally {
|
|
is_changing_password = false;
|
|
}
|
|
if (ae_promises.change_password) {
|
|
console.log('Password changed successfully.');
|
|
alert('Password changed successfully.');
|
|
$ae_sess.show__modal_change_password = false;
|
|
} else {
|
|
console.error('Failed to change password.');
|
|
alert('Failed to change password. Check password length.');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!-- It is important to keep in mind that a Person can only fully sign in if they have a linked User record. -->
|
|
<!-- *:hover:inline -->
|
|
<section
|
|
id="AE-Sign-In-Out"
|
|
class="
|
|
ae_sign_in_out
|
|
hidden-print
|
|
|
|
bg-blue-100 text-gray-900
|
|
dark:bg-blue-800 dark:text-gray-200
|
|
|
|
flex flex-col flex-wrap gap-1
|
|
items-end justify-center
|
|
|
|
w-72 max-w-72
|
|
p-1
|
|
border-2 border-gray-200
|
|
|
|
duration-300 delay-150 hover:delay-1000 hover:ease-out
|
|
transition-all
|
|
"
|
|
class:hidden
|
|
>
|
|
<header class:hidden={!$ae_sess.show__sign_in_out__fields} class="ae_header w-full">
|
|
<h2 class="text-sm text-center font-semibold">
|
|
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
|
<!-- <button
|
|
type="button"
|
|
class="btn btn-sm variant-outline-surface hover:variant-filled-surface"
|
|
title="Show/Hide Sign-In/Out Options"
|
|
onclick={() => {
|
|
$ae_sess.show__sign_in_out__fields = !$ae_sess.show__sign_in_out__fields; // Toggle the visibility of the sign-in form
|
|
}}
|
|
> -->
|
|
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
|
<!-- <span class="text-sm text-gray-500">
|
|
{$ae_loc?.user.username ?? '-- not set --'}
|
|
</span> -->
|
|
<!-- {#if log_lvl > 1}
|
|
<span class="text-xs text-gray-500">({$ae_loc.person_id} / {$ae_loc.user_id})</span>
|
|
{/if} -->
|
|
<!-- </button> -->
|
|
{:else}
|
|
<!-- <LogIn class="mx-1" /> -->
|
|
User Sign In
|
|
{/if}
|
|
</h2>
|
|
</header>
|
|
|
|
<span
|
|
class:hidden={!$ae_sess.show__sign_in_out__fields}
|
|
class="flex flex-col gap-1 transition-all w-full"
|
|
>
|
|
{#if !$ae_loc?.person_id && !$ae_loc?.user_id}
|
|
<!-- Form for user look up based on email address -->
|
|
<form
|
|
class="
|
|
ae_sign_in_form
|
|
flex flex-col gap-1 items-end justify-center
|
|
p-1 my-1
|
|
"
|
|
onsubmit={async (e) => {
|
|
e.preventDefault();
|
|
if ($ae_sess.auth__entered_email) {
|
|
alert('Attempting to look up user by email address.');
|
|
handle_lookup_user_email({
|
|
email: $ae_sess.auth__entered_email
|
|
});
|
|
} else {
|
|
alert('Please enter an email address to look up.');
|
|
}
|
|
}}
|
|
>
|
|
<input
|
|
type="text"
|
|
class="input max-w-48"
|
|
placeholder="Email Address"
|
|
value={$ae_sess.auth__entered_email ?? ''}
|
|
oninput={(e) => ($ae_sess.auth__entered_email = (e.target as HTMLInputElement).value)}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500"
|
|
title="Look up user by email and send sign in email"
|
|
>
|
|
<!-- <User class="mx-1" /> -->
|
|
<!-- <Mail class="mx-1" /> -->
|
|
<MailCheck class="mx-1" />
|
|
Email Sign In
|
|
</button>
|
|
</form>
|
|
|
|
<!-- We need to get the person's linked User ID and auth_key or User username and password to sign in. -->
|
|
<form
|
|
class="
|
|
ae_sign_in_form
|
|
flex flex-col gap-1 items-end
|
|
p-1 my-1
|
|
"
|
|
onsubmit={async (e) => {
|
|
e.preventDefault();
|
|
|
|
// WARNING: Logging in as a global user does not work yet. The API needs to be updated. Currently it returns multiple user records from the v_user view if there is more than one person record linked to the user ID.
|
|
|
|
if ($ae_sess.auth__entered_user_id && $ae_sess.auth__entered_user_key) {
|
|
// Try to use the user ID and user auth key passed in the URL params for authentication
|
|
alert('Attempting to authenticate with User ID and Auth Key.');
|
|
|
|
ae_promises['user'] = await core_func
|
|
.auth_ae_obj__user_id_user_auth_key({
|
|
api_cfg: $ae_api,
|
|
account_id: $ae_loc.account_id,
|
|
// null_account_id: false, // Set to true to allow to authenticate as global user (Super or Manager)
|
|
user_id: $ae_sess.auth__entered_user_id,
|
|
user_auth_key: $ae_sess.auth__entered_user_key,
|
|
log_lvl: 0
|
|
})
|
|
.then((user_response) => {
|
|
// console.log(`HERE:`, user_response);
|
|
if (user_response?.user_id) {
|
|
console.log(
|
|
`Successfully authenticated in with User ID and User Auth Key: ${user_response.username}`,
|
|
user_response
|
|
);
|
|
user_obj = user_response; // Store the user object for later use
|
|
user_id = user_obj.user_id; // Use the user_id for further API calls
|
|
// person_id = user_obj.person_id_random;
|
|
} else if (!user_response) {
|
|
alert('Sign in failed: No response from the server. Check your connection and try again.');
|
|
} else {
|
|
// API returns 'detail' for validation errors (FastAPI standard), 'error' for app-level errors
|
|
const reason = user_response?.detail || user_response?.error || 'Invalid User ID or Auth Key.';
|
|
alert('Sign in failed: ' + reason);
|
|
}
|
|
})
|
|
.then((response) => {
|
|
if (!user_id) {
|
|
// Auth failed in the previous .then() — user has already been alerted
|
|
console.error('Auth (user_id+key): user_id not set after authentication attempt.');
|
|
return;
|
|
}
|
|
|
|
// Next we need to get the person's information. This is odd because we need to look it up based on the account_id and user_id. There should only be one account person record per user. 99% of the time the user's account ID will be the same as the person's account ID. The exception is for AE Global users (Super or Manager) who can have multiple accounts but only one person record per account.
|
|
|
|
// let params = {
|
|
// user_id_random: user_id, // The user_id_random from the above authentication
|
|
// }
|
|
|
|
// let params_json: key_val = {};
|
|
|
|
// params_json['and_qry'] = {};
|
|
|
|
// if (user_id) {
|
|
// params_json['and_qry']['user_id_random'] = user_id;
|
|
// }
|
|
|
|
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
|
|
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
|
|
// We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if
|
|
// technical fields like 'hide' are NULL or the record is temporarily disabled.
|
|
ae_promises['person'] = core_func
|
|
.load_ae_obj_li__person({
|
|
api_cfg: $ae_api,
|
|
for_obj_type: 'account',
|
|
for_obj_id: $ae_loc.account_id,
|
|
qry_user_id: user_id, // The user_id_random from the above authentication
|
|
enabled: 'all',
|
|
hidden: 'all',
|
|
// params_json: params_json,
|
|
// params: params,
|
|
log_lvl: 0
|
|
})
|
|
.then((person_response) => {
|
|
// Safety Check: Ensure the response is valid and contains at least one record before accessing index 0.
|
|
// V3 CRUD returns 'id' as the random identifier; 'person_id_random' is a legacy field name.
|
|
const person_rec = person_response?.[0];
|
|
if (person_rec && (person_rec.id || person_rec.person_id_random)) {
|
|
console.log(
|
|
`Successfully loaded person (${user_id}):`,
|
|
person_rec
|
|
);
|
|
person_obj = person_rec;
|
|
person_id = person_rec.id ?? person_rec.person_id_random;
|
|
|
|
trigger = true; // Set trigger to true to indicate we can now sign in
|
|
} else {
|
|
alert(
|
|
'Sign in failed: No person record is linked to this user account. Please contact your administrator.'
|
|
);
|
|
}
|
|
})
|
|
.then(() => {
|
|
if (user_id && person_id) {
|
|
console.log(`Successfully authenticated and loaded user and person records: user_id: ${user_id}, person_id: ${person_id}`);
|
|
} else {
|
|
console.error(
|
|
'Auth (user_id+key): finished but missing user_id or person_id — sign_in() will not be called.'
|
|
);
|
|
}
|
|
});
|
|
});
|
|
// console.log('DONE???', ae_promises);
|
|
} else if ($ae_sess.auth__entered_username && $ae_sess.auth__entered_password) {
|
|
// Try to use the username/password for authentication
|
|
// alert('Attempting to authenticate with Username and Password.');
|
|
ae_promises['user'] = await core_func
|
|
.auth_ae_obj__username_password({
|
|
api_cfg: $ae_api,
|
|
account_id: $ae_loc.account_id,
|
|
// null_account_id: false, // Set to true to allow to authenticate as global user (Super or Manager)
|
|
username: $ae_sess.auth__entered_username,
|
|
password: $ae_sess.auth__entered_password,
|
|
log_lvl: 0
|
|
})
|
|
.then((user_response) => {
|
|
if (user_response?.user_id) {
|
|
console.log(
|
|
`Successfully authenticated in with Username (${user_response.username}) and Password:`,
|
|
user_response
|
|
);
|
|
user_obj = user_response; // Store the user object for later use
|
|
user_id = user_obj.user_id; // Use the user_id for further API calls
|
|
// person_id = user_obj.person_id_random;
|
|
} else if (!user_response) {
|
|
alert('Sign in failed: No response from the server. Check your connection and try again.');
|
|
} else {
|
|
// API returns 'detail' for validation errors (FastAPI standard), 'error' for app-level errors
|
|
const reason = user_response?.detail || user_response?.error || 'Invalid username or password.';
|
|
alert('Sign in failed: ' + reason);
|
|
}
|
|
})
|
|
.then((response) => {
|
|
if (!user_id) {
|
|
// Auth failed in the previous .then() — user has already been alerted
|
|
console.error('Auth (username+password): user_id not set after authentication attempt.');
|
|
return;
|
|
}
|
|
|
|
// Next we need to get the person's information. This is odd because we need to look it up based on the account_id and user_id. There should only be one account person record per user. 99% of the time the user's account ID will be the same as the person's account ID. The exception is for AE Global users (Super or Manager) who can have multiple accounts but only one person record per account.
|
|
|
|
// let params = {
|
|
// user_id_random: user_id, // The user_id_random from the above authentication
|
|
// }
|
|
|
|
// let params_json: key_val = {};
|
|
|
|
// params_json['and_qry'] = {};
|
|
|
|
// if (user_id) {
|
|
// params_json['and_qry']['user_id_random'] = user_id;
|
|
// }
|
|
|
|
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
|
|
// WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned.
|
|
// We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if
|
|
// technical fields like 'hide' are NULL or the record is temporarily disabled.
|
|
ae_promises['person'] = core_func
|
|
.load_ae_obj_li__person({
|
|
api_cfg: $ae_api,
|
|
for_obj_type: 'account',
|
|
for_obj_id: $ae_loc.account_id,
|
|
qry_user_id: user_id, // The user_id_random from the above authentication
|
|
enabled: 'all',
|
|
hidden: 'all',
|
|
// params_json: params_json,
|
|
// params: params,
|
|
log_lvl: 0
|
|
})
|
|
.then((person_response) => {
|
|
// Safety Check: Ensure the response is valid and contains at least one record before accessing index 0.
|
|
// V3 CRUD returns 'id' as the random identifier; 'person_id_random' is a legacy field name.
|
|
const person_rec = person_response?.[0];
|
|
if (person_rec && (person_rec.id || person_rec.person_id_random)) {
|
|
console.log(
|
|
`Successfully loaded person (${user_id}):`,
|
|
person_rec
|
|
);
|
|
person_obj = person_rec;
|
|
person_id = person_rec.id ?? person_rec.person_id_random;
|
|
|
|
trigger = true; // Set trigger to true to indicate we can now sign in
|
|
} else {
|
|
alert(
|
|
'Sign in failed: No person record is linked to this user account. Please contact your administrator.'
|
|
);
|
|
}
|
|
})
|
|
.then(() => {
|
|
if (user_id && person_id) {
|
|
console.log(`Successfully authenticated and loaded user and person records: user_id: ${user_id}, person_id: ${person_id}`);
|
|
} else {
|
|
console.error(
|
|
'Auth (username+password): finished but missing user_id or person_id — sign_in() will not be called.'
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// console.log('DONE???', ae_promises);
|
|
} else {
|
|
alert(
|
|
'Please enter either a User ID and Auth Key or Username and Password.'
|
|
);
|
|
return false; // Prevent form submission if no credentials are provided
|
|
}
|
|
}}
|
|
>
|
|
{#if $ae_sess.auth__entered_user_id}
|
|
<input
|
|
type="text"
|
|
class="input max-w-36"
|
|
placeholder="User ID"
|
|
value={$ae_sess.auth__entered_user_id ?? ''}
|
|
oninput={(e) => ($ae_sess.auth__entered_user_id = (e.target as HTMLInputElement).value)}
|
|
/>
|
|
<input
|
|
type="text"
|
|
class="input max-w-36"
|
|
placeholder="Auth Key"
|
|
value={$ae_sess.auth__entered_user_key ?? ''}
|
|
oninput={(e) => ($ae_sess.auth__entered_user_key = (e.target as HTMLInputElement).value)}
|
|
/>
|
|
{:else}
|
|
<input
|
|
type="text"
|
|
class="input max-w-48"
|
|
placeholder="Username"
|
|
value={$ae_sess.auth__entered_username ?? ''}
|
|
oninput={(e) => ($ae_sess.auth__entered_username = (e.target as HTMLInputElement).value)}
|
|
/>
|
|
<input
|
|
type="password"
|
|
class="input max-w-48"
|
|
placeholder="Password"
|
|
value={$ae_sess.auth__entered_password ?? ''}
|
|
oninput={(e) => ($ae_sess.auth__entered_password = (e.target as HTMLInputElement).value)}
|
|
/>
|
|
{/if}
|
|
|
|
<button
|
|
type="submit"
|
|
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500"
|
|
title="Sign in with username and password"
|
|
>
|
|
<!-- <LogIn class="mx-1" /> -->
|
|
<UserCheck class="mx-1" />
|
|
{#if $ae_sess.auth__entered_user_id}
|
|
User ID
|
|
{:else}
|
|
Username
|
|
{/if}
|
|
Sign In
|
|
</button>
|
|
</form>
|
|
{:else}
|
|
<div class="flex flex-col gap-1 items-center justify-center">
|
|
<span class="text-sm text-gray-500">
|
|
{$ae_loc?.user?.username ?? '-- not set --'}
|
|
</span>
|
|
|
|
<!-- Display change password option if the user is signed in and in Edit Mode -->
|
|
{#if $ae_loc.edit_mode}
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500"
|
|
title="Change Password"
|
|
onclick={() => {
|
|
$ae_sess.show__modal_change_password = true;
|
|
}}
|
|
>
|
|
<LockKeyhole class="mx-1" />
|
|
<span class="hidden sm:inline"> Change Password </span>
|
|
</button>
|
|
{/if}
|
|
|
|
<!-- Display sign out option if the user is signed in -->
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500"
|
|
title="Sign Out"
|
|
onclick={async () => {
|
|
if (confirm('Are you sure you want to sign out?')) {
|
|
sign_out();
|
|
}
|
|
}}
|
|
>
|
|
<LogOut class="mx-1" />
|
|
<span class="hidden sm:inline"> Sign Out </span>
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</span>
|
|
|
|
<div class="flex flex-row gap-1 items-center justify-between w-full transition-all">
|
|
<span>
|
|
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
|
<span class="fas fa-user mx-1 text-gray-500"></span>
|
|
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
|
{:else}
|
|
<!-- <span class="fas fa-user-x mx-1 text-gray-500"></span> -->
|
|
<ShieldUser class="inline-block text-gray-500" />
|
|
User sign in:
|
|
{/if}
|
|
</span>
|
|
|
|
<button
|
|
type="button"
|
|
class="
|
|
btn btn-sm text-xs
|
|
variant-outline-warning hover:preset-tonal-warning
|
|
border border-warning-500 group
|
|
"
|
|
title="Sign In"
|
|
onclick={() => {
|
|
$ae_sess.show__sign_in_out__fields = !$ae_sess.show__sign_in_out__fields; // Toggle the visibility of the sign-in form
|
|
if (!$ae_sess.show__sign_in_out__fields) {
|
|
$ae_sess.app_cfg.show_element__passcode_input = true;
|
|
}
|
|
}}
|
|
>
|
|
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
|
{#if $ae_sess.show__sign_in_out__fields}
|
|
<CircleX class="m-1" />
|
|
<span
|
|
class="
|
|
cfg_text
|
|
hidden
|
|
group-hover:inline
|
|
"
|
|
>
|
|
Hide Sign-In
|
|
</span>
|
|
{:else}
|
|
<User class="mx-1 inline-block text-gray-500" />
|
|
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
|
{/if}
|
|
{:else if $ae_sess.show__sign_in_out__fields}
|
|
<CircleX class="mx-1" />
|
|
<span
|
|
class="
|
|
cfg_text
|
|
hidden
|
|
group-hover:inline
|
|
"
|
|
>
|
|
Hide Sign-In
|
|
</span>
|
|
{:else}
|
|
<!-- <LockKeyhole size="1.25em" class="mx-1 inline-block text-gray-500" /> -->
|
|
<UserLock size="1.25em" class="m-1 inline-block text-gray-500" />
|
|
<!-- <User class="mx-1 inline-block text-gray-500" /> -->
|
|
<span
|
|
class="
|
|
cfg_text
|
|
hidden
|
|
group-hover:inline
|
|
"
|
|
>
|
|
User Sign-In
|
|
</span>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Change Password Modal -->
|
|
<!-- They should be able to see their password (text) -->
|
|
<!-- autocomplete="off" -->
|
|
{#if $ae_sess.show__modal_change_password}
|
|
<Modal
|
|
title="Change Password"
|
|
bind:open={$ae_sess.show__modal_change_password}
|
|
autoclose={false}
|
|
placement="top-center"
|
|
size="md"
|
|
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 mx-auto w-full divide-y"
|
|
>
|
|
<div class="modal-box flex flex-col gap-2 items-center justify-center">
|
|
<!-- If the user is a global Manager or Super then they can change the password for any user. Otherwise, they can only change their own password. Show email address field for a quick lookup to get the user.id. -->
|
|
<div class="flex flex-col flex-wrap gap-2">
|
|
<span class="text-sm text-gray-500">
|
|
Your Username: {$ae_loc?.user?.username ?? '-- not set --'}
|
|
</span>
|
|
<span class="text-sm text-gray-500">
|
|
Your Email: {$ae_loc?.user?.email ?? '-- not set --'}
|
|
</span>
|
|
<span class="text-sm text-gray-500">
|
|
Your User ID: {$ae_loc?.user_id ?? '-- not set --'}
|
|
</span>
|
|
</div>
|
|
{#if $ae_loc?.manager_access}
|
|
<div class="flex flex-row flex-wrap gap-2">
|
|
<input
|
|
type="text"
|
|
name="user_email"
|
|
autocomplete="off"
|
|
placeholder="User's email address"
|
|
bind:value={user_email}
|
|
class="input input-bordered w-48 max-w-full"
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Allow for new password and confirm password fields. -->
|
|
<div class="flex flex-row flex-wrap gap-2 items-center justify-center">
|
|
<span class="flex flex-row flex-wrap gap-1">
|
|
<input
|
|
required
|
|
type={show_password_text}
|
|
autocomplete="new-password"
|
|
name="new_password"
|
|
placeholder="New Password"
|
|
bind:value={new_password}
|
|
class="input input-bordered w-48 max-w-full required:border-red-500"
|
|
/>
|
|
<input
|
|
required
|
|
type={show_password_text}
|
|
autocomplete="off"
|
|
name="confirm_password"
|
|
placeholder="Confirm Password"
|
|
bind:value={confirm_password}
|
|
class="input input-bordered w-48 max-w-full required:border-red-500"
|
|
/>
|
|
</span>
|
|
|
|
<!-- Show/Hide Password Text -->
|
|
<button
|
|
type="button"
|
|
class="btn btn-icon preset-tonal-secondary"
|
|
onclick={() => {
|
|
// const inputs = document.querySelectorAll('.modal-box input[type="password"]');
|
|
// inputs.forEach((input) => {
|
|
// input.type = input.type === 'password' ? 'text' : 'password';
|
|
// });
|
|
if (show_password_text === 'password') {
|
|
show_password_text = 'text';
|
|
} else {
|
|
show_password_text = 'password';
|
|
}
|
|
}}
|
|
title="Show/Hide Password Text"
|
|
>
|
|
{#if show_password_text === 'password'}
|
|
<EyeOff class="mx-1" />
|
|
{:else}
|
|
<Eye class="mx-1" />
|
|
{/if}
|
|
<!-- <Eye class="mx-1"/> -->
|
|
<!-- <span class="hidden sm:inline">
|
|
Show/Hide
|
|
</span> -->
|
|
</button>
|
|
|
|
<div>
|
|
<span class="text-sm text-warning-800 dark:text-gray-200">
|
|
Password must be at <strong>least 10 characters</strong> long. It is recommend
|
|
that you use a pass phrase (multiple words as your password) or a complex password
|
|
(uppercase letter, lowercase letters, numbers, and special characters).
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-row flex-wrap gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn preset-filled-warning-500"
|
|
onclick={handle_change_password}
|
|
disabled={is_changing_password}
|
|
>
|
|
<Key class="mx-1" />
|
|
Change Password
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-secondary"
|
|
onclick={() => ($ae_sess.show__modal_change_password = false)}
|
|
>
|
|
<CircleX class="mx-1" />
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
{/if}
|
|
|
|
<style lang="scss">
|
|
</style>
|