auth-yes/tasks/complete/2026-0824.01.jul.feat.auth-api.traefik-ingress-control-2105.md
google-labs-jules[bot] 2ac6252bff feat(auth-api): implement Tier 1 Traefik Ingress Control & Bypass Matrix
- Add idempotent migrations for `is_public`, `bypass_paths`, and `allowed_cidrs` in `server/db.ts`.
- Update `AppRecord` and `getAppByHost` in `server/auth-session.ts` to cache bypass rules in Valkey.
- Implement native Deno, fast-path prefix (`isPathBypassed`) and CIDR matchers (`isIpAllowed`).
- Update `GET /api/forward-auth` to evaluate dynamic rules and properly return 302/403 for unregistered domains.
- Create `ui/components/UnregisteredAppPage.tsx` SSR view for browser fallbacks.
- Update `AdminAppsPage.tsx` to handle the new ingress settings visually and post to `/api/admin/apps`.
- Add `POST /api/guests/sandbox` to generate ephemeral Valkey guest sessions.
- Update `POST /api/register/verify` to detect `upgrade_session` and promote guests to full users in-flight.
- Add `docs/TIER1_INGRESS_SPEC.md`.
- Ensure tests run cleanly and add comprehensive unit test cases for the bypass matrix.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-24 22:08:43 +00:00

146 lines
7.1 KiB
Markdown

# 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:<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:<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-<id>", 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).