# TASK METADATA - **Target Files:** `server/db.ts`, `server/auth-session.ts`, `server/main.ts`, `server/main.test.ts`, `ui/components/AdminAppsPage.tsx`, `ui/mod.ts`, `infra/setup.ts`, `docs/TIER1_INGRESS_SPEC.md` - **Core Objective:** Implement Tier 1 Universal Global Edge Ingress Protection on Traefik with dynamic Valkey-cached bypass matrix, self-exemption, CIDR allowlisting, unregistered app SSR fallback, and ephemeral guest sandboxes. - **Dependencies:** PostgreSQL `apps` schema, Valkey 8 cache, `docs/FORWARDAUTH_REDIRECT_SPEC.md`. - **Additional Important Notes:** Must guarantee $<30\mu s$ edge lookups via Valkey L1/L2 caching and prevent infinite redirect deadlocks on `auth-api` and ACME challenge routers. --- ### 1. TASK METADATA (Defined in the header block above) ### 2. Architectural Considerations & Risks - **Risks & Vulnerabilities:** - **Recursive Deadlocks (Self-Exemption):** Applying ForwardAuth globally at `entryPoints.websecure.http.middlewares` creates an infinite loop if `auth-api` intercepts its own login/verification endpoints. `auth.atyg.org` and `/.well-known/acme-challenge/*` must be explicitly exempt. - **Edge Latency Regressions:** Querying PostgreSQL on every incoming ForwardAuth request for dynamic bypass rules would bottleneck cluster throughput. All `is_public`, `bypass_paths`, and `allowed_cidrs` fields must be cached inside the Valkey `auth:app_by_host:` key. - **ReDoS in Path Matching:** Evaluating wildcard bypasses using unconstrained regular expressions could allow malicious paths to trigger catastrophic backtracking. Path matching must use fast, deterministic prefix and glob matching. - **Open Redirects on Unregistered Apps:** When routing unregistered domains to SSR error views, destination parameters must be strictly validated against approved hosts to prevent phishing vectors. - **Alternatives & Architecture Decisions:** - **Valkey App Caching over Traefik HTTP Dynamic Provider:** Evaluating dynamic bypass paths inside `auth-api` via Valkey caches eliminates Traefik config file churn, provider polling overhead, and proxy reload downtime. - **Defense-in-Depth Universal Bypasses:** - _Traefik Level:_ `/.well-known/acme-challenge/*` and global `/healthz` bypass ForwardAuth entirely at the edge router level to avoid certificate renewal failures during container restarts. - _Auth-API Level:_ Application-specific paths (`/api/public/*`, `raw/branch/main/sdk/*`, `/webhooks/*`) and CIDR subnets are evaluated in `auth-api` with zero network overhead. - **Dual-Response Protocol Compliance:** Unregistered domains receive an HTTP `302 Redirect` to `https://auth.atyg.org/errors/unregistered?host=...` for browsers (`Accept: text/html`) and a clean HTTP `403 Forbidden` (`{"error": "Application not registered"}`) for APIs/cURL. - **Ephemeral Guest Sandboxes (Use Case 11):** Ephemeral sessions are held in Valkey with `account_status: "guest"` and promoted in-flight to full users upon passkey enrollment without losing UUID or state. ### 3. Proposed Implementation #### Phase 1: Database Schema & Valkey Caching Upgrades 1. **Database Migration (`server/db.ts`):** - Add idempotent migrations inside `initDb()`: ```sql ALTER TABLE apps ADD COLUMN IF NOT EXISTS is_public BOOLEAN DEFAULT FALSE; ALTER TABLE apps ADD COLUMN IF NOT EXISTS bypass_paths TEXT[] DEFAULT '{}'; ALTER TABLE apps ADD COLUMN IF NOT EXISTS allowed_cidrs TEXT[] DEFAULT '{}'; ``` 2. **Valkey App Cache Hydration (`server/auth-session.ts`):** - Update `getAppByHost(host)` to select `id, name, domain, is_public, bypass_paths, allowed_cidrs`. - Cache the complete application metadata in `auth:app_by_host:` with 1-hour TTL. #### Phase 2: Dynamic Bypass Evaluator & ForwardAuth Hardening 1. **Path & CIDR Matcher Helper (`server/auth-session.ts`):** - Implement `isPathBypassed(requestPath: string, bypassPaths: string[]): boolean` using deterministic prefix matching (`path.startsWith(...)` and `/*` wildcard support). - Implement `isIpAllowed(clientIp: string, allowedCidrs: string[]): boolean` for subnet validation. 2. **Update `GET /api/forward-auth` (`server/main.ts`):** - **Step 1: Unregistered App Check:** If `appRecord` is null: - If `Accept: text/html`: Return `302 Redirect` to `https://${loginDomain}/errors/unregistered?host=${encodeURIComponent(host)}`. - Else: Return `403 Forbidden` (`{"error": "Application not registered"}`). - **Step 2: Dynamic Bypass Check:** - If `appRecord.is_public === true`, or `isPathBypassed(uri, appRecord.bypass_paths)`, or `isIpAllowed(clientIp, appRecord.allowed_cidrs)`: - Immediately return `200 OK` (bypassing session checks). - **Step 3: RFC 9421 & Session Checks:** Proceed with existing HTTP Signatures and WebAuthn session validation. #### Phase 3: Unregistered Application SSR Error Page & Admin UI 1. **Create SSR Error View (`ui/components/UnregisteredAppPage.tsx`):** - Pure Hono SSR JSX page explaining the domain is not registered in the Auth-Yes catalog, with a direct 1-click button for Global Admins: _"Register Application in IAM Console"_. 2. **Update Route in `ui/mod.ts`:** - Mount `GET /errors/unregistered` rendering `UnregisteredAppPage`. 3. **Management Console Updates (`ui/components/AdminAppsPage.tsx` & `server/main.ts`):** - Expose `is_public`, `bypass_paths`, and `allowed_cidrs` in `POST /api/admin/apps` and `PUT /api/admin/apps/:id`. - Add toggle switches and input fields to the Admin Web UI. #### Phase 4: Ephemeral Guest Sandboxes (Use Case 11) 1. **Guest Endpoint (`server/main.ts`):** - `POST /api/guests/sandbox`: Generates a guest `sessionId`, stores `{ uuid: guestUuid, username: "guest-", account_status: "guest" }` in Valkey with 2-hour TTL, and returns the session cookie. 2. **In-Flight Passkey Promotion (`server/main.ts`):** - Update `/api/register/verify` to detect `upgrade_session` parameter, creating the PostgreSQL user with the existing `guestUuid` and changing status to `active`. #### Phase 5: Documentation & Traefik Compose Guides 1. **Author `docs/TIER1_INGRESS_SPEC.md`:** - Document static/dynamic Traefik configurations for `entryPoints.websecure.http.middlewares=auth-forward@docker` with router-level self-exemption overrides for `auth.atyg.org`. ### 4. Testing & Verification Matrix - [ ] **Unit Tests (`server/main.test.ts`):** - Verify `GET /api/forward-auth` returns `200 OK` when `appRecord.is_public === true`. - Verify `GET /api/forward-auth` returns `200 OK` for paths matching `bypass_paths` (e.g. `/api/public/ping`). - Verify `GET /api/forward-auth` returns `302 Redirect` to `/errors/unregistered` for unauthenticated browser requests to unregistered domains. - Verify `GET /api/forward-auth` returns `403 Forbidden` for API requests to unregistered domains. - Verify guest session generation and in-flight passkey promotion. - [ ] **Quality Gates:** - `deno fmt`, `deno task lint`, `deno task check`, `deno task test` (100% pass).