From ea0d57658fc0dcbff317a6d26841d20403911b38 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Wed, 7 Jan 2026 17:43:23 -0500 Subject: [PATCH] Standardize JWT authentication and finalize Activity Log V3 migration --- GEMINI.md | 15 ++- TODO.md | 20 +-- documentation/V3_FRONTEND_API_GUIDE.md | 115 +++++++++--------- msg_to_backend.json | 6 + src/lib/ae_api/api_get_object.ts | 34 +++--- src/lib/ae_api/api_post_object.ts | 31 +++-- src/lib/ae_core/ae_core__site.ts | 36 +++++- src/lib/ae_core/core__user.ts | 2 + src/lib/api/api.ts | 13 +- .../app_components/e_app_sign_in_out.svelte | 2 + src/lib/stores/ae_stores.ts | 2 + src/routes/+layout.ts | 32 ++--- 12 files changed, 199 insertions(+), 109 deletions(-) create mode 100644 msg_to_backend.json diff --git a/GEMINI.md b/GEMINI.md index 251aa9fa..bbbe9f3c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -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 `` or `` 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. diff --git a/TODO.md b/TODO.md index e5b77d2a..5fa5cd51 100644 --- a/TODO.md +++ b/TODO.md @@ -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. --- diff --git a/documentation/V3_FRONTEND_API_GUIDE.md b/documentation/V3_FRONTEND_API_GUIDE.md index 5bb0b855..6fbeee78 100644 --- a/documentation/V3_FRONTEND_API_GUIDE.md +++ b/documentation/V3_FRONTEND_API_GUIDE.md @@ -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 +``` + +### 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`). \ No newline at end of file +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. diff --git a/msg_to_backend.json b/msg_to_backend.json new file mode 100644 index 00000000..272e978c --- /dev/null +++ b/msg_to_backend.json @@ -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." +} diff --git a/src/lib/ae_api/api_get_object.ts b/src/lib/ae_api/api_get_object.ts index f7045b88..f969d0ff 100644 --- a/src/lib/ae_api/api_get_object.ts +++ b/src/lib/ae_api/api_get_object.ts @@ -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 }; diff --git a/src/lib/ae_api/api_post_object.ts b/src/lib/ae_api/api_post_object.ts index a1f1dd9a..4b02d9e9 100644 --- a/src/lib/ae_api/api_post_object.ts +++ b/src/lib/ae_api/api_post_object.ts @@ -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 = {}; - 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); } diff --git a/src/lib/ae_core/ae_core__site.ts b/src/lib/ae_core/ae_core__site.ts index 0d54f119..69a5f40e 100644 --- a/src/lib/ae_core/ae_core__site.ts +++ b/src/lib/ae_core/ae_core__site.ts @@ -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. diff --git a/src/lib/ae_core/core__user.ts b/src/lib/ae_core/core__user.ts index 071d8743..2ce615c0 100644 --- a/src/lib/ae_core/core__user.ts +++ b/src/lib/ae_core/core__user.ts @@ -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); } diff --git a/src/lib/api/api.ts b/src/lib/api/api.ts index 8bdb72db..1a8863c3 100644 --- a/src/lib/api/api.ts +++ b/src/lib/api/api.ts @@ -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, diff --git a/src/lib/app_components/e_app_sign_in_out.svelte b/src/lib/app_components/e_app_sign_in_out.svelte index 7ce5f6d7..cf6e8d86 100644 --- a/src/lib/app_components/e_app_sign_in_out.svelte +++ b/src/lib/app_components/e_app_sign_in_out.svelte @@ -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; diff --git a/src/lib/stores/ae_stores.ts b/src/lib/stores/ae_stores.ts index 79ad1bd9..c8fe67fd 100644 --- a/src/lib/stores/ae_stores.ts +++ b/src/lib/stores/ae_stores.ts @@ -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: { diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 45e3316e..056708b5 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -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) {