auth-yes/tasks/new/2026-0821.01.jul.audit.layered-security.verify-3tier-auth-and-rbac-2303.md

6.8 KiB

TASK METADATA

  • Target Files:
    • server/main.ts
    • server/main.test.ts (new)
    • server/db.ts
    • server/db.test.ts (new)
    • server/spire_ffi.test.ts (new)
    • server/valkey.ts
    • server/valkey.test.ts (new)
    • sdk/mod.ts
    • sdk/hono.ts (new)
    • sdk/mod.test.ts
    • sdk/hono.test.ts (new)
  • Core Objective: Design a comprehensive multi-tier automated test harness and verification suite for Auth-Yes, validating Tier 1 & 2 (Traefik ForwardAuth), Tier 3 (ConnectRPC with SPIFFE/mTLS workload attestation), decoupled Default-Deny RBAC authorization, and modular @auth-yes/sdk client middleware using native Deno mocking.
  • Dependencies: Deno standard library @std/testing/mock, @std/testing/bdd, and hono.
  • Additional Important Notes: Do NOT use Testcontainers or Docker dependencies. Mock external services (Postgres, Valkey, SPIFFE API) using Deno's native mocking capabilities to ensure hermetic, fast, and deterministic test execution. Refactor server/main.ts top-level side effects (server listener and network probes) behind if (import.meta.main) and export app for in-memory app.request() testing.

Architectural Considerations & Risks

Risks

  1. Mocking Accuracy: Using mocks for PostgreSQL (db.ts) and Valkey (valkey.ts) risks diverging from actual production behavior if the database schema or RESP3 client tracking intricacies change. We must ensure the mocked responses accurately reflect both success cases and edge cases (e.g., specific Valkey error codes, Postgres constraint violations).
  2. Top-Level Side Effects during Test Imports: Currently, importing server/main.ts, server/db.ts, or server/valkey.ts executes top-level code (e.g., Deno.serve(...), MetadataService.initialize(), pingValkey(), and strict env validation). These must be cleanly decoupled/guarded so test runners can import modules in isolation without triggering network listeners or unhandled runtime rejections.
  3. SPIFFE Workload Mismatches: Relying on explicit Deno stubs for extractSpiffeIdFromCert and fetchSpiffeIdentity must be robust enough to simulate corrupted X.509 certificates or unrecognized SPIFFE IDs deterministically. If stubs aren't well isolated between tests, state bleed could cause flaky test runs.
  4. SDK Modularity: Integrating Hono middleware directly into sdk/mod.ts risks coupling the core SDK to a specific web framework. To preserve Rule 1 of AGENTS.md (zero-dependency SDK), Hono helpers must be housed in a dedicated modular submodule (sdk/hono.ts) while keeping sdk/mod.ts completely framework-agnostic.

Alternatives

  • Alternative 1: E2E Containers (Rejected): Spinning up live Postgres, Valkey, and SPIRE agent containers using Docker/Testcontainers would provide higher fidelity but drastically slow down the test suite and introduce environment dependency friction. As requested, we are prioritizing hermetic Deno native mocking.
  • Alternative 2: In-Memory Hono App & Direct RPC Testing (Selected): Leverage Hono's .request() helper on the exported app instance to test API endpoints (Tier 1 & 2) in memory while stubbing the underlying sql and valkey exports. For Tier 3 (ConnectRPC), test both direct service handler invocations and RPC routes in memory.

Proposed Implementation

Phase 1: Test Environment & Server Module Decoupling

  1. Entrypoint Refactoring (server/main.ts):
    • Export app from server/main.ts.
    • Wrap Deno.serve(...), pingValkey(), and MetadataService.initialize() inside if (import.meta.main) { ... } so importing main.ts in tests does not spin up a live server or execute remote network calls.
    • Provide safe fallback defaults for RP_ID and ORIGIN in test mode or allow environment overrides before import.
  2. Database & Valkey Isolation:
    • Create reusable mock helpers using @std/testing/mock (stub and spy).
    • Stub the sql export in server/db.ts to return controlled records representing mock users, apps, and grants.
    • Stub valkey.get and valkey.setex in server/valkey.ts to simulate session cache hits, misses, and invalidations.
  3. SPIFFE/mTLS Mocking (Tier 3):
    • Stub extractSpiffeIdFromCert in server/spire_ffi.ts.
    • Provide test cases for valid SPIFFE URIs (e.g., spiffe://system.local/ed-droid-backend), invalid URIs, missing extensions, and unparseable certs.

Phase 2: Tier 1 & 2 (Edge ForwardAuth) Verification

  1. Target: GET /api/forward-auth via app.request() in server/main.test.ts.
  2. Test Cases:
    • Valid session cookie: Assert HTTP 200 OK and presence of X-Forwarded-User and X-Forwarded-User-Id headers.
    • Missing session cookie: Assert HTTP 401 Unauthorized.
    • Invalid/Expired session (simulated via Valkey cache miss): Assert HTTP 401 Unauthorized.
    • Suspended account (simulated via mocked sql returning account_status: 'suspended'): Assert HTTP 403 Forbidden.

Phase 3: Tier 3 (Zero-Trust Mesh & RBAC) Verification

  1. Target: AuthService.validateSession ConnectRPC endpoint & handler.
  2. Test Cases:
    • Default-Deny: Unassigned user attempting to access a valid SPIFFE ID. Assert valid: false and authorization rejection.
    • Valid RBAC Grant: Authenticated user with explicit grant for the calling app's SPIFFE ID. Assert valid: true, correct UUID, and correct scope (e.g., viewer, admin).
    • SPIFFE Attestation Failure: Missing or invalid x-peer-cert header. Assert validation failure.

Phase 4: Audit Ledger Verification

  1. Target: auditLog function calls in server/audit.ts.
  2. Test Cases:
    • Spy on auditLog in server/audit.ts.
    • Verify that successful logins (login_success), login failures (login_failed), and explicit access denials (session_validation_failed) generate the correct event payload structures and are dispatched to the mocked DB.

Phase 5: Modular SDK Ergonomics & Hono Middleware

  1. Target: sdk/hono.ts (new) and sdk/hono.test.ts (new).
  2. Implementation Steps:
    • Create sdk/hono.ts exporting createAuthMiddleware(sdk: AuthSdk) so the core sdk/mod.ts remains framework-agnostic.
    • The middleware extracts the session_id cookie or Authorization header, calls sdk.requireAuth(), and injects the resulting userId into the Hono context (c.set('userId', userId)).
    • If requireAuth throws, the middleware catches it and returns HTTP 401 Unauthorized.
  3. Verification:
    • In sdk/hono.test.ts, create a test Hono app, attach the middleware, and verify that authorized requests pass and unauthorized requests return 401 Unauthorized.