From f2c426b595c332ffc70b3d3847e6c8105c414a33 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 24 Feb 2026 16:38:36 -0500 Subject: [PATCH] test: combine and modernize private-network checks; remove legacy disabled variants --- documentation/GUIDE__V3_FRONTEND_API.md | 132 +++++++++++ .../ae_archives__archive_content.ts | 11 +- .../ae_events__event_badge_template.ts | 177 ++++++++------- src/lib/ae_events/ae_events__event_device.ts | 211 +++++++++--------- .../ae_events/ae_events__event_location.ts | 149 ++++++++----- .../ae_events__event_presentation.ts | 59 ++--- .../ae_events/ae_events__event_presenter.ts | 153 ++++++++----- src/lib/ae_events/ae_events__event_session.ts | 121 +++++++--- src/lib/ae_events/ae_events__exhibit.ts | 116 ++++++---- .../ae_events/ae_events__exhibit_tracking.ts | 149 +++++++------ tests/disabled/private_network_cdp.test.ts | 62 ----- .../disabled/private_network_combined.test.ts | 84 +++++++ .../private_network_detection.test.ts | 62 ----- tests/disabled/v3_api_security.test.ts | 105 --------- 14 files changed, 877 insertions(+), 714 deletions(-) create mode 100644 documentation/GUIDE__V3_FRONTEND_API.md delete mode 100644 tests/disabled/private_network_cdp.test.ts create mode 100644 tests/disabled/private_network_combined.test.ts delete mode 100644 tests/disabled/private_network_detection.test.ts delete mode 100644 tests/disabled/v3_api_security.test.ts diff --git a/documentation/GUIDE__V3_FRONTEND_API.md b/documentation/GUIDE__V3_FRONTEND_API.md new file mode 100644 index 00000000..a0bc1b2c --- /dev/null +++ b/documentation/GUIDE__V3_FRONTEND_API.md @@ -0,0 +1,132 @@ +# Aether API V3 Frontend Integration Guide (Svelte/TypeScript) + +This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints. + +--- + +## 1. Authentication and Security (Mandatory) + +V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Requests require two levels of validation. + +### A. The "Entry Ticket" (API Key) +**Mandatory for all requests.** identifies the application or client. +* **Header:** `x-aether-api-key: ` +* **Status Code:** `403 Forbidden` if missing or invalid. + +### B. The "Visa" (Account Context) +Required for any non-public data (Journals, Badges, Users, etc.). +1. **Standard Access**: Provide the `x-account-id` (the random string ID). + * **Header:** `x-account-id: ` +2. **Administrative Bypass**: For authorized scripts needing global access. + * **Header:** `x-no-account-id: bypass` +3. **Token Access**: Provide a **JWT** in the query string. + * **Query Param:** `?jwt=` + +> [!CAUTION] +> **UNSUPPORTED HEADERS:** The header `x-aether-api-token` is **NOT recognized** by the V3 API. If you send it, the backend will treat you as a guest and block access to private data. + +--- + +## 2. Bootstrapping (The FQDN Handshake) + +When the frontend first loads and doesn't know the `account_id`, it performs a "handshake" using its domain name. + +**Endpoint:** `POST /v3/crud/site_domain/search` +**Body:** +```json +{ + "and": [ + { "field": "fqdn", "op": "eq", "value": "demo.oneskyit.com" } + ] +} +``` +**Results:** +* Returns 200 + a list containing the `account_id` (random string ID) and `site_id` (random string ID). +* ** デザイン Choice:** If the domain is not found, it returns **200 OK with an empty list `[]`**. It is NOT a 404. + +--- + +## 3. Standard CRUD Patterns + +### A. GET by ID +Used when the ID is known. +* **Endpoint:** `GET /v3/crud/{obj_type}/{id}` +* **Security:** Returns 403 if the record doesn't belong to your `x-account-id`. + +### B. POST Search +The primary way to retrieve data. +* **Endpoint:** `POST /v3/crud/{obj_type}/search` +* **Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects. + +### C. POST Create / PATCH Update +Modify data in the system. +* **Endpoints:** + * `POST /v3/crud/{obj_type}/` + * `PATCH /v3/crud/{obj_type}/{id}` +* **Strict Mode (Default):** The API validates your payload against the Pydantic model. If you send fields that do not exist in the model, the database might return a 400 "Unknown column" error. +* **Permissive Mode (Header):** To allow the frontend to send "extra" fields (like local UI state) without causing errors, use the following header: + * **Header:** `x-ae-ignore-extra-fields: true` + * **Behavior:** When set to `true`, the backend will automatically strip any fields from the payload that are not defined in the object's model before attempting to save to the database. + +--- + +## 4. V3 Uniform Lookup System + +The V3 Lookup system provides a hierarchical, deduplicated interface for standardized tables (Countries, Timezones, etc.). It supports global defaults, account overrides, and site-specific whitelisting. + +### A. List Lookups +Retrieve a ranked and filtered list of lookup items. +* **Endpoint:** `GET /v3/lookup/{lu_type}/list` +* **Available Types:** `country`, `country_subdivision`, `time_zone` +* **Parameters:** + * `site_id` (Optional): Random ID of the site to apply a **Whitelist Policy**. + * `only_priority` (Optional): Set to `true` to return only high-priority items (e.g., common time zones). + * `for_type` / `for_id` (Optional): Context for object-specific overrides. + * `include_disabled` (Optional): Set to `true` to see shadowed/disabled records. + +### B. Resolve Identity +Resolves a string (code, group, or name) to a single record. +* **Endpoint:** `GET /v3/lookup/{lu_type}/resolve?q=VALUE` +* **Usage:** Use this when you have an external code (e.g., ISO "US") and need the full Aether record. + +### C. Site Whitelist Policy +To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_json` field. +**Schema:** +```json +{ + "lookup_policy": { + "country": ["US", "CA", "GB"], + "time_zone": ["America/New_York"] + } +} +``` +*Note: Whitelist values must match the `group` field in the database.* + +--- + +## 5. Event File Data Retrieval (Hosted Files) + +Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File itself is a metadata record for binary content (files), which is accessed via separate Action endpoints (e.g., `/v3/action/hosted_file/download`). This API endpoint provides metadata about the associated hosted file. To retrieve this additional metadata: + +* **Endpoint:** `GET /v3/crud/event_file/{event_file_id}` +* **Query Parameter:** Add `inc_hosted_file=true` + * Example: `/v3/crud/event_file/?inc_hosted_file=true` + +**Response Impact:** + +1. **Top-Level Convenience Fields:** The response will include top-level fields for commonly needed hosted file data. These are populated directly from the SQL view via JOINs. + * `hosted_file_hash_sha256` (string) + * `hosted_file_subdirectory_path` (string) + * `hosted_file_content_type` (string) + * `hosted_file_size` (string - in bytes) +2. **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc. + +--- + +## 5. Troubleshooting 403 Forbidden + +If you receive a 403 on a valid ID: +1. Verify `x-aether-api-key` is correct. +2. Ensure you are sending `x-account-id` and NOT `x-aether-api-token`. +3. Verify the record actually belongs to the account ID you are sending. +4. Check if the object is marked `public_read: True` in the registry. (Posts and Archive Content allow guest access; Journals and Badges do not). diff --git a/src/lib/ae_archives/ae_archives__archive_content.ts b/src/lib/ae_archives/ae_archives__archive_content.ts index fe9084ae..c3929a21 100644 --- a/src/lib/ae_archives/ae_archives__archive_content.ts +++ b/src/lib/ae_archives/ae_archives__archive_content.ts @@ -170,13 +170,12 @@ export async function create_ae_obj__archive_content({ return null; } - const result = await api.create_ae_obj_v3({ + const result = await api.create_nested_obj_v3({ api_cfg, - obj_type: 'archive_content', - fields: { - archive_id_random: archive_id, - ...data_kv - }, + parent_type: 'archive', + parent_id: archive_id, + child_type: 'archive_content', + fields: data_kv, params, log_lvl }); diff --git a/src/lib/ae_events/ae_events__event_badge_template.ts b/src/lib/ae_events/ae_events__event_badge_template.ts index 6c0f609c..f368d9d9 100644 --- a/src/lib/ae_events/ae_events__event_badge_template.ts +++ b/src/lib/ae_events/ae_events__event_badge_template.ts @@ -241,108 +241,115 @@ export async function load_ae_obj_li__event_badge_template({ // Updated 2026-01-20 to V3 export async function create_ae_obj__event_badge_template({ - api_cfg, - event_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }) { - const result = await api.create_ae_obj_v3({ - api_cfg, - obj_type: 'event_badge_template', - fields: { - event_id: event_id, - ...data_kv - }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_badge_template', + fields: { ...data_kv }, + log_lvl + }); - if (result && try_cache) { - const processed_obj_li = await process_ae_badge_template_props({ - obj_li: [result], - log_lvl - }); - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'badge_template', - obj_li: processed_obj_li, - properties_to_save, - log_lvl - }); - } - return result; + if (result && try_cache) { + const processed_obj_li = await process_ae_badge_template_props({ + obj_li: [result], + log_lvl + }); + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'badge_template', + obj_li: processed_obj_li, + properties_to_save, + log_lvl + }); + } + return result; } // Updated 2026-01-20 to V3 export async function delete_ae_obj_id__event_badge_template({ - api_cfg, - event_badge_template_id, - method = 'delete', - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + event_badge_template_id, + method = 'delete', + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_badge_template_id: string; - method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + event_badge_template_id: string; + method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; + try_cache?: boolean; + log_lvl?: number; }) { - const result = await api.delete_ae_obj_v3({ - api_cfg, - obj_type: 'event_badge_template', - obj_id: event_badge_template_id, - method, - log_lvl - }); + const result = await api.delete_nested_ae_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_badge_template', + obj_id: event_badge_template_id, + method, + log_lvl + }); - if (try_cache) { - await db_events.badge_template.delete(event_badge_template_id); - } - return result; + if (try_cache) { + await db_events.badge_template.delete(event_badge_template_id); + } + return result; } // Updated 2026-01-20 to V3 export async function update_ae_obj__event_badge_template({ - api_cfg, - event_badge_template_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + event_badge_template_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_badge_template_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + event_badge_template_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }) { - const result = await api.update_ae_obj_v3({ - api_cfg, - obj_type: 'event_badge_template', - obj_id: event_badge_template_id, - fields: data_kv, - log_lvl - }); + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_badge_template', + obj_id: event_badge_template_id, + fields: data_kv, + log_lvl + }); - if (result && try_cache) { - const processed_obj_li = await process_ae_badge_template_props({ - obj_li: [result], - log_lvl - }); - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'badge_template', - obj_li: processed_obj_li, - properties_to_save, - log_lvl - }); - } - return result; + if (result && try_cache) { + const processed_obj_li = await process_ae_badge_template_props({ + obj_li: [result], + log_lvl + }); + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'badge_template', + obj_li: processed_obj_li, + properties_to_save, + log_lvl + }); + } + return result; } // Updated 2026-01-20 to V3 diff --git a/src/lib/ae_events/ae_events__event_device.ts b/src/lib/ae_events/ae_events__event_device.ts index cc7ae376..986ee16b 100644 --- a/src/lib/ae_events/ae_events__event_device.ts +++ b/src/lib/ae_events/ae_events__event_device.ts @@ -178,133 +178,140 @@ export async function load_ae_obj_li__event_device({ // Updated 2026-01-20 to V3 export async function create_ae_obj__event_device({ - api_cfg, - event_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - if (log_lvl) { - console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`); - } + if (log_lvl) { + console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`); + } - const result = await api.create_ae_obj_v3({ - api_cfg, - obj_type: 'event_device', - fields: { - event_id, - ...data_kv - }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_device', + fields: { ...data_kv }, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__event_device_props({ - obj_li: [result], - log_lvl - }); - const processed_obj = processed[0]; + if (result) { + const processed = await process_ae_obj__event_device_props({ + obj_li: [result], + log_lvl + }); + const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'device', - obj_li: [processed_obj], - properties_to_save, - log_lvl - }); - } - return processed_obj; - } + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'device', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } - return null; + return null; } // Updated 2026-01-20 to V3 export async function delete_ae_obj_id__event_device({ - api_cfg, - event_device_id, - method = 'delete', - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + event_device_id, + method = 'delete', + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_device_id: string; - method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + event_device_id: string; + method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; + try_cache?: boolean; + log_lvl?: number; }) { - if (log_lvl) { - console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`); - } + if (log_lvl) { + console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`); + } - const result = await api.delete_ae_obj_v3({ - api_cfg, - obj_type: 'event_device', - obj_id: event_device_id, - method, - log_lvl - }); + const result = await api.delete_nested_ae_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_device', + obj_id: event_device_id, + method, + log_lvl + }); - if (try_cache) { - await db_events.device.delete(event_device_id); - } + if (try_cache) { + await db_events.device.delete(event_device_id); + } - return result; + return result; } // Updated 2026-01-20 to V3 export async function update_ae_obj__event_device({ - api_cfg, - event_device_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + event_device_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_device_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + event_device_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - if (log_lvl) { - console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`); - } + if (log_lvl) { + console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`); + } - const result = await api.update_ae_obj_v3({ - api_cfg, - obj_type: 'event_device', - obj_id: event_device_id, - fields: data_kv, - log_lvl - }); + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_device', + obj_id: event_device_id, + fields: data_kv, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__event_device_props({ - obj_li: [result], - log_lvl - }); - const processed_obj = processed[0]; + if (result) { + const processed = await process_ae_obj__event_device_props({ + obj_li: [result], + log_lvl + }); + const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'device', - obj_li: [processed_obj], - properties_to_save, - log_lvl - }); - } - return processed_obj; - } + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'device', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } - return null; + return null; } // Updated 2026-01-20 to V3 diff --git a/src/lib/ae_events/ae_events__event_location.ts b/src/lib/ae_events/ae_events__event_location.ts index eade72b8..314db1f6 100644 --- a/src/lib/ae_events/ae_events__event_location.ts +++ b/src/lib/ae_events/ae_events__event_location.ts @@ -177,78 +177,113 @@ async function _handle_nested_loads(location_obj: any, { api_cfg, inc_file_li, i // Updated 2026-01-20 to V3 export async function create_ae_obj__event_location({ - api_cfg, - event_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.create_ae_obj_v3({ - api_cfg, obj_type: 'event_location', - fields: { event_id, ...data_kv }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_location', + fields: { ...data_kv }, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'location', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } // Updated 2026-01-20 to V3 export async function delete_ae_obj_id__event_location({ - api_cfg, - event_location_id, - method = 'delete', - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + event_location_id, + method = 'delete', + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_location_id: string; - method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + event_location_id: string; + method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; + try_cache?: boolean; + log_lvl?: number; }) { - const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, method, log_lvl }); - if (try_cache) await db_events.location.delete(event_location_id); - return result; + const result = await api.delete_nested_ae_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_location', + obj_id: event_location_id, + method, + log_lvl + }); + if (try_cache) await db_events.location.delete(event_location_id); + return result; } // Updated 2026-01-20 to V3 export async function update_ae_obj__event_location({ - api_cfg, - event_location_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_id, + event_location_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_location_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_id: string; + event_location_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, fields: data_kv, log_lvl }); - if (result) { - const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_location', + obj_id: event_location_id, + fields: data_kv, + log_lvl + }); + if (result) { + const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'location', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } // Updated 2026-01-20 to V3 diff --git a/src/lib/ae_events/ae_events__event_presentation.ts b/src/lib/ae_events/ae_events__event_presentation.ts index 16a6a3c7..79bacdcc 100644 --- a/src/lib/ae_events/ae_events__event_presentation.ts +++ b/src/lib/ae_events/ae_events__event_presentation.ts @@ -197,35 +197,42 @@ async function _refresh_presentation_li_background({ api_cfg, for_obj_type, for_ // Updated 2026-01-20 to V3 export async function create_ae_obj__event_presentation({ - api_cfg, - event_id, - event_session_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_session_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_id: string; - event_session_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_session_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.create_ae_obj_v3({ - api_cfg, obj_type: 'event_presentation', - fields: { event_id, event_session_id, ...data_kv }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event_session', + for_obj_id: event_session_id, + obj_type: 'event_presentation', + fields: { ...data_kv }, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'presentation', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } // Updated 2026-01-20 to V3 diff --git a/src/lib/ae_events/ae_events__event_presenter.ts b/src/lib/ae_events/ae_events__event_presenter.ts index 41bf6432..1487dee0 100644 --- a/src/lib/ae_events/ae_events__event_presenter.ts +++ b/src/lib/ae_events/ae_events__event_presenter.ts @@ -173,82 +173,113 @@ async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj // Updated 2026-01-20 to V3 export async function create_ae_obj__event_presenter({ - api_cfg, - event_id, - event_session_id, - event_presentation_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_presentation_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_id: string; - event_session_id: string; - event_presentation_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_presentation_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.create_ae_obj_v3({ - api_cfg, obj_type: 'event_presenter', - fields: { event_id, event_session_id, event_presentation_id, ...data_kv }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event_presentation', + for_obj_id: event_presentation_id, + obj_type: 'event_presenter', + fields: { ...data_kv }, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'presenter', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } // Updated 2026-01-20 to V3 export async function delete_ae_obj_id__event_presenter({ - api_cfg, - event_presenter_id, - method = 'delete', - try_cache = true, - log_lvl = 0 + api_cfg, + event_presentation_id, + event_presenter_id, + method = 'delete', + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_presenter_id: string; - method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_presentation_id: string; + event_presenter_id: string; + method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; + try_cache?: boolean; + log_lvl?: number; }) { - const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, method, log_lvl }); - if (try_cache) await db_events.presenter.delete(event_presenter_id); - return result; + const result = await api.delete_nested_ae_obj_v3({ + api_cfg, + for_obj_type: 'event_presentation', + for_obj_id: event_presentation_id, + obj_type: 'event_presenter', + obj_id: event_presenter_id, + method, + log_lvl + }); + if (try_cache) await db_events.presenter.delete(event_presenter_id); + return result; } // Updated 2026-01-20 to V3 export async function update_ae_obj__event_presenter({ - api_cfg, - event_presenter_id, - data_kv, - try_cache = true, - log_lvl = 0 + api_cfg, + event_presentation_id, + event_presenter_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - event_presenter_id: string; - data_kv: key_val; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + event_presentation_id: string; + event_presenter_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, fields: data_kv, log_lvl }); - if (result) { - const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event_presentation', + for_obj_id: event_presentation_id, + obj_type: 'event_presenter', + obj_id: event_presenter_id, + fields: data_kv, + log_lvl + }); + if (result) { + const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'presenter', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } // Updated 2026-01-21 to Restore Full Aether Search Logic diff --git a/src/lib/ae_events/ae_events__event_session.ts b/src/lib/ae_events/ae_events__event_session.ts index d3b8461a..53e29165 100644 --- a/src/lib/ae_events/ae_events__event_session.ts +++ b/src/lib/ae_events/ae_events__event_session.ts @@ -249,47 +249,110 @@ async function _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_i } export async function create_ae_obj__event_session({ - api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0 + api_cfg, + event_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number; + api_cfg: any; + event_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.create_ae_obj_v3({ api_cfg, obj_type: 'event_session', fields: { event_id, ...data_kv }, log_lvl }); - if (result) { - const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_session', + fields: { ...data_kv }, + log_lvl + }); + if (result) { + const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'session', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } export async function delete_ae_obj_id__event_session({ - api_cfg, event_session_id, method = 'delete', try_cache = true, log_lvl = 0 + api_cfg, + event_id, + event_session_id, + method = 'delete', + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; event_session_id: string; method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; try_cache?: boolean; log_lvl?: number; + api_cfg: any; + event_id: string; + event_session_id: string; + method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; + try_cache?: boolean; + log_lvl?: number; }) { - const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, method, log_lvl }); - if (try_cache) await db_events.session.delete(event_session_id); - return result; + const result = await api.delete_nested_ae_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_session', + obj_id: event_session_id, + method, + log_lvl + }); + if (try_cache) await db_events.session.delete(event_session_id); + return result; } export async function update_ae_obj__event_session({ - api_cfg, event_session_id, data_kv, try_cache = true, log_lvl = 0 + api_cfg, + event_id, + event_session_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; event_session_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number; + api_cfg: any; + event_id: string; + event_session_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, fields: data_kv, log_lvl }); - if (result) { - const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl }); - } - return processed_obj; - } - return null; + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_session', + obj_id: event_session_id, + fields: data_kv, + log_lvl + }); + if (result) { + const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'session', + obj_li: [processed_obj], + properties_to_save, + log_lvl + }); + } + return processed_obj; + } + return null; } export async function search__event_session({ diff --git a/src/lib/ae_events/ae_events__exhibit.ts b/src/lib/ae_events/ae_events__exhibit.ts index 6cd737a5..aad42052 100644 --- a/src/lib/ae_events/ae_events__exhibit.ts +++ b/src/lib/ae_events/ae_events__exhibit.ts @@ -295,65 +295,87 @@ async function _refresh_exhibit_li_background({ api_cfg, event_id, enabled, hidd * Exhibit Create (V3) */ export async function create_ae_obj__exhibit({ - api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0 + api_cfg, + event_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number; + api_cfg: any; + event_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.create_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit', - fields: { event_id: event_id, ...data_kv }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_exhibit', + fields: { ...data_kv }, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit', - obj_li: [processed_obj], - properties_to_save: properties_to_save_exhibit, - log_lvl - }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit, + log_lvl + }); + } + return processed_obj; + } + return null; } /** * Exhibit Update (V3) */ export async function update_ae_obj__exhibit({ - api_cfg, exhibit_id, data_kv, try_cache = true, log_lvl = 0 + api_cfg, + event_id, + exhibit_id, + data_kv, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; exhibit_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number; + api_cfg: any; + event_id: string; + exhibit_id: string; + data_kv: key_val; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.update_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit', - obj_id: exhibit_id, - fields: data_kv, - log_lvl - }); + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event', + for_obj_id: event_id, + obj_type: 'event_exhibit', + obj_id: exhibit_id, + fields: data_kv, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit', - obj_li: [processed_obj], - properties_to_save: properties_to_save_exhibit, - log_lvl - }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit, + log_lvl + }); + } + return processed_obj; + } + return null; } /** diff --git a/src/lib/ae_events/ae_events__exhibit_tracking.ts b/src/lib/ae_events/ae_events__exhibit_tracking.ts index 8288430d..a8475f27 100644 --- a/src/lib/ae_events/ae_events__exhibit_tracking.ts +++ b/src/lib/ae_events/ae_events__exhibit_tracking.ts @@ -308,90 +308,95 @@ async function _refresh_tracking_li_background({ api_cfg, exhibit_id, enabled, h * Lead Capture (V3) */ export async function create_ae_obj__exhibit_tracking({ - api_cfg, - exhibit_id, - event_badge_id, - external_person_id, - group, - try_cache = true, - log_lvl = 0 + api_cfg, + exhibit_id, + event_badge_id, + external_person_id, + group, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - exhibit_id: string; - event_badge_id: string; - external_person_id: string; - group?: string; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + exhibit_id: string; + event_badge_id: string; + external_person_id: string; + group?: string; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.create_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit_tracking', - fields: { - event_exhibit_id: exhibit_id, - event_badge_id: event_badge_id, - external_person_id, - group - }, - log_lvl - }); + const result = await api.create_nested_obj_v3({ + api_cfg, + for_obj_type: 'event_exhibit', + for_obj_id: exhibit_id, + obj_type: 'event_exhibit_tracking', + fields: { + event_badge_id: event_badge_id, + external_person_id, + group + }, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit_tracking', - obj_li: [processed_obj], - properties_to_save: properties_to_save_exhibit_tracking, - log_lvl - }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit_tracking', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit_tracking, + log_lvl + }); + } + return processed_obj; + } + return null; } /** * Lead Update (V3) */ export async function update_ae_obj__exhibit_tracking({ - api_cfg, - exhibit_tracking_id, - data, - try_cache = true, - log_lvl = 0 + api_cfg, + exhibit_id, + exhibit_tracking_id, + data, + try_cache = true, + log_lvl = 0 }: { - api_cfg: any; - exhibit_tracking_id: string; - data: any; - try_cache?: boolean; - log_lvl?: number; + api_cfg: any; + exhibit_id: string; + exhibit_tracking_id: string; + data: any; + try_cache?: boolean; + log_lvl?: number; }): Promise { - const result = await api.update_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit_tracking', - obj_id: exhibit_tracking_id, - fields: data, - log_lvl - }); + const result = await api.update_nested_obj_v3({ + api_cfg, + for_obj_type: 'event_exhibit', + for_obj_id: exhibit_id, + obj_type: 'event_exhibit_tracking', + obj_id: exhibit_tracking_id, + fields: data, + log_lvl + }); - if (result) { - const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); - const processed_obj = processed[0]; - if (try_cache) { - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit_tracking', - obj_li: [processed_obj], - properties_to_save: properties_to_save_exhibit_tracking, - log_lvl - }); - } - return processed_obj; - } - return null; + if (result) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit_tracking', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit_tracking, + log_lvl + }); + } + return processed_obj; + } + return null; } /** diff --git a/tests/disabled/private_network_cdp.test.ts b/tests/disabled/private_network_cdp.test.ts deleted file mode 100644 index 25a75058..00000000 --- a/tests/disabled/private_network_cdp.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { test, expect } from '@playwright/test'; - -// This test attaches to the Chrome DevTools Protocol to capture low-level -// network events (including those from service workers). Run this with the -// real Chrome binary (channel: 'chrome') to reproduce the exact browser -// behavior that shows the PNA prompt. - -test('CDP: detect private/local network requests and PNA preflights', async ({ page }) => { - const privateRequests: Array = []; - - function isPrivateHostname(hostname: string) { - if (!hostname) return false; - if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true; - if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true; - return false; - } - - // Create a CDP session for the page to listen to Network events - const client = await page.context().newCDPSession(page); - await client.send('Network.enable'); - - client.on('Network.requestWillBeSent', (params) => { - try { - const url = params.request.url; - const hostname = new URL(url).hostname; - const headers = params.request.headers || {}; - const pna = headers['access-control-request-private-network'] === 'true' || headers['Access-Control-Request-Private-Network'] === 'true'; - - if (isPrivateHostname(hostname) || pna) { - privateRequests.push({ - url, - method: params.request.method, - initiator: params.initiator?.type, - pnaPreflight: pna, - headers - }); - } - } catch (e) {} - }); - - // Also capture console messages to surface the same errors you see in Chrome. - const consoleMessages: string[] = []; - page.on('console', (msg) => { - consoleMessages.push(`${msg.type()}: ${msg.text()}`); - }); - - // Navigate to the site that triggers the behavior in your environment. - await page.goto('http://demo.localhost:5173/'); - await page.waitForLoadState('networkidle'); - - if (privateRequests.length > 0) { - console.error('CDP detected private/local requests or PNA preflights:'); - for (const r of privateRequests) console.error(JSON.stringify(r, null, 2)); - } - - if (consoleMessages.length) { - console.log('Captured console messages:'); - consoleMessages.forEach((m) => console.log(m)); - } - - expect(privateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0); -}); diff --git a/tests/disabled/private_network_combined.test.ts b/tests/disabled/private_network_combined.test.ts new file mode 100644 index 00000000..290266b3 --- /dev/null +++ b/tests/disabled/private_network_combined.test.ts @@ -0,0 +1,84 @@ +import { test, expect } from '@playwright/test'; + +// Combined private-network detection test. +// Keeps both high-level request observation and low-level CDP capture in one +// place so you can run this when troubleshooting PNA / localhost access issues. + +function isPrivateHostname(hostname: string) { + if (!hostname) return false; + if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true; + if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true; + return false; +} + +test('detect private/local network requests and PNA preflights (combined)', async ({ page }) => { + const privateRequests: Array = []; + const cdpPrivateRequests: Array = []; + const consoleMessages: string[] = []; + + page.on('console', (msg) => consoleMessages.push(`${msg.type()}: ${msg.text()}`)); + + page.on('request', (request) => { + try { + const url = new URL(request.url()); + const headers = request.headers(); + const hostname = url.hostname; + const pnaHeader = (headers['access-control-request-private-network'] || headers['Access-Control-Request-Private-Network']); + + if (isPrivateHostname(hostname) || pnaHeader === 'true') { + privateRequests.push({ url: request.url(), method: request.method(), resourceType: request.resourceType(), pnaPreflight: pnaHeader === 'true', headers }); + } + } catch (e) { + // ignore parse errors + } + }); + + page.on('requestfailed', (r) => { + try { + const url = new URL(r.url()); + if (isPrivateHostname(url.hostname)) { + privateRequests.push({ url: r.url(), method: r.method(), failure: r.failure() }); + } + } catch (e) {} + }); + + // CDP capture (may not be available in some browser channels) + try { + const client = await page.context().newCDPSession(page); + await client.send('Network.enable'); + client.on('Network.requestWillBeSent', (params) => { + try { + const url = params.request.url; + const hostname = new URL(url).hostname; + const headers = params.request.headers || {}; + const pna = headers['access-control-request-private-network'] === 'true' || headers['Access-Control-Request-Private-Network'] === 'true'; + if (isPrivateHostname(hostname) || pna) { + cdpPrivateRequests.push({ url, method: params.request.method, initiator: params.initiator?.type, pnaPreflight: pna, headers }); + } + } catch (e) {} + }); + } catch (e) { + // CDP not available in this context; continue with high-level capture + } + + await page.goto('http://demo.localhost:5173/'); + await page.waitForLoadState('networkidle'); + + // Report findings for easier debugging + if (privateRequests.length > 0 || cdpPrivateRequests.length > 0 || consoleMessages.length > 0) { + if (consoleMessages.length) { + console.log('Console messages:'); + consoleMessages.forEach((m) => console.log(m)); + } + if (privateRequests.length) { + console.error('Detected private/local requests or PNA preflights (high-level):'); + for (const r of privateRequests) console.error(JSON.stringify(r, null, 2)); + } + if (cdpPrivateRequests.length) { + console.error('Detected private/local requests or PNA preflights (CDP):'); + for (const r of cdpPrivateRequests) console.error(JSON.stringify(r, null, 2)); + } + } + + expect(privateRequests.length + cdpPrivateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0); +}); diff --git a/tests/disabled/private_network_detection.test.ts b/tests/disabled/private_network_detection.test.ts deleted file mode 100644 index 6a623dbd..00000000 --- a/tests/disabled/private_network_detection.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { test, expect } from '@playwright/test'; - -// Detect requests to private/local address space or PNA preflight headers. -test('detect private/local network requests and PNA preflights', async ({ page }) => { - const privateRequests: Array = []; - - function isPrivateHostname(hostname: string) { - if (!hostname) return false; - // hostnames that resolve to local addresses or explicitly localhost - if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true; - // IPv4 private ranges - if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true; - return false; - } - - page.on('request', (request) => { - try { - const url = new URL(request.url()); - const headers = request.headers(); - const hostname = url.hostname; - - const pnaHeader = (headers['access-control-request-private-network'] || headers['Access-Control-Request-Private-Network']); - - if (isPrivateHostname(hostname) || pnaHeader === 'true') { - privateRequests.push({ - url: request.url(), - method: request.method(), - resourceType: request.resourceType(), - pnaPreflight: pnaHeader === 'true', - headers - }); - } - } catch (e) { - // ignore parse errors - } - }); - - page.on('requestfailed', (r) => { - // capture failed requests that might be private and blocked - try { - const url = new URL(r.url()); - if (isPrivateHostname(url.hostname)) { - privateRequests.push({ url: r.url(), method: r.method(), failure: r.failure() }); - } - } catch (e) {} - }); - - // Navigate to the dev site used in other tests. - await page.goto('http://demo.localhost:5173/'); - - // Wait for network to settle. - await page.waitForLoadState('networkidle'); - - if (privateRequests.length > 0) { - console.error('Detected private/local requests or PNA preflights:'); - for (const r of privateRequests) { - console.error(JSON.stringify(r, null, 2)); - } - } - - expect(privateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0); -}); diff --git a/tests/disabled/v3_api_security.test.ts b/tests/disabled/v3_api_security.test.ts deleted file mode 100644 index f2946dc2..00000000 --- a/tests/disabled/v3_api_security.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { ae_app_local_data_defaults } from '../_helpers/ae_defaults'; - -test.describe('V3 API Header Integrity', () => { - test.setTimeout(7000); - - test.beforeEach(async ({ page }) => { - // Log browser console errors to the terminal for easier debugging. - page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`)); - page.on('console', (msg) => { - if (msg.type() === 'error' || msg.type() === 'warn') { - console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`); - } - }); - - // Mock all local /v3/ API requests so tests are deterministic. - await page.route('**/v3/**', async (route) => { - const req = route.request(); - const url = req.url(); - - // Handshake mock so app boots predictably - if (url.includes('site_domain/search')) { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'test-site-domain-id', - account_id: 'test-account-id', - site_id: 'test-site-id', - account_name: 'Test Account', - enable: '1', - cfg_json: {} - } - ] - }) - }); - } - - // Provide lightweight responses for endpoints we assert on - if (url.includes('/v3/lookup/country/list')) { - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); - } - - if (url.includes('/v3/crud/user/search')) { - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); - } - - // Default handler - return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) }); - }); - }); - - test('Verify lookup requests include the unauthenticated bypass header', async ({ page }) => { - // Prepare the browser's localStorage with the necessary state for this test. - await page.addInitScript((defaults) => { - const testData = { ...defaults, account_id: 'test-account-id', manager_access: true }; - window.localStorage.setItem('ae_loc', JSON.stringify(testData)); - }, ae_app_local_data_defaults); - - // Start waiting for the lookup request *before* navigating. - const requestPromise = page.waitForRequest((request) => - request.url().includes('/v3/lookup/country/list') - ); - - // Navigate to the page that triggers the lookup. - await page.goto('/core/lookups'); - - // Wait for the request to be captured. - const request = await requestPromise; - const headers = request.headers(); - - // Assert that the correct bypass headers were used. - expect(headers['x-no-account-id']).toBe('Nothing to See Here'); - expect(headers['x-aether-api-key']).toBeDefined(); - }); - - test('Verify Account ID Scavenging from localStorage on CRUD requests', async ({ page }) => { - const testAccountId = 'scavenged-account-id-123'; - - // Prepare the browser's localStorage with a specific account ID. - await page.addInitScript(({ defaults, id }) => { - const testData = { ...defaults, account_id: id, manager_access: true }; - window.localStorage.setItem('ae_loc', JSON.stringify(testData)); - },{ defaults: ae_app_local_data_defaults, id: testAccountId }); - - // Start waiting for the CRUD request. - const requestPromise = page.waitForRequest((request) => { - const url = request.url(); - // The /core/users page triggers a 'user' search on load. - return url.includes('/v3/crud/user/search'); - }); - - // Navigate to a page that is guaranteed to make a standard CRUD call. - await page.goto('/core/users'); - - // Wait for the request to be captured. - const request = await requestPromise; - const headers = request.headers(); - - // Assert that the scavenged account ID was correctly included in the header. - expect(headers['x-account-id']).toBe(testAccountId); - }); -});