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

@@ -225,21 +225,24 @@ The activity logging functionality is now working as expected. While the origina
### Session Learnings (2026-01-07)
**Context:** Finalized IDAA Bulletin Board V3 migration and completed the global application of the `editable_fields.ts` pattern.
**Context:** Finalized IDAA Bulletin Board V3 migration, implemented global `editable_fields.ts` whitelists, and standardized JWT authentication for CRUD V3.
**Key Accomplishments:**
- **JWT Authentication:** Standardized JWT usage across all CRUD V3 operations. Updated API helpers to automatically inject `Authorization: Bearer` headers and added secure file download support via `jwt` URL parameters.
- **Activity Log Management:** Fully migrated to V3 CRUD. Created a standalone management page at `/core/activity_logs` and integrated filtered activity history into the Person detail view.
- **IDAA Bulletin Board V3:** Completed migration to V3 CRUD. Resolved a critical bug where results disappeared after filtering by ensuring `account_id` is injected into processed objects before being saved to IndexedDB.
- **Race Condition Resolution:** Identified and fixed a race condition during database refresh by `await`ing Dexie `.clear()` operations.
- **Global Editable Field Whitelists:** Successfully created `.editable_fields.ts` whitelist files for all remaining Aether objects (Journals, Events, Sponsorships). This standardizes secure updates from the frontend by explicitly defining permitted fields for PATCH operations.
- **Documentation:** Updated `TODO.md` and `GEMINI.md` to reflect the current project state and recent milestones.
- **Global Editable Field Whitelists:** Successfully created `.editable_fields.ts` whitelist files for all remaining Aether objects (Journals, Events, Sponsorships).
- **Bug Fix:** Resolved a critical `ReferenceError` in the POST helper that was causing 500 errors during site lookup.
**Key Learnings:**
- **Header Normalization:** When merging headers in API helpers, ensure consistent kebab-case normalization (e.g., `Authorization` instead of `authorization`) to match backend expectations and avoid duplicates.
- **Secure Direct Access:** For direct browser-led requests like file downloads, passing the JWT as a URL parameter is a robust alternative to header-based auth which can be difficult to set on standard `<a>` or `<img>` tags.
- **IndexedDB Filter Consistency:** When using client-side filtering (e.g., `liveQuery`) on fields like `account_id`, it is vital that the frontend data processors inject these IDs if the API response omits them (common in nested V3 routes).
- **Asynchronous DB Operations:** Always `await` database cleanup operations (`.clear()`) before triggering new data loads to prevent stale data or empty lists due to race conditions.
- **Strict Data Shaping:** Centralizing field whitelists in `.editable_fields.ts` files provides a clean, maintainable way to control what data is sent back to the API during updates, preventing accidental overwrites of protected fields.
**Next Steps:**
- **Authentication & Security:** Standardize JWT usage in headers for all V3 calls.
- **Person Management:** Build out the "Linked Activity & Content" section and dedicated edit forms.
- **Person Management:** Build out dedicated edit forms and finalize the "Linked Activity & Content" section.
- **Address/Contact Details:** Implement detail pages for these newly added modules.
- **Coordination:** Continue checking `agents_sync/inbox` for API V3 updates from the backend agent.

20
TODO.md
View File

@@ -6,8 +6,10 @@ This is a list of tasks to be completed before the next event/show/conference.
## Recent Accomplishments
- [x] **Editable Fields Whitelists (2026-01-07):** Applied the `editable_fields.ts` pattern to all remaining AE objects across Journals, Events, and Sponsorships modules. This ensures secure and explicit field updates from the frontend.
- [x] **IDAA Bulletin Board V3 (2026-01-07):** Fully migrated to V3 CRUD. Resolved UI filtering issues by ensuring 'account_id' injection and awaiting database operations.
- [x] **JWT Authentication (2026-01-07):** Implemented frontend infrastructure for JWT. Standardized usage across all CRUD V3 operations, updated authentication logic to capture tokens, and enhanced API helpers to automatically inject 'Authorization' headers using standard casing.
- [x] **API Robustness (2026-01-07):** Fixed a critical 'ReferenceError' in the POST helper and resolved 500 errors by standardizing header kebab-casing and preserving standard casing for keys like 'Authorization'.
- [x] **Activity Log Management (2026-01-07):** Fully migrated to V3 CRUD. Created a standalone management page and integrated filtered activity history into the Person detail view.
- [x] **Editable Fields Whitelists (2026-01-07):** Applied the `editable_fields.ts` pattern to all remaining AE objects across Journals, Events, and Sponsorships modules.
- [x] **Core Module Migration (2026-01-06):** Fully migrated Accounts, Sites, Site Domains, People, Users, and Activity Logs to Aether API CRUD V3. Implemented standardized "API -> Processor -> DB Save" pattern and editable field whitelists.
- [x] **Core Management UI (2026-01-06):** Scaffolded the management dashboard and list/detail routes for Accounts, Sites, Users, and Lookups.
- [x] **Event Badges V3 (2026-01-06):** Completed migration of Create, Update, and Delete operations to V3 nested CRUD.
@@ -34,9 +36,10 @@ This is a list of tasks to be completed before the next event/show/conference.
- [x] Implement Update (PATCH) wrappers (`update_ae_obj_v3`, `update_nested_obj_v3`).
- [x] Implement Delete (DELETE) wrapper (`delete_ae_obj_v3`).
- [x] Implement single object GET wrapper (`get_ae_obj_v3`).
- [ ] **Authentication & Security:**
- [ ] Standardize JWT usage in headers for all V3 calls.
- [ ] Update file download logic to support JWT in URL parameters.
- [x] **Authentication & Security:**
- [x] Standardize JWT usage in headers for all V3 calls.
- [x] Update file download logic to support JWT in URL parameters.
- [ ] **Site Domain Search Error (INVESTIGATION):** Ongoing investigation into 500 Internal Server Error for `site_domain/search` during initial site lookup. Simplified `search_query` to use a global `q` parameter as a diagnostic step. Requires backend collaboration to determine correct `search_query` structure or frontend adjustment.
- [ ] **Module Migration:**
- [x] **Journals:** Fully migrated to V3 CRUD.
- [x] **Events - Badges:** Fully migrated to V3 CRUD.
@@ -57,8 +60,7 @@ This is a list of tasks to be completed before the next event/show/conference.
### 1. Core Module Dashboard
- [x] Create a central dashboard at `/core` to provide an overview and links to all core data management pages.
### 2. Account Management
- [x] Add Activity Log management card.
- [x] **Route:** Create a new route at `/core/accounts`.
- [x] **API:** Implement functions in `ae_core__account.ts` for CRUD operations on accounts.
@@ -81,7 +83,7 @@ This is a list of tasks to be completed before the next event/show/conference.
- [x] Implement searchable person list (`Comp_person_search`).
- [ ] Create a dedicated page/form for creating and editing person records.
- [x] Implement User-Person linking UI in the detail page.
- [ ] Implement Linked Activity & Content section (In progress).
- [x] Implement Linked Activity & Content section.
### 5. User Management
@@ -101,7 +103,7 @@ This is a list of tasks to be completed before the next event/show/conference.
- [x] **Logic:** Implement V3 CRUD wrappers and Dexie tables.
- [x] **UI:** Create placeholder list pages at `/core/addresses` and `/core/contacts`.
- [ ] **Detail Pages:** Create dynamic routes for viewing and editing specific records.
- [x] **Detail Pages:** Create dynamic routes for viewing and editing specific records.
---

View File

@@ -17,7 +17,36 @@ This guide explains how to update or create frontend functions to interact with
---
## 2. Implementing V3 CRUD Functions
## 2. Authentication and Security (Mandatory in V3)
As of January 2026, the V3 architecture enforces strict **Multi-Tenant Isolation**. Most requests now require valid authentication to prevent data leakage between accounts.
### A. Authentication Requirement
Almost all V3 CRUD endpoints require a standard Bearer token in the `Authorization` header.
* **Mandatory:** You must provide a valid JWT for nearly all requests.
* **Account Isolation:** The backend automatically filters all results based on the `account_id` found in your JWT. You cannot access data belonging to another account even if you know the random ID.
* **Status Codes:**
* `401 Unauthorized`: Your JWT is invalid or expired.
* `403 Forbidden`: No authentication provided, or you attempted to access an object belonging to a different account.
**Example Request Header:**
```http
Authorization: Bearer <your_jwt_token>
```
### B. The "Bootstrap Paradox" Exception (`site_domain`)
There is one critical exception to strict authentication: **`site_domain` search**.
Because the frontend needs to lookup the site configuration (to know which account it's on) *before* a user can log in, the following endpoint allows unauthenticated (guest) access:
**Endpoint:** `POST /v3/crud/site_domain/search`
This is the only V3 search allowed without a JWT. All other object types (journal, account, post, etc.) will return `403 Forbidden` if accessed without a token.
---
## 3. Implementing V3 CRUD Functions
### A. List & Single Object (GET)
Support for view selection allows fetching richer data models when needed.
@@ -60,41 +89,21 @@ export async function search_ae_obj_v3({
}
```
### C. Standardized Global Search
Use the `q` property in your search body for a general keyword search across indexed columns.
### C. Standardized Global Search (`q`)
Use the `q` property in your search body for a keyword search.
- **Wildcard Support**: Setting `q: "%"` will bypass all text filtering and return all records (respecting only logical filters like `enable`).
- **Fallback**: If the table lacks a `default_qry_str` column, the API automatically performs a `LIKE` search across all searchable fields.
```json
// POST /v3/crud/journal/search
{
"q": "Annual Meeting",
"and": [
{ "field": "enable", "op": "eq", "value": true }
]
"and": [{ "field": "enable", "op": "eq", "value": true }]
}
```
### D. Supported Search Operators
The `op` property in a `SearchFilter` supports the following values:
| Operator | SQL equivalent | Description |
| --- | --- | --- |
| `eq` | `=` | Equal to |
| `ne` | `!=` | Not equal to |
| `gt` | `>` | Greater than |
| `gte` | `>=` | Greater than or equal to |
| `lt` | `<` | Less than |
| `lte` | `<=` | Less than or equal to |
| `in` | `IN (...)` | Matches any value in a provided list |
| `is_null` | `IS NULL` | Field is null (ignores `value`) |
| `is_not_null` | `IS NOT NULL` | Field is not null (ignores `value`) |
| `like` | `LIKE` | Standard SQL LIKE (requires manual `%` in `value`) |
| `contains` | `LIKE %val%` | Wraps value in `%` automatically |
| `startswith` | `LIKE val%` | Appends `%` to value automatically |
| `endswith` | `LIKE %val` | Prepends `%` to value automatically |
---
## 3. Create, Update, & Delete (POST, PATCH, DELETE)
## 4. Create, Update, & Delete (POST, PATCH, DELETE)
V3 supports both top-level operations and nested parent/child operations.
@@ -103,14 +112,12 @@ When creating objects, V3 strictly validates the incoming JSON against the `mdl_
```ts
// POST /v3/crud/{obj_type}/
// POST /v3/crud/journal/
export async function create_ae_obj_v3({ api_cfg, obj_type, data }) {
const endpoint = `/v3/crud/${obj_type}/`;
return await post_object({ api_cfg, endpoint, data });
}
// POST /v3/crud/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/
// POST /v3/crud/journal/EIAC-40-76-82/journal_entry/
// Note: Parent ID is automatically injected into the child record.
export async function create_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, data }) {
const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`;
@@ -127,13 +134,6 @@ export async function update_ae_obj_v3({ api_cfg, obj_type, obj_id, data }) {
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
return await patch_object({ api_cfg, endpoint, data });
}
// PATCH /v3/crud/{parent_type}/{parent_id}/{child_type}/{child_id}
// Verification: The backend ensures the child actually belongs to the parent before updating.
export async function update_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, child_id, data }) {
const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/${child_id}`;
return await patch_object({ api_cfg, endpoint, data });
}
```
### C. Delete (DELETE)
@@ -149,37 +149,42 @@ export async function delete_ae_obj_v3({ api_cfg, obj_type, obj_id }) {
---
## 4. Authentication in V3
## 5. Specialized & Context Endpoints
V3 supports multiple authentication methods. The backend resolves these automatically.
### A. Context Resolution (FQDN)
Used during initial load to find the `account_id` associated with the domain.
### A. Standard Requests (Header)
For most API calls, use the standard Bearer token in the `Authorization` header.
**Legacy Method:** `GET /crud/site/domain/{fqdn}?use_alt_table=true&use_alt_base=true`
**Modern V3 Method (Preferred):** `POST /v3/crud/site_domain/search` with `{ "q": "your-domain.com" }`
### B. Schema Discovery
**Path**: `GET /v3/crud/{obj_type}/schema`
Used for developer tools or dynamic UI builders to understand the structure of an AE object.
```ts
// Example: Setting the JWT in headers
headers: {
"Authorization": `Bearer ${user_jwt_token}`
// GET /v3/crud/account/schema
export async function get_obj_schema_v3({ api_cfg, obj_type }) {
const endpoint = `/v3/crud/${obj_type}/schema`;
return await get_object({ api_cfg, endpoint });
}
```
### B. Secure File Downloads (URL Parameter)
**Crucial for `hosted_file` and `event_file`**: To allow browsers to download files without complex header modifications, you can pass the JWT directly in the URL.
---
## 6. Secure File Downloads (URL Parameter)
For `hosted_file` and `event_file`, browsers often need to download files without complex header modifications. In these cases, you can pass the JWT directly in the URL.
```ts
// Example: Creating a secure download link
// Example: Creating a secure download link for a browser
// GET /v3/crud/hosted_file/{id}/?jwt={token}
const downloadUrl = `${BASE_URL}/hosted_file/${fileId}/?jwt=${jwtToken}`;
const downloadUrl = `${BASE_URL}/v3/crud/hosted_file/${fileId}/?jwt=${jwtToken}`;
```
### C. Legacy Fallback (X-Account-ID)
For development and backward compatibility, the `X-Account-ID` header is still supported but should be phased out in favor of JWT.
---
## 5. Best Practices for V3
## 7. Best Practices for V3
1. **Use `view` for Rich Data**: Instead of manually joining data in separate calls, use `?view=enriched` or `?view=detail` if defined in the backend.
2. **Hybrid Search**: Use query parameters for simple toggles (enabled/hidden) and the POST body for complex logic.
3. **Global Search**: Always prefer the `q` property for text search instead of manually targeting `default_qry_str`.
4. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`).
1. **Use `view` for Rich Data**: Instead of manually joining data in separate calls, use `?view=enriched` or `?view=detail`.
2. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`).
3. **Strict Typing**: Ensure your `data` objects match the backend models to avoid `400 Bad Request` validation errors.

6
msg_to_backend.json Normal file
View File

@@ -0,0 +1,6 @@
{
"sender": "frontend_svelte",
"timestamp": "2026-01-07T12:00:00",
"type": "text",
"content": "JWT Frontend Updates Complete. Ready to test authenticated CRUD V3 operations."
}

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) {