Files
GearBox/.planning/phases/15-external-authentication/15-VERIFICATION.md

14 KiB

phase, verified, status, score, re_verification
phase verified status score re_verification
15-external-authentication 2026-04-04T19:30:00Z passed 12/12 must-haves verified false

Phase 15: External Authentication Verification Report

Phase Goal: Users can register and log in via a self-hosted OIDC auth provider, replacing the built-in single-user auth system Verified: 2026-04-04T19:30:00Z Status: PASSED Re-verification: No — initial verification

Goal Achievement

Observable Truths

All truths are drawn from must_haves across the three plan files.

Plan 01 Truths

# Truth Status Evidence
1 Logto container starts alongside Postgres in docker-compose VERIFIED docker-compose.yml lines 17-31 and docker-compose.dev.yml lines 19-33 both define svhd/logto:latest service with depends_on: postgres: condition: service_healthy
2 Logto admin console is accessible at port 3002 VERIFIED Both compose files expose ports "3001:3001" and "3002:3002"
3 A separate logto database is created automatically on Postgres first boot VERIFIED docker/init-logto-db.sql contains CREATE DATABASE logto;, mounted to docker-entrypoint-initdb.d in both compose files
4 GearBox schema no longer contains users or sessions tables VERIFIED src/db/schema.ts has no export const users or export const sessions; migration drizzle/0010_foamy_marvel_zombies.sql drops both tables
5 All OIDC env vars documented VERIFIED .env.example contains LOGTO_ENDPOINT, LOGTO_CLIENT_ID, LOGTO_CLIENT_SECRET, OIDC_AUTH_SECRET

Plan 02 Truths

# Truth Status Evidence
6 requireAuth middleware validates API keys, MCP Bearer tokens, and OIDC session cookies VERIFIED src/server/middleware/auth.ts checks X-API-KeyAuthorization: BearergetAuth(c) in that order; no getUserCount bypass
7 GET /login redirects unauthenticated users to Logto VERIFIED src/server/index.ts line 44: app.get("/login", oidcAuthMiddleware(), async (c) => c.redirect("/"))
8 GET /callback processes the OIDC authorization code and sets a session cookie VERIFIED src/server/index.ts line 45: app.get("/callback", async (c) => processOAuthCallback(c))
9 GET /api/auth/me returns user identity from OIDC claims or null VERIFIED src/server/routes/auth.ts uses getAuth(c) and returns { user: { id: auth.sub, email: auth.email }, authenticated: true } or { user: null, authenticated: false }
10 API keys continue to authenticate programmatic requests VERIFIED verifyApiKey first path in requireAuth; auth.service.ts retains all four API key functions
11 MCP OAuth /oauth/authorize validates via OIDC session instead of username/password VERIFIED src/server/routes/oauth.ts GET and POST /authorize both call getAuth(c), redirect to /login if null; no verifyPassword reference anywhere

Plan 03 Truths

# Truth Status Evidence
12 Login page redirects users to Logto instead of showing a credential form VERIFIED src/client/routes/login.tsx has no <form>, no <input>, shows a "Sign In" button with onClick={() => { window.location.href = "/login"; }}
13 useAuth hook returns OIDC-based user identity (sub string, not integer id) VERIFIED src/client/hooks/useAuth.ts defines AuthState with `user: { id: string; email?: string }
14 E2E seed script creates API keys directly without inserting into users table VERIFIED e2e/seed.ts has no schema.users reference; creates API key with db.insert(schema.apiKeys) with hardcoded key string
15 Unit tests for auth middleware and service pass without users/sessions tables VERIFIED All three test files pass: auth.service.test.ts (5/5), auth.test.ts middleware (8/8), auth.test.ts routes (8/8)

Score: 12/12 truths verified (note: truths 1-5 count as one per plan, mapping to 12 discrete checks above)

Required Artifacts

Artifact Expected Status Details
docker-compose.yml Production Logto service definition VERIFIED Contains svhd/logto:latest, depends_on: postgres: condition: service_healthy, ports 3001/3002, OIDC env vars on app service
docker-compose.dev.yml Dev Logto service definition VERIFIED Matching logto service with hardcoded dev password
docker/init-logto-db.sql Postgres init script creating logto database VERIFIED Contains CREATE DATABASE logto;
src/db/schema.ts Schema without users/sessions tables VERIFIED No users or sessions exports; retains apiKeys, oauthClients, oauthCodes, oauthTokens
.env.example Documentation of required OIDC env vars VERIFIED Contains LOGTO_ENDPOINT, LOGTO_CLIENT_ID, LOGTO_CLIENT_SECRET, OIDC_AUTH_SECRET
src/server/middleware/auth.ts Three-way auth middleware VERIFIED Exports requireAuth; imports getAuth, verifyApiKey, verifyAccessToken; no getUserCount
src/server/services/auth.service.ts API key CRUD only VERIFIED Exports exactly createApiKey, verifyApiKey, listApiKeys, deleteApiKey; no user/session functions
src/server/routes/auth.ts OIDC /me + API key CRUD routes VERIFIED Exports authRoutes; GET /me uses getAuth(c); GET/POST/DELETE /keys present
src/server/routes/oauth.ts MCP OAuth with OIDC session validation VERIFIED Imports getAuth; both GET and POST /authorize redirect to /login if no OIDC session; no verifyPassword
src/server/index.ts Root-level OIDC routes + updated registration VERIFIED GET /login, /callback, /logout at top-level before /api/* middleware
src/client/routes/login.tsx Login page that redirects to /login (OIDC) VERIFIED No form, no inputs; button with window.location.href = "/login"
src/client/hooks/useAuth.ts Auth hooks without useLogin, useSetup, useChangePassword VERIFIED Exports: useAuth, useLogout, useApiKeys, useCreateApiKey, useDeleteApiKey only
e2e/seed.ts E2E seed without users table insert VERIFIED No schema.users reference; inserts API key into schema.apiKeys
tests/middleware/auth.test.ts Middleware tests for three-way auth VERIFIED 8 tests covering API key, Bearer token, OIDC session paths with mocked getAuth
tests/services/auth.service.test.ts Service tests for API key functions only VERIFIED 5 tests for createApiKey, verifyApiKey, listApiKeys, deleteApiKey only
tests/routes/auth.test.ts Route tests for /me and /keys endpoints VERIFIED 8 tests covering GET /me with mocked OIDC, GET/POST/DELETE /keys with API key auth
From To Via Status Details
src/server/middleware/auth.ts @hono/oidc-auth getAuth() for OIDC session check WIRED Line 2: import { getAuth } from "@hono/oidc-auth", used at line 26
src/server/middleware/auth.ts src/server/services/auth.service.ts verifyApiKey for API key path WIRED Line 3 import, used at line 12
src/server/routes/auth.ts @hono/oidc-auth getAuth for /me response WIRED Line 2 import, used at lines 22-29
src/server/index.ts @hono/oidc-auth oidcAuthMiddleware, processOAuthCallback, revokeSession for OIDC routes WIRED Lines 5-8 import; used at lines 44-49
src/server/routes/oauth.ts @hono/oidc-auth getAuth() replaces verifyPassword in authorize GET/POST WIRED Line 1 import; used at lines 136 and 182
src/server/mcp/index.ts src/server/services/auth.service.ts verifyApiKey WIRED Line 6 import; used in auth middleware block
docker-compose.yml docker/init-logto-db.sql postgres volume mount to docker-entrypoint-initdb.d WIRED Line 10: ./docker/init-logto-db.sql:/docker-entrypoint-initdb.d/init-logto-db.sql
src/client/hooks/useAuth.ts /api/auth/me apiGet fetch WIRED Line 12: apiGet<AuthState>("/api/auth/me")
src/client/routes/login.tsx /login (OIDC server route) window.location.href redirect WIRED Line 40: window.location.href = "/login"
e2e/seed.ts apiKeys table direct insert WIRED Lines 209-211: db.insert(schema.apiKeys).values(...)

Data-Flow Trace (Level 4)

Level 4 data-flow tracing is not applicable for this phase. The primary artifacts are authentication middleware, service functions, and configuration — not components that render dynamic data fetched from a database. The auth middleware routes requests, it doesn't render data. The login page renders a static redirect button.

Behavioral Spot-Checks

Behavior Command Result Status
auth.service exports only API key functions Module export check No createUser, verifyPassword, getUserCount, createSession anywhere in src/server/ PASS
Three-way auth middleware has all paths Code inspection getAuth, verifyApiKey, verifyAccessToken all present, no getUserCount PASS
OIDC routes registered before /api/* in index.ts Code inspection Lines 44-49 before lines 65+ PASS
Login page has no form or inputs Code inspection No <form>, no <input> in login.tsx PASS
Build compiles cleanly bun run build Built in 474ms, no TypeScript errors PASS
Auth service tests bun test tests/services/auth.service.test.ts 5 pass, 0 fail PASS
Auth middleware tests bun test tests/middleware/auth.test.ts 8 pass, 0 fail PASS
Auth route tests bun test tests/routes/auth.test.ts 8 pass, 0 fail PASS

Requirements Coverage

Requirement Source Plan Description Status Evidence
AUTH-01 15-02, 15-03 User can register an account via external OIDC auth provider SATISFIED Logto handles registration; /login redirects to Logto via oidcAuthMiddleware(); login.tsx renders redirect button
AUTH-02 15-02, 15-03 User can log in via external auth provider and access their data SATISFIED OIDC login/callback/logout flow implemented; getAuth(c) validates OIDC session in middleware and /me route
AUTH-03 15-02 API keys remain functional for programmatic access (MCP, scripts) SATISFIED verifyApiKey is first check in requireAuth; MCP middleware checks API key; all four API key service functions preserved; 8/8 middleware tests pass including API key paths. Note: REQUIREMENTS.md still shows [ ] (unchecked) — documentation inconsistency, implementation is complete
AUTH-04 15-01 Auth provider runs self-hosted alongside the application SATISFIED svhd/logto:latest in both docker-compose.yml and docker-compose.dev.yml with Postgres dependency and init script. Note: REQUIREMENTS.md still shows [ ] (unchecked) — documentation inconsistency, implementation is complete
AUTH-05 15-03 E2E tests authenticate via API keys without depending on the auth provider SATISFIED e2e/seed.ts creates apiKeys record with static key; no schema.users reference; no Logto dependency in E2E auth path

Note on REQUIREMENTS.md checkbox discrepancy: AUTH-03 and AUTH-04 are marked [ ] (pending) in REQUIREMENTS.md but their implementation is fully present. The plan summaries claim them complete. This is a documentation synchronization gap — the code satisfies the requirements.

Anti-Patterns Found

No anti-patterns found in phase 15 artifacts.

  • No TODO/FIXME/placeholder comments in modified files
  • No empty handlers or stub implementations
  • No hardcoded empty data passed to renderers
  • No references to removed functions (getUserCount, createUser, verifyPassword, createSession, getSession, deleteSession, refreshSession) anywhere in src/server/
  • No references to removed hooks (useLogin, useSetup (auth), useChangePassword) in src/client/
  • The deferred item noted in 15-03-SUMMARY ("tests/routes/oauth.test.ts still references createUser") was resolved — no such reference exists in current code

Human Verification Required

The following items require a running Logto instance and cannot be verified programmatically:

1. End-to-End OIDC Login Flow

Test: Start docker compose -f docker-compose.dev.yml up -d, configure a Logto application (Traditional Web), set OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_AUTH_SECRET in environment, start bun run dev, visit /login, click "Sign In".

Expected: Redirected to Logto login page; after completing Logto login/registration, redirected back to GearBox dashboard as authenticated user; GET /api/auth/me returns { authenticated: true, user: { id: "<logto-sub>", email: "..." } }.

Why human: Requires running Logto container, browser interaction, real OIDC token exchange; cannot be tested with grep or static analysis.

2. MCP OAuth Flow via OIDC Session

Test: With a valid OIDC session active in the browser, navigate to /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256. Expected: See consent form with "Authorize" button (no username/password fields).

Expected: Consent form shown when OIDC session is present; redirect to login if no session.

Why human: Requires running Logto, active OIDC session cookie, and OAuth client registration.

3. Logto Admin Console Accessibility

Test: docker compose -f docker-compose.dev.yml up -d && curl -s http://localhost:3002 (or open in browser).

Expected: Logto admin console UI loads at port 3002.

Why human: Requires Docker environment and Logto container to be running; cannot verify port accessibility from static analysis.

Gaps Summary

No gaps. All automated verification checks pass. Phase goal is achieved from the perspective of code correctness, architecture, and test coverage. The only items requiring human attention are live integration tests with a running Logto instance, which were gated as a human checkpoint in Plan 03 Task 3.

One minor documentation note: REQUIREMENTS.md checkboxes for AUTH-03 and AUTH-04 remain unchecked despite their implementation being complete. This does not affect the verification status — the code satisfies the requirements.


Verified: 2026-04-04T19:30:00Z Verifier: Claude (gsd-verifier)