auth-yes/tasks/new/2026-0827.02.gem.feat.security.p2p-vouching-and-web-of-trust-1040.md

150 lines
6.8 KiB
Markdown

# TASK METADATA
- **Target Files:**
- `server/db.ts`
- `server/routes/events.ts`
- `server/routes/sessions.ts`
- `server/routes/forward_auth.ts`
- `ui/components/events/VouchingModal.tsx`
- `ui/components/sessions/EventGuestsDrawer.tsx`
- `ui/components/sessions/SessionsScript.tsx`
- `server/tests/vouching.test.ts`
- **Core Objective:** Implement a decentralized Peer-to-Peer (P2P) Vouching Web
of Trust for high-security events, where joining guests enter a quarantined
read-only state until verified by a peer or host via QR/Emoji pairing, with
recursive cascade revocation.
- **Dependencies:**
- Existing `event_passes` schema and `users.event_pass_id` relational linking.
- Valkey L1/L2 cache for real-time session state propagation.
- **Additional Important Notes:**
- Must remain opt-in per event via `require_vouching BOOLEAN DEFAULT FALSE`.
- UI must remain 100% React-free Hono SSR JSX with vanilla JavaScript DOM
state manipulation.
---
## 2. Architectural Considerations & Risks
### Risks
1. **Vouching Graph Cycles & Endless Loops:** If attendee A vouches for B, B
vouches for C, and C attempts to vouch for A, cyclical graphs could corrupt
hierarchy metrics or create infinite loops during cascade revocation.
- _Mitigation:_ Enforce an acyclic tree constraint. A user can only be
vouched for once (setting an immutable `vouched_by UUID` pointing to their
verified parent). Vouchers must already be in an `active` (non-quarantined)
state.
2. **Cascade Revocation Overhead & Blast Radius:** If an organizer revokes a
rogue voucher at the root of a large subtree, revoking dozens of downstream
guest sessions could block the event loop or leave orphaned cache records.
- _Mitigation:_ Use a PostgreSQL Recursive Common Table Expression (CTE) to
fetch all downstream descendant session IDs in a single atomic query
(`WITH RECURSIVE subordinates AS (...)`), followed by bulk atomic cache
eviction in Valkey using pipelined `UNLINK`.
3. **UX Friction in Standard Workshops:** Mandating vouching for open public
demos or casual workshops adds unnecessary friction.
- _Mitigation:_ Make P2P vouching an opt-in toggle (`require_vouching`) in
the `[ Delegate Session ]` creation drawer.
### Alternatives
- **Centralized Host-Only Approval Queue:** Instead of P2P vouching, all
attendees could wait in a lobby for the host to click "Approve". While
simpler, this creates an operational bottleneck for workshops with 50+
attendees. P2P vouching allows any already-admitted participant to onboard
their neighbor, enabling rapid, distributed verification.
- **WebRTC / Bluetooth Proximity:** We considered WebRTC or WebBluetooth for
physical co-location verification. However, browser support and permission
prompts make this fragile. A 3-emoji visual sequence + 1-click QR code camera
scan provides instant, high-friction-to-bots physical co-presence verification
with zero native permission dependencies.
---
## 3. Proposed Implementation
### Phase 1: Database Schema & Entity Relationships (`server/db.ts`)
1. Add vouching metadata columns to `event_passes`:
- `require_vouching BOOLEAN DEFAULT FALSE`
- `vouch_depth_limit INT DEFAULT 2` (0 = host-only, 1 = direct guests only,
2+ = transitive)
2. Add verification tracking to `users`:
- `is_quarantined BOOLEAN DEFAULT FALSE`
- `vouched_by UUID REFERENCES users(id) ON DELETE SET NULL`
- `vouched_at TIMESTAMP WITH TIME ZONE`
3. Add ephemeral vouching challenge table `vouch_challenges`:
- `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`
- `target_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE`
- `event_id UUID NOT NULL REFERENCES event_passes(id) ON DELETE CASCADE`
- `emoji_sequence TEXT NOT NULL` (e.g. `🚀-🦊-⚡`)
- `pairing_code TEXT UNIQUE NOT NULL` (6-char alphanumeric code or QR token)
- `created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()`
- `expires_at TIMESTAMP WITH TIME ZONE NOT NULL`
### Phase 2: Ingress Quarantine & Verification API (`server/routes/events.ts`)
1. **Quarantined Ingress:**
- In `POST /api/join` and `GET /join/:slug`, if
`event.require_vouching = TRUE`, set `users.is_quarantined = TRUE` and tag
the session with custom scope `['guest:quarantined']`.
- Issue a `vouch_challenge` returning the 3-emoji sequence, QR payload, and
pairing code.
2. **ForwardAuth Guard:**
- In `server/routes/forward_auth.ts`, check if the session contains
`guest:quarantined`. If quarantined, block access to internal apps (return
403 or redirect to `/vouch/pending`).
3. **Vouching Endpoints:**
- `POST /api/events/:id/vouch/verify`:
- Authenticated voucher submits the 3-emoji sequence or scans the QR
pairing token.
- Verify the voucher is an active (non-quarantined) participant of the same
event.
- Check `vouch_depth_limit`.
- Atomically update `users.is_quarantined = FALSE`, set
`users.vouched_by = voucher.id`, delete the challenge, and broadcast
session upgrade to Valkey.
### Phase 3: Recursive Cascade Revocation (`server/routes/sessions.ts`)
1. Refactor `DELETE /api/sessions/:id`:
- If the target session belongs to a guest user who has vouched for others,
execute a recursive CTE query:
```sql
WITH RECURSIVE tree AS (
SELECT id, event_pass_id FROM users WHERE id = ${targetUserId}
UNION ALL
SELECT u.id, u.event_pass_id FROM users u
INNER JOIN tree t ON u.vouched_by = t.id
)
SELECT s.id as session_id, t.id as user_id FROM sessions s
JOIN tree t ON s.user_id = t.id;
```
- Delete all discovered sessions in PostgreSQL and evict all matching keys
from Valkey.
### Phase 4: UI & Mobile Pairing Interface
1. **Attendee Waiting Screen (`ui/components/events/QuarantineLobby.tsx`):**
- Render large high-contrast 3-emoji sequence and QR code: "Show this to the
workshop host or an admitted attendee to unlock full access".
- Poll or listen for activation event via SSE/fetch.
2. **Voucher Action Modal (`ui/components/events/VouchingModal.tsx`):**
- Admitted attendees see a `[ 🤝 Vouch for Peer ]` button in their top bar or
Guest Drawer.
- Opens simple camera QR scanner or 3-emoji selector grid to confirm the peer
in front of them.
3. **Guest Drawer Web of Trust Tree:**
- In `EventGuestsDrawer.tsx`, show vouching tree indentation / badge
(`Vouched by Tyler G`).
### Phase 5: Quality Gates & Integration Testing
1. Authored unit and integration tests in `server/tests/vouching.test.ts`:
- Verify unvouched attendees cannot access ForwardAuth-protected endpoints.
- Verify valid emoji/QR verification lifts quarantine immediately.
- Verify cascading revocation cleans up all child and grandchild sessions
recursively.
2. Run `deno fmt`, `deno task lint`, `deno task check`, and
`deno test -A --no-check`.