feat(idaa): server-side Novi member verification endpoint

Proxies GET /customers/{uuid} to Novi AMS server-to-server so members'
browser IPs are no longer in the call path, eliminating false "Access
Denied" for users on hotel/conference WiFi, VPNs, and CDN-filtered nets.

- New router: GET /v3/action/idaa/novi_member/{uuid}
- Business logic in app/methods/idaa_novi_verify_methods.py
  - Redis cache (4h TTL, key: idaa:novi_member:{uuid})
  - 404 never cached (recently-joined member anti-pattern)
  - Email space→+ normalization (Novi quirk)
  - Display name: "FirstName L." format with Name field fallback
- Registered in registry.py under /v3/action/idaa tag
- 9 unit tests covering all response paths (200/404/429/503/unreachable,
  cache hit, email normalization, display name format)
- Frontend guide (Section 12) and tests/README updated with full spec
  and migration table for frontend hand-off

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-19 18:35:01 -04:00
parent c7335bbc3e
commit 221854df90
7 changed files with 519 additions and 1 deletions

View File

@@ -39,6 +39,42 @@
- [ ] **Step 2:** Coordination (Verify Frontend uses `x-account-id` instead of token).
- [ ] **Step 3:** Frontend V3 WebSocket integration test — queued after IDAA-specific work. Backend is ready (auth wired, heartbeat presence refresh confirmed, unit tests passing). Frontend guide updated at `GUIDE__AE_API_V3_for_Frontend_websockets.md`.
## 🔌 IDAA: Server-Side Novi Verification (Mini Project)
> **Status: P1P4 Complete (May 2026).** Endpoint live at `GET /v3/action/idaa/novi_member/{uuid}`. P5 (frontend migration) is the remaining step.
> Rationale and frontend integration notes: `aether_app_sveltekit/documentation/CLIENT__IDAA_and_customized_mods.md` → "Planned: Server-Side Novi Verification"
**Goal:** Proxy the Novi member-verification call server-to-server (FastAPI → Novi) so members' browser IPs are no longer in the call path.
- [x] **[P1] New router:** `app/routers/api_v3_actions_idaa.py`
- Route: `GET /v3/action/idaa/novi_member/{uuid}`
- Required auth: `Depends(get_account_context)` — valid API key + any account context (x-account-id, JWT, or bypass). This is the standard V3 gate.
- Reads `novi_idaa_api_key` / `novi_api_root_url` from site `cfg_json` via `_load_idaa_cfg()` (same as Mailman bridge)
- Calls Novi: `GET {novi_api_root_url}/customers/{uuid}` with `Authorization: Basic {api_key}`
- Normalize email: `.replace(' ', '+')` (Novi quirk — see Novi-Mailman bridge notes)
- Build display name: `"{FirstName} {LastName[0]}."` format, fall back to `Name` field
- Returns `{ "verified": true, "full_name": "...", "email": "..." }` on success
- Returns `404` if Novi 200 with no identity data (empty-member anti-pattern)
- Returns `429` if Novi rate limits; `503` if Novi unreachable or 5xx
- Business logic in `app/methods/idaa_novi_verify_methods.py`
- [x] **[P2] Redis cache:**
- Key: `idaa:novi_member:{uuid}` — TTL 4 hours
- Note: `account_id` dropped from key — Novi credentials are hardcoded to the IDAA site; same UUID always returns the same data regardless of caller, so per-caller scoping wastes Redis space and halves hit rate.
- Cache only verified (200) results — do NOT cache 404 (member may have just joined)
- Uses `redis_client` from `lib_redis_helpers.py` directly
- [x] **[P3] Register in registry:** Added to `routers/registry.py` at `/v3/action/idaa` tag `IDAA Actions (V3)`. Confirmed live — endpoint appears in `/openapi.json`.
- [x] **[P4] Tests:** `tests/unit/test_unit_idaa_novi_verify.py` — 9 tests, all passing.
- Mock Novi responses (200/empty-200/404/429/503/unreachable)
- Verify Redis cache is set on 200, hit bypasses Novi call
- Verify email normalization (space → +)
- Verify display name format (5 cases)
- [ ] **[P5] Coordinate with Frontend Agent** once P1P3 are done:
- Frontend replaces direct `fetch()` to Novi in `+layout.svelte:verify_novi_uuid()`
- Map response codes: 200 → verified, 404 → denied, 429 → `'rate_limited'`, 503 → `'api_error'`
## 🛡️ Security & Privacy Baseline (IDAA)
- **Status:** **ENFORCED**.
- **Maintenance:** Run `tests/e2e/test_e2e_v3_security_audit.py` after ANY router or registry change.