diff --git a/documentation/CLIENT__IDAA_and_customized_mods.md b/documentation/CLIENT__IDAA_and_customized_mods.md index b0a63da2..62234089 100644 --- a/documentation/CLIENT__IDAA_and_customized_mods.md +++ b/documentation/CLIENT__IDAA_and_customized_mods.md @@ -220,6 +220,30 @@ These fields are read elsewhere in the IDAA UI to enable flows for verified user If you need a compact checklist for re-creating this flow in another integration, ask and I will add a small runbook with exact request/response field mappings. +### Planned: Server-Side Novi Verification (FastAPI) + +**Problem:** The current implementation calls the Novi API client-side — from the member's browser directly to Novi. Hotel/conference WiFi, VPNs, corporate/hospital networks, and Cloudflare IP reputation filtering can block these calls and produce false "Access Denied" for legitimate members. + +**Solution:** A FastAPI endpoint proxies the Novi call server-to-server (Aether → Novi), caching results in Redis. Members' browser IPs are no longer in the call path. + +**Endpoint:** `GET /v3/action/idaa/novi_member/{uuid}` +- Standard Aether auth headers required (`x-aether-api-key`, `x-account-id`) +- Server reads `novi_idaa_api_key` / `novi_api_root_url` from site `cfg_json` +- Redis cache key: `idaa:novi_member:{account_id}:{uuid}` — TTL 4 hours, only cache verified 200s + +**Response codes:** + +| Code | Meaning | Frontend action | +|---|---|---| +| `200` | Verified — `{ "verified": true, "full_name": "...", "email": "..." }` | Grant access | +| `404` | UUID not in Novi (genuine non-member) | Deny access | +| `429` | Novi rate limited | Show retry UI (not a denial) | +| `503` | Novi unreachable | Show retry UI (not a denial) | + +**Frontend change when implemented:** Replace the direct `fetch()` to Novi in `verify_novi_uuid()` with a call to this endpoint via `ae_api`. The `api_key` param becomes unused (server holds it). Response code mapping: 404 → denied, 429 → `'rate_limited'`, 503 → `'api_error'`. + +**FastAPI task:** Tracked in `aether_api_fastapi/documentation/TODO__Agents.md` under "IDAA: Server-Side Novi Verification". + ### Permission Levels (Ascending) | Level | Condition | Access | |---|---|---| diff --git a/src/routes/idaa/(idaa)/+layout.svelte b/src/routes/idaa/(idaa)/+layout.svelte index 84048597..10dedb5f 100644 --- a/src/routes/idaa/(idaa)/+layout.svelte +++ b/src/routes/idaa/(idaa)/+layout.svelte @@ -66,7 +66,7 @@ let verify_failed_for_uuid: string | null = null; // Tracks the type of Novi API failure for the UI — 'rate_limited' or 'api_error'. // A transient API error is NOT the same as a real membership denial; this lets us // show a distinct "Verification Unavailable" state instead of "Access Denied". -let verify_error_type: 'rate_limited' | 'api_error' | null = $state(null); +let verify_error_type: 'rate_limited' | 'api_error' | 'network_error' | null = $state(null); // Shown inside the spinner — updated during rate-limit retry waits. let verifying_status_msg: string = $state('Verifying identity...'); // Incremented by handle_verify_retry() to re-run Effect 2 without a full page reload. @@ -148,7 +148,10 @@ $effect(() => { } }); -const VERIFIED_TTL_MS_DEFAULT = 45 * 60 * 1000; // 45 minutes +// WHY 12 hours: members open IDAA in a hotel/conference context and should not need to +// re-verify mid-day. After our client-side Novi call is replaced by server-side (FastAPI), +// this TTL can be tuned independently. Until then, 12h covers a full workday. +const VERIFIED_TTL_MS_DEFAULT = 12 * 60 * 60 * 1000; // 12 hours // Effect 1: Set URL origin and params $effect(() => { @@ -451,8 +454,14 @@ async function verify_novi_uuid( error ); verify_failed_for_uuid = uuid; // Latch — stop retry loop for this UUID. See declaration comment. - // 'rate_limited' is set before throw for 429; everything else is an unexpected API error. - if (!verify_error_type) verify_error_type = 'api_error'; + // 'rate_limited' is set before throw for 429. + // 'network_error' for persistent network/timeout failures after retry — both the first + // attempt AND the auto-retry failed with TypeError/AbortError. Most common cause: hotel/ + // conference WiFi, VPN, or Cloudflare IP reputation blocking the client-side Novi call. + // 'api_error' for everything else (4xx, 5xx, empty 200). + if (!verify_error_type) { + verify_error_type = is_network_or_timeout ? 'network_error' : 'api_error'; + } $idaa_loc.novi_uuid = null; $idaa_loc.novi_email = null; $idaa_loc.novi_full_name = null; @@ -538,9 +547,19 @@ function handle_verify_retry() {

The membership directory is temporarily busy (rate limited). Please wait a moment and try again.

+ {:else if verify_error_type === 'network_error'} +

+ We were unable to reach the membership directory after two attempts. This is usually caused by a network filter blocking the connection — it is not a problem with your membership. +

+ {:else}

- We were unable to connect to the membership directory. This is usually a temporary network issue — it is not a problem with your membership or access. + We were unable to connect to the membership directory. This is usually a temporary issue — it is not a problem with your membership or access.

{/if}