docs(idaa): update Novi verification docs to reflect server-side proxy (complete)
CLIENT__IDAA_and_customized_mods.md:
- Verification Flow: describe Aether proxy call, not direct browser-to-Novi fetch
- Replace old fetch() code snippet with new Aether endpoint call
- Update novi_idaa_api_key / novi_api_root_url field descriptions (server-side only now)
- Security notes: key never sent to browser; shape changes go in backend method
- Rate limit note: 12h TTL (was 5-min), add 503 auto-retry behavior
- Fix Redis cache key: idaa:novi_member:{uuid} (account_id was dropped from key)
GUIDE__AE_API_V3_for_Frontend.md §12:
- 503 frontend action: auto-retry once after 3s before api_error
- Mark migration section complete (2026-05-19); update table to show retry behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -113,21 +113,22 @@ IDAA members do not log in through Aether — they log in through Novi (idaa.org
|
||||
|
||||
> **Security note (2026-03-09):** The iframe HTML files previously also passed `email` and `full_name`
|
||||
> via URL params. These were unverifiable claims that could be spoofed via URL. They have been removed.
|
||||
> The SvelteKit layout now fetches verified identity directly from the Novi API.
|
||||
> The SvelteKit layout now verifies identity via the Aether server-side Novi proxy — the Novi API
|
||||
> call originates from the server, not the member's browser.
|
||||
> See "Iframe Integration" → "Novi UUID Verification Flow" below.
|
||||
|
||||
### Verification Flow (`(idaa)/+layout.svelte`)
|
||||
|
||||
When a `uuid` param is present in the URL, the layout performs an **async Novi API call** to verify:
|
||||
When a `uuid` param is present in the URL, the layout performs an **async call to the Aether server-side endpoint** (`GET /v3/action/idaa/novi_member/{uuid}`), which proxies to Novi server-to-server:
|
||||
|
||||
1. The UUID actually exists in Novi's system (prevents fake/crafted UUIDs)
|
||||
2. Gets verified name and email directly from Novi — these can't be forged via URL
|
||||
2. Gets verified name and email — these can't be forged via URL
|
||||
3. Sets `$idaa_loc.novi_uuid`, `$idaa_loc.novi_email`, `$idaa_loc.novi_full_name`
|
||||
4. Sets `$idaa_loc.novi_verified = true` on success
|
||||
|
||||
A `novi_verifying` UI state prevents the "Access Denied" screen from flashing during the API round-trip.
|
||||
|
||||
**All or nothing:** If the Novi API key is not configured, or the verification call fails, access is denied. There is no URL-param fallback.
|
||||
**All or nothing:** If the Novi API key is not configured on the site, or the verification call fails, access is denied. There is no URL-param fallback.
|
||||
|
||||
**Required `site_cfg_json` fields:**
|
||||
```json
|
||||
@@ -148,22 +149,26 @@ This section documents the exact way Aether uses the Novi API for the IDAA integ
|
||||
|
||||
- **All-or-nothing policy:** If the Novi API key is not configured or the verification call fails, the Novi-based access path is denied. The layout explicitly prevents child routes from rendering while verification is in-flight to avoid flashing "Access Denied".
|
||||
|
||||
- **Rate limits (Novi API):** 20 calls/second · 600 calls/minute · 100,000 calls/day. The layout handles 429 responses with a 10-second flat backoff and one retry. If the retry also returns 429, access is denied and a "Reload / Retry" button is shown. The 5-minute TTL cache on successful verification prevents repeated calls during normal use.
|
||||
- **Rate limits (Novi API):** 20 calls/second · 600 calls/minute · 100,000 calls/day. The Aether backend handles 429 responses; the frontend receives a `429` and retries once after 10 seconds. The 12-hour TTL cache on successful verification (Redis server-side + `$idaa_loc` client-side) prevents repeated calls during normal use. A `503` (Novi unreachable) is auto-retried once after 3 seconds before surfacing an error to the user.
|
||||
|
||||
### Verification Flow (implementation)
|
||||
|
||||
1. The IDAA iframe loads Aether pages with a `?uuid=<uuid>&iframe=true` param.
|
||||
2. When the `uuid` param is present the IDAA layout performs an authenticated GET against the Novi customers endpoint:
|
||||
2. When the `uuid` param is present the IDAA layout calls the Aether server-side proxy:
|
||||
|
||||
```js
|
||||
// simplified
|
||||
fetch(`${api_root_url}/customers/${uuid}`, {
|
||||
fetch(`${aether_api_url}/v3/action/idaa/novi_member/${uuid}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': `Basic ${api_key}` }
|
||||
headers: {
|
||||
'x-aether-api-key': api_key,
|
||||
'x-account-id': account_id
|
||||
}
|
||||
})
|
||||
// Aether calls Novi server-to-server; member's browser IP is never in the Novi call path.
|
||||
```
|
||||
|
||||
3. On success the layout uses the returned JSON to build a display name and normalized email, then writes these values to the IDAA store and marks verification success.
|
||||
3. On success (`200`), the layout reads `data.full_name` and `data.email` from the response and writes them to the IDAA store, marking verification success.
|
||||
|
||||
4. The layout then determines a target Novi permission level (`authenticated`, `trusted`, `administrator`) by checking configured UUID lists (`novi_trusted_li`, `novi_admin_li`) and upgrades the Aether session only if the Novi-derived level is higher than the current global level.
|
||||
|
||||
@@ -171,9 +176,9 @@ fetch(`${api_root_url}/customers/${uuid}`, {
|
||||
|
||||
### Key `site_cfg_json` fields and where they are used
|
||||
|
||||
- **`novi_idaa_api_key`**: Base64-encoded Basic auth token provided by Novi. Required for the verification request. Accessed in code as `$ae_loc.site_cfg_json.novi_idaa_api_key` and passed in the `Authorization: Basic <key>` header. If missing, Novi-based access is denied.
|
||||
- **`novi_idaa_api_key`**: Base64-encoded Basic auth token provided by Novi. Used by the Aether **server** to authenticate against Novi — the frontend never touches the key itself. The frontend checks only for its *presence* in `site_cfg_json` as a guard meaning "IDAA is configured for this site". If missing, Novi-based access is denied.
|
||||
|
||||
- **`novi_api_root_url`**: Optional API root (defaults to `https://www.idaa.org/api`). Used to form the verification URL.
|
||||
- **`novi_api_root_url`**: Optional Novi API root (defaults to `https://www.idaa.org/api`). Read by the Aether server, not the frontend.
|
||||
|
||||
- **`novi_admin_li`**: Array of UUIDs treated as administrators for IDAA. Merged into `$idaa_loc.novi_admin_li` during layout initialization and used to set `administrator` level.
|
||||
|
||||
@@ -214,35 +219,31 @@ These fields are read elsewhere in the IDAA UI to enable flows for verified user
|
||||
### Security notes and operational guidance
|
||||
|
||||
- The previous implementation leaked `email` and `full_name` via URL params — this was removed because those values are unauthenticated and can be spoofed.
|
||||
- The API key is sensitive — keep it only in site config and do not expose it in client-side code or public repositories.
|
||||
- The verification request uses Basic auth with the provided `novi_idaa_api_key` (already Base64-encoded by Novi) — treat the token like a password.
|
||||
- If Novi changes their customer API shape, update the layout parsing (display name/email normalization) and this documentation.
|
||||
- The API key is sensitive — keep it only in site `cfg_json` and do not expose it in client-side code or public repositories. The key is read and used exclusively by the Aether backend; it is never sent to the browser.
|
||||
- If Novi changes their customer API shape, update `app/methods/idaa_novi_verify_methods.py` in the backend (display name/email normalization) and this documentation.
|
||||
|
||||
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)
|
||||
### ~~Planned: Server-Side Novi Verification~~ ✅ Implemented (2026-05-19)
|
||||
|
||||
**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.
|
||||
**Problem solved:** The previous client-side Novi API call originated from the member's browser.
|
||||
Hotel/conference WiFi, VPNs, corporate/hospital networks, and Cloudflare IP reputation filtering
|
||||
could 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.
|
||||
**Solution implemented:** A FastAPI endpoint proxies the Novi call server-to-server
|
||||
(Aether → Novi), with Redis caching. 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`)
|
||||
- Standard Aether auth headers (`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
|
||||
- Redis cache: `idaa:novi_member:{uuid}` — 4-hour TTL, only 200s cached
|
||||
- `404` results never cached (recently-joined members not incorrectly denied)
|
||||
|
||||
**Response codes:**
|
||||
**Frontend:** `verify_novi_uuid()` in `(idaa)/+layout.svelte` now calls this endpoint with
|
||||
standard Aether headers. The `novi_idaa_api_key` is still checked for presence in
|
||||
`site_cfg_json` as a proxy for "is IDAA configured for this site" (server holds the key itself).
|
||||
|
||||
| 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".
|
||||
**Full API spec:** `GUIDE__AE_API_V3_for_Frontend.md` §12.
|
||||
|
||||
### Permission Levels (Ascending)
|
||||
| Level | Condition | Access |
|
||||
|
||||
@@ -673,19 +673,19 @@ Verifies a Novi AMS member UUID by proxying the Novi API call through the Aether
|
||||
|---|---|---|
|
||||
| `404` | UUID not found in Novi, or Novi returned 200 with no identity data (empty-member anti-pattern — member may have just joined) | Treat as denied / not a member |
|
||||
| `429` | Novi rate limit hit | Surface as `'rate_limited'`; advise retry |
|
||||
| `503` | Novi unreachable or Novi 5xx error | Surface as `'api_error'`; advise retry |
|
||||
| `503` | Novi unreachable or Novi 5xx error | Auto-retry once after 3s; if retry also fails, surface as `'api_error'` |
|
||||
|
||||
### Migration from direct Novi call
|
||||
### Migration from direct Novi call — ✅ Complete (2026-05-19)
|
||||
|
||||
The frontend's `+layout.svelte:verify_novi_uuid()` currently calls Novi directly from the browser. Replace that `fetch()` with this endpoint. Response code mapping:
|
||||
`+layout.svelte:verify_novi_uuid()` now calls this endpoint instead of Novi directly. Response code mapping (for reference):
|
||||
|
||||
| Direct Novi result | This endpoint returns | Frontend state |
|
||||
| Direct Novi result | This endpoint returns | Frontend behavior |
|
||||
|---|---|---|
|
||||
| `200` with identity data | `200` | `verified` |
|
||||
| `200` with no identity data | `404` | `denied` |
|
||||
| `404` | `404` | `denied` |
|
||||
| `429` | `429` | `'rate_limited'` |
|
||||
| Network error / Novi 5xx | `503` | `'api_error'` |
|
||||
| `429` | `429` | Auto-retry after 10s; `'rate_limited'` if retry fails |
|
||||
| Network error / Novi 5xx | `503` | Auto-retry after 3s; `'api_error'` if retry fails |
|
||||
|
||||
### Caching
|
||||
|
||||
|
||||
Reference in New Issue
Block a user