- Allowed guest accounts to be evaluated in `forward-auth`
- Validated `guest` account's `customScopes` and rejected ungranted access
- Added Array parameterization and `UNION` query in `getDashboardApps`
- Mapped `customScopes` to `getDashboardApps` in the UI route `/dashboard`
- Wired web and CLI joins in `events.ts` to `auditWrapper.auditLog` using correct schema (`event.id`, `{slug, method}`)
- Added `auditWrapper.auditLog` unit test validations in `events.test.ts`
- Added guest session scope unit tests in `forward_auth.test.ts`
- Moved Markdown tasks logic from `tasks/new/` to `tasks/complete/`
Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
4.5 KiB
4.5 KiB
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 session minting must correctly log to the Merkle audit ledger for SIEM visibility.
2. Architectural Considerations & Risks
-
Risks:
- ForwardAuth Security: Modifying ForwardAuth to accept
guestaccounts risks inadvertently allowing guests to access applications they are not explicitly scoped for. Strict validation ofauth.customScopesagainst the requestedappRecord.nameis critical. - Launchpad SQL Array Guard: If a user or guest has no
app:custom scopes, passing an empty array into anIN ()clause causes invalid SQL. The query must useif (appNames.length > 0)orWHERE name = ANY(${appNames}::text[]). - Audit Logging Integrity: Failure to capture the audit events properly
during the
joinphase would break SIEM visibility. Using the non-blockingauditWrapper.auditLogwithgetClientIp(c)is required.
- ForwardAuth Security: Modifying ForwardAuth to accept
-
Alternatives:
- We considered a bypass in
ui/db_queries.tswhere guests completely skip thegrantsJOIN. However, using aUNIONis 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.
- We considered a bypass in
3. Proposed Implementation
Phase 1: ForwardAuth Guest Ingress (server/routes/auth_forward.ts)
- Permit Guest Accounts: Update the account status validation (around
line 120) to allow
user.account_status === 'guest'in addition to'active'. - Custom Scopes Validation: If the user is a guest (or relying on custom
scopes), verify that
auth.customScopescontains the explicit application grant (e.g.,app:${appRecord.name}or wildcard*). If not, return a403 Forbidden. - Header Injection: For guest sessions, compute the scopes to inject into
X-Forwarded-Scopesby taking the comma-joined list ofauth.customScopes(auth.customScopes.filter(Boolean).join(",") || "viewer"). 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)
- Function Signature Update: Update
getDashboardApps(userId: string, isAdmin: boolean, customScopes?: string[])to accept the session's custom scopes. - UNION Query with Array Guard:
- Extract app names from
customScopes(e.g. scopes starting withapp:). - If
appNames.length > 0, execute aUNIONwithSELECT id, name, description, domain, 'Guest (Viewer)' as role FROM apps WHERE domain IS NOT NULL AND name = ANY(${appNames}::text[]). - If
appNames.length === 0, return only the primarygrantsquery result.
- Extract app names from
- UI Integration: In
ui/mod.tsunder/dashboard, passauth.customScopesfrom the resolved session intogetDashboardApps.
Phase 3: Join Audit Logging (server/routes/events.ts)
- Import
getClientIp: ImportgetClientIpfrom../middleware.ts. - 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)). - 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)).
Phase 4: Unit Testing
- ForwardAuth Tests (
server/tests/forward_auth.test.ts):- Add test:
Tier 1 & 2: GET /api/forward-auth - Guest session with app scope allowed. - Add test:
Tier 1 & 2: GET /api/forward-auth - Guest session without app scope rejected (403).
- Add test:
- Events Tests (
server/tests/events.test.ts):- Ensure the
POST /api/joinandGET /join/:slugassertions verify theauditLogcall payload (event_seat_claimedaction,event.idas resource, and correct metadata structure).
- Ensure the