docs(phase-15): complete phase execution
This commit is contained in:
@@ -189,7 +189,7 @@ Plans:
|
||||
| 12. Comparison View | v1.3 | 1/1 | Complete | 2026-03-17 |
|
||||
| 13. Setup Impact Preview | v1.3 | 0/2 | Not started | - |
|
||||
| 14. PostgreSQL Migration | v2.0 | 0/? | Not started | - |
|
||||
| 15. External Authentication | v2.0 | 1/1 | Complete | 2026-04-04 |
|
||||
| 15. External Authentication | v2.0 | 1/1 | Complete | 2026-04-04 |
|
||||
| 16. Multi-User Data Model | v2.0 | 0/? | Not started | - |
|
||||
| 17. Object Storage | v2.0 | 0/? | Not started | - |
|
||||
| 18. Global Items & Public Profiles | v2.0 | 0/? | Not started | - |
|
||||
|
||||
@@ -4,13 +4,13 @@ milestone: v1.3
|
||||
milestone_name: Research & Decision Tools
|
||||
status: planning
|
||||
stopped_at: Completed 15-03-PLAN.md
|
||||
last_updated: "2026-04-04T18:55:48.924Z"
|
||||
last_activity: 2026-04-03 — v2.0 roadmap created (Phases 14-18)
|
||||
last_updated: "2026-04-04T19:52:25.447Z"
|
||||
last_activity: 2026-04-04
|
||||
progress:
|
||||
total_phases: 8
|
||||
completed_phases: 7
|
||||
total_plans: 13
|
||||
completed_plans: 11
|
||||
total_phases: 9
|
||||
completed_phases: 8
|
||||
total_plans: 21
|
||||
completed_plans: 19
|
||||
percent: 0
|
||||
---
|
||||
|
||||
@@ -25,10 +25,10 @@ See: .planning/PROJECT.md (updated 2026-04-03)
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 14 of 18 (PostgreSQL Migration)
|
||||
Plan: 0 of ? in current phase
|
||||
Phase: 15 of 18 (PostgreSQL Migration)
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: 2026-04-03 — v2.0 roadmap created (Phases 14-18)
|
||||
Last activity: 2026-04-04
|
||||
|
||||
Progress: [----------] 0% (v2.0 milestone)
|
||||
|
||||
|
||||
167
.planning/phases/15-external-authentication/15-VERIFICATION.md
Normal file
167
.planning/phases/15-external-authentication/15-VERIFICATION.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
phase: 15-external-authentication
|
||||
verified: 2026-04-04T19:30:00Z
|
||||
status: passed
|
||||
score: 12/12 must-haves verified
|
||||
re_verification: 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-Key` → `Authorization: Bearer` → `getAuth(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 } | null` |
|
||||
| 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 |
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| 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)_
|
||||
Reference in New Issue
Block a user