fix(idaa): add VPN/network hint, bump TTL to 12h, document server-side verify plan

- Classify persistent network/timeout failures as 'network_error' (separate from
  generic 'api_error') so the UI can show a targeted message
- Add actionable hint for members on hotel WiFi, VPN, or corporate networks:
  turn off VPN, switch to cellular, try a different network
- Extend VERIFIED_TTL_MS_DEFAULT from 45 min to 12 hours — covers a full workday
  so members at conferences do not need to re-verify mid-day
- Document planned server-side Novi verification FastAPI endpoint in
  CLIENT__IDAA_and_customized_mods.md (once implemented, eliminates client-side
  Cloudflare/IP-reputation exposure entirely)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-19 18:23:45 -04:00
parent 71e79f032d
commit 6755a68b13
2 changed files with 48 additions and 5 deletions

View File

@@ -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 |
|---|---|---|

View File

@@ -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() {
<p class="text-sm">
The membership directory is temporarily busy (rate limited). Please wait a moment and try again.
</p>
{:else if verify_error_type === 'network_error'}
<p class="text-sm">
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.
</p>
<ul class="mt-1 list-disc pl-5 text-left text-sm">
<li>Turn off your VPN if one is running</li>
<li>Switch from hotel or conference WiFi to your phone's cellular data</li>
<li>Try a different network (mobile data, home WiFi)</li>
<li>If on a corporate or hospital network, try from a personal device</li>
</ul>
{:else}
<p class="text-sm">
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.
</p>
{/if}
<p class="text-xs italic text-gray-500">