Standardize JWT authentication and finalize Activity Log V3 migration

This commit is contained in:
Scott Idem
2026-01-07 17:43:23 -05:00
parent 87023e7483
commit ea0d57658f
12 changed files with 199 additions and 109 deletions

View File

@@ -65,29 +65,35 @@ export const get_object = async function get_object({
delete api_cfg['headers']['x-no-account-id'];
}
// Clean the headers
// Clean and merge headers
const headers_cleaned: key_val = {};
for (const prop in headers) {
const merged_headers = { ...api_cfg['headers'], ...headers };
// Standardize all headers to kebab-case and ensure string values
for (const prop in merged_headers) {
const prop_cleaned = prop.replaceAll('_', '-');
if (typeof headers[prop] != 'string') {
headers[prop] = JSON.stringify(headers[prop]);
}
headers_cleaned[prop_cleaned] = headers[prop];
if (log_lvl > 1) {
console.log(`${prop_cleaned}: ${headers_cleaned[prop_cleaned]}`);
let value = merged_headers[prop];
if (value === null || value === undefined) continue;
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
headers_cleaned[prop_cleaned] = value;
}
headers = headers_cleaned;
// Auto-inject Authorization header if JWT is present but header is missing
const jwt = headers_cleaned['jwt'] || headers_cleaned['JWT'] || api_cfg['jwt'];
if (jwt && !headers_cleaned['Authorization'] && !headers_cleaned['authorization']) {
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
}
if (log_lvl > 1) {
console.log('All headers cleaned:', headers);
console.log('Final cleaned headers:', headers_cleaned);
}
const fetchOptions: RequestInit = {
method: 'GET',
headers: {
...api_cfg['headers'],
...headers
},
headers: headers_cleaned,
signal: controller.signal
};

View File

@@ -5,10 +5,11 @@ export const post_blob_percent_completed = temp_post_blob_percent_completed;
export const temp_post_object_percent_completed = 0;
export const post_object_percent_completed = temp_post_object_percent_completed;
// Updated 2024-05-23
// Updated 2026-01-07
export const post_object = async function post_object({
api_cfg = null,
endpoint = '',
headers = {},
params = {},
data = {},
form_data = null,
@@ -23,6 +24,7 @@ export const post_object = async function post_object({
}: {
api_cfg: any;
endpoint: string;
headers?: any;
params?: any;
data?: any;
form_data?: any;
@@ -68,27 +70,38 @@ export const post_object = async function post_object({
// console.log('HERE!! API POST 2');
// Clean the headers
// Clean and merge headers
const headers_cleaned: Record<string, string> = {};
for (const prop in api_cfg['headers']) {
const merged_headers = { ...api_cfg['headers'], ...headers };
for (const prop in merged_headers) {
const prop_cleaned = prop.replaceAll('_', '-');
headers_cleaned[prop_cleaned] = api_cfg['headers'][prop];
let value = merged_headers[prop];
if (value === null || value === undefined) continue;
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
headers_cleaned[prop_cleaned] = value;
}
// console.log('HERE!! API POST 3');
// Auto-inject Authorization header if JWT is present but header is missing
const jwt = headers_cleaned['jwt'] || headers_cleaned['JWT'] || api_cfg['jwt'];
if (jwt && !headers_cleaned['Authorization'] && !headers_cleaned['authorization']) {
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
}
if (form_data) {
// headers_cleaned['Content-Type'] = 'multipart/form-data';
delete headers_cleaned['content-type'];
delete headers_cleaned['Content-Type'];
delete headers_cleaned['content-type']; // Just in case
delete headers_cleaned['Content-type']; // Just in case
console.log('Form Data:', form_data);
} else {
headers_cleaned['Content-Type'] = 'application/json';
}
if (log_lvl > 1) {
console.log('Cleaned Headers:', headers_cleaned);
console.log('Final cleaned headers:', headers_cleaned);
}
// console.log('HERE!! API POST 4');
@@ -108,6 +121,8 @@ export const post_object = async function post_object({
signal: controller.signal
};
console.log('Final fetch options for post_object:', fetchOptions);
if (log_lvl > 1) {
console.log('Fetch Options:', fetchOptions);
}

View File

@@ -76,6 +76,40 @@ export interface Site_Domain {
* --- SITE CRUD ---
*/
export async function lookup_site_domain({
api_cfg,
fqdn,
view = 'default',
log_lvl = 0
}: {
api_cfg: any;
fqdn: string;
view?: string;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** lookup_site_domain() *** fqdn=${fqdn}`);
}
// We use get_ae_obj_id_crud because we are looking up by a unique field (fqdn) rather than ID.
// This is the older method that uses the /crud/site/domain/:id endpoint.
const result = await api.get_ae_obj_id_crud({
api_cfg,
no_account_id: true,
obj_type: 'site_domain',
obj_id: fqdn,
use_alt_table: true,
use_alt_base: true,
log_lvl
});
if (result) {
return result;
}
return null;
}
// Updated 2026-01-06
// Updated 2026-01-06
export async function lookup_site_domain_v3({
@@ -94,7 +128,7 @@ export async function lookup_site_domain_v3({
}
const search_query = {
and: [{ field: 'fqdn', op: 'eq', value: fqdn }]
q: fqdn
};
// We use search because we are looking up by a unique field (fqdn) rather than ID.

View File

@@ -38,6 +38,7 @@ export async function auth_ae_obj__username_password({
}
params['username'] = username; // Required
params['password'] = password; // Required
params['inc_jwt'] = true; // Request a JWT in the response
if (log_lvl > 1) {
console.log(`auth_ae_obj__username_password() - params:`, params);
}
@@ -104,6 +105,7 @@ export async function auth_ae_obj__user_id_user_auth_key({
params['user_id'] = user_id; // Required
params['auth_key'] = user_auth_key; // Required
params['inc_jwt'] = true; // Request a JWT in the response
if (log_lvl > 1) {
console.log(`auth_ae_obj__user_id_user_auth_key() - params:`, params);
}

View File

@@ -665,7 +665,7 @@ export const delete_ae_obj_id_crud = async function delete_ae_obj_id_crud({
/* BEGIN: Hosted File Related */
// Updated 2023-08-17
// Updated 2026-01-07
export const download_hosted_file = async function download_hosted_file({
api_cfg,
hosted_file_id,
@@ -693,6 +693,17 @@ export const download_hosted_file = async function download_hosted_file({
}
params['return_file'] = true;
// Inject JWT into URL parameters if available
if (api_cfg.jwt) {
params['jwt'] = api_cfg.jwt;
} else if (api_cfg.headers?.['authorization']) {
// Fallback: extract from header if present
const auth_header = api_cfg.headers['authorization'];
if (auth_header.startsWith('Bearer ')) {
params['jwt'] = auth_header.substring(7);
}
}
const hosted_file_download_get_promise = await api.get_object({
api_cfg: api_cfg,
endpoint: endpoint,

View File

@@ -64,6 +64,7 @@
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
@@ -112,6 +113,7 @@
function sign_out() {
// Clear the session information
$ae_loc.jwt = null;
$ae_loc.person_id = null;
$ae_loc.person = {};
$ae_loc.user_id = null;

View File

@@ -131,6 +131,8 @@ const ae_app_local_data_defaults: key_val = {
user_email: null, // Currently used with Sponsorships only?
user_access_type: null, // Used to revert back to the user's access type after quick access (temporarily escalate permissions) turned off.
jwt: null, // JSON Web Token for authenticated API requests
// Added 2025-04-04
person_id: null, // The current person_id of the logged-in user (if any)
person: {

View File

@@ -2,7 +2,7 @@
// console.log(`ae_root +layout.ts: start`);
import { error } from '@sveltejs/kit';
import { lookup_site_domain_v3 } from '$lib/ae_core/ae_core__site';
import { lookup_site_domain } from '$lib/ae_core/ae_core__site';
import type { key_val } from '$lib/stores/ae_stores';
import {
@@ -31,8 +31,8 @@ const ae_api_init: key_val = {
ver: '2024-08-11_11',
base_url: api_base_url,
base_url_bak: api_base_url_bak,
api_secret_key: api_secret_key,
api_secret_key_bak: api_secret_key,
api_secret_key: api_secret_key,
api_secret_key_bak: api_secret_key,
api_crud_super_key: api_crud_super_key,
headers: {},
account_id: ae_account_id
@@ -83,7 +83,7 @@ export async function load({ fetch, params, parent, route, url }) {
ae_loc: {},
ae_api: ae_api_init,
ae_ds: {},
ae_hub: {},
ae_hub: {},
ae_m_sponsorships: {},
ae_m_events: {},
ae_m_events_speakers: {},
@@ -102,11 +102,11 @@ export async function load({ fetch, params, parent, route, url }) {
};
const fqdn = url.host;
const result = await lookup_site_domain_v3({
const result = await lookup_site_domain({
api_cfg: ae_api_init,
fqdn,
view: 'base',
view: 'base',
log_lvl
}).catch((err) => {
console.log('Site lookup failed in root layout.', err);
@@ -128,11 +128,13 @@ export async function load({ fetch, params, parent, route, url }) {
ae_api_init['account_id'] = json_data.account_id_random;
ae_api_init['headers']['x-account-id'] = json_data.account_id_random;
ae_api_init['headers']['x-no-account-id'] = null;
// ae_api_init['headers']['x-no-account-id'] = null;
ae_api_headers['x-account-id'] = ae_account_id;
ae_loc_init['account_id'] = json_data.account_id_random;
ae_loc_init['account_code'] = json_data.account_code;
ae_loc_init['account_name'] = json_data.account_name;
ae_loc_init['account_code'] = json_data.account_code;
ae_loc_init['account_name'] = json_data.account_name;
ae_loc_init['site_id'] = json_data.site_id_random;
ae_loc_init['site_domain_id'] = json_data.site_domain_id_random;
@@ -142,17 +144,17 @@ export async function load({ fetch, params, parent, route, url }) {
ae_loc_init['site_google_tracking_id'] = json_data.google_tracking_id;
ae_loc_init['site_access_code_kv'] = json_data.access_code_kv_json;
ae_loc_init['site_cfg_json'] = json_data.cfg_json;
ae_loc_init['site_access_key'] = json_data.access_key;
ae_loc_init['site_domain_access_key'] = json_data.site_domain_access_key;
ae_loc_init['site_access_key'] = json_data.access_key;
ae_loc_init['site_domain_access_key'] = json_data.site_domain_access_key;
ae_loc_init['base_url'] = url.origin;
ae_loc_init['hostname'] = url.hostname;
if (!ae_loc_init['site_access_key'] && !ae_loc_init['site_domain_access_key']) {
ae_loc_init['key_checked'] = true;
ae_loc_init['allow_access'] = true;
ae_loc_init['key_checked'] = true;
ae_loc_init['allow_access'] = true;
} else {
const access_key = url.searchParams.get('key');
const access_key = url.searchParams.get('key');
if (access_key) {
if (log_lvl) {