diff --git a/tasks/new/2026-0826.01.jul.story.events.phase-1-guest-ingress-1400.md b/tasks/new/2026-0826.01.jul.story.events.phase-1-guest-ingress-1400.md new file mode 100644 index 0000000..5e397ea --- /dev/null +++ b/tasks/new/2026-0826.01.jul.story.events.phase-1-guest-ingress-1400.md @@ -0,0 +1,85 @@ +# TASK METADATA + +- **Target Files:** `server/routes/auth_forward.ts`, `ui/db_queries.ts`, + `ui/mod.ts`, `server/routes/events.ts`, `server/tests/forward_auth.test.ts`, + `server/tests/events.test.ts` +- **Core Objective:** Implement Phase 1 of the Event & Session Overhaul: Enable + Traefik ForwardAuth guest ingress for scoped event attendees, bridge session + custom scopes to the Launchpad UI, and wire audit logging on event seat + claims. +- **Dependencies:** None. +- **Additional Important Notes:** Follow zero-trust default-deny principles for + ForwardAuth. Guest sessions minting needs to correctly log to the Merkle audit + ledger for SIEM visibility. + +--- + +### 2. Architectural Considerations & Risks + +- **Risks:** + - **ForwardAuth Security:** Modifying ForwardAuth to accept `guest` accounts + risks inadvertently allowing guests to access applications they are not + explicitly scoped for. Strict validation of `auth.customScopes` against the + requested `appRecord.name` is critical. + - **Launchpad Leakage:** The `getDashboardApps` query modification must be + robust. If the UNION query is not correctly structured, it could leak + visibility of unregistered or unauthorized apps to guests. + - **Audit Logging Integrity:** Failure to capture the audit events properly + during the `join` phase would break SIEM visibility. Using the non-blocking + `auditWrapper.auditLog` is required to avoid impacting user request latency + while ensuring compliance. + +- **Alternatives:** + - We considered a bypass in `ui/db_queries.ts` where guests completely skip + the `grants` JOIN. However, using a `UNION` is structurally superior as it + seamlessly supports both pure guests and regular active users who might hold + delegated scoped passes. It provides a unified data retrieval flow. + +### 3. Proposed Implementation + +#### Phase 1: ForwardAuth Guest Ingress (`server/routes/auth_forward.ts`) + +1. **Permit Guest Accounts:** Update the account status validation to allow + `user.account_status === 'guest'` in addition to `'active'`. +2. **Custom Scopes Validation:** If the user is a guest (or relying on custom + scopes), verify that `auth.customScopes` contains the explicit application + grant (e.g., `app:${appRecord.name}`). If not, return a 403 Forbidden. +3. **Header Injection:** For guest sessions, compute the scopes to inject into + `X-Forwarded-Scopes` by taking the comma-joined list of `auth.customScopes` + (`auth.customScopes.join(",")`). This propagates the app scope (e.g., + `app:ed-droid`) and role (e.g., `viewer`) to downstream reverse proxies. + +#### Phase 2: Launchpad App Query (`ui/db_queries.ts` & `ui/mod.ts`) + +1. **Function Signature Update:** Update + `getDashboardApps(userId: string, isAdmin: boolean, customScopes?: string[])` + to accept the session's custom scopes. +2. **UNION Query:** Update the non-admin SQL query to use a `UNION`. + - The first part queries explicitly granted apps via the `grants` table. + - The second part parses the provided `customScopes` array to extract allowed + app names (e.g., stripping the `app:` prefix), querying the `apps` table + for those matching names, and assigning them a pseudo-role such as + `"Guest (Viewer)"` (or extracting the role from the scopes if possible). +3. **UI Integration:** In `ui/mod.ts`, pass `auth.customScopes` from the + resolved session into `getDashboardApps`. + +#### Phase 3: Join Audit Logging (`server/routes/events.ts`) + +1. **Audit `POST /api/join` (Web):** Immediately after minting the guest + session, invoke + `auditWrapper.auditLog(guestUuid, "event_seat_claimed", event.id, { slug: event.slug, name: event.name, seatNumber: event.seats_claimed, method: "web" }, getClientIp(c))`. +2. **Audit `GET /join/:slug` (CLI):** Similarly, wire + `auditWrapper.auditLog(guestUuid, "event_seat_claimed", event.id, { slug: event.slug, name: event.name, seatNumber: event.seats_claimed, method: "cli" }, getClientIp(c))` + into this endpoint before returning the token. + +#### Phase 4: Unit Testing + +1. **ForwardAuth Tests (`server/tests/forward_auth.test.ts`):** + - Add tests simulating a guest session with an `app:{name}` scope to ensure a + `200 OK` response with correctly formatted `X-Forwarded-Scopes`. + - Add tests simulating a guest session without the necessary `app:{name}` + scope to ensure a `403 Forbidden` response. +2. **Events Tests (`server/tests/events.test.ts`):** + - Ensure the `POST /api/join` and `GET /join/:slug` assertions capture the + `auditLog` spy calls and verify the exact payload (`event_seat_claimed` + action, `event.id` as resource, correct metadata structure).