--- phase: 15-external-authentication plan: 01 type: execute wave: 1 depends_on: [] files_modified: - docker-compose.yml - docker-compose.dev.yml - docker/init-logto-db.sql - src/db/schema.ts - .env.example autonomous: true requirements: [AUTH-04] must_haves: truths: - "Logto container starts alongside Postgres in docker-compose" - "Logto admin console is accessible at port 3002" - "Logto OIDC discovery endpoint responds at /oidc/.well-known/openid-configuration" - "GearBox schema no longer contains users or sessions tables" - "A separate logto database is created automatically on Postgres first boot" artifacts: - path: "docker-compose.yml" provides: "Production Logto service definition" contains: "svhd/logto" - path: "docker-compose.dev.yml" provides: "Dev Logto service definition" contains: "svhd/logto" - path: "docker/init-logto-db.sql" provides: "Postgres init script creating logto database" contains: "CREATE DATABASE logto" - path: "src/db/schema.ts" provides: "Schema without users/sessions tables" - path: ".env.example" provides: "Documentation of required OIDC env vars" contains: "OIDC_ISSUER" key_links: - from: "docker-compose.yml" to: "docker/init-logto-db.sql" via: "postgres volume mount to docker-entrypoint-initdb.d" pattern: "init-logto-db.sql:/docker-entrypoint-initdb.d" - from: "docker-compose.yml logto service" to: "docker-compose.yml postgres service" via: "depends_on with service_healthy" pattern: "condition: service_healthy" --- Add Logto as a Docker Compose service and remove the users/sessions tables from the GearBox schema. Purpose: Establishes the infrastructure foundation for OIDC authentication -- Logto must be running before server-side auth code can be integrated. Schema changes remove the old auth tables that will be replaced by Logto-managed identity. Output: Updated docker-compose files with Logto, cleaned schema, env var documentation. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/15-external-authentication/15-CONTEXT.md @.planning/phases/15-external-authentication/15-RESEARCH.md @src/db/schema.ts @docker-compose.yml @docker-compose.dev.yml Task 1: Add Logto service to Docker Compose and create init script docker-compose.yml, docker-compose.dev.yml, docker/init-logto-db.sql, .env.example - docker-compose.yml (current production compose) - docker-compose.dev.yml (current dev compose) - .planning/phases/15-external-authentication/15-RESEARCH.md (Pattern 4: Logto Docker Compose Integration, Pitfall 1: OIDC Issuer URL Mismatch) **Per D-13 and D-14:** Add Logto as a service in both docker-compose files. 1. Create `docker/init-logto-db.sql` with content: ```sql -- Creates a separate database for Logto on the shared Postgres instance CREATE DATABASE logto; ``` 2. Update `docker-compose.yml` (production): - Add volume mount on postgres service: `./docker/init-logto-db.sql:/docker-entrypoint-initdb.d/init-logto-db.sql` - Add `logto` service: ```yaml logto: image: svhd/logto:latest depends_on: postgres: condition: service_healthy entrypoint: ["sh", "-c", "npm run cli db seed -- --swe && npm start"] ports: - "3001:3001" - "3002:3002" environment: TRUST_PROXY_HEADER: "1" DB_URL: postgres://gearbox:${POSTGRES_PASSWORD}@postgres:5432/logto ENDPOINT: ${LOGTO_ENDPOINT:-http://localhost:3001} ADMIN_ENDPOINT: ${LOGTO_ADMIN_ENDPOINT:-http://localhost:3002} ``` - Add to `app` service environment: ```yaml OIDC_ISSUER: ${LOGTO_ENDPOINT:-http://localhost:3001}/oidc OIDC_CLIENT_ID: ${LOGTO_CLIENT_ID} OIDC_CLIENT_SECRET: ${LOGTO_CLIENT_SECRET} OIDC_AUTH_SECRET: ${OIDC_AUTH_SECRET} ``` - Add `depends_on` for app -> logto: `condition: service_started` 3. Update `docker-compose.dev.yml`: - Add the same postgres init volume mount - Add same `logto` service definition (ports 3001, 3002) - Logto environment uses hardcoded dev password: `DB_URL: postgres://gearbox:gearbox@postgres:5432/logto` 4. Create or update `.env.example` with all new OIDC env vars: ``` # PostgreSQL POSTGRES_PASSWORD=changeme # Logto OIDC (get from Logto Admin Console at http://localhost:3002) LOGTO_ENDPOINT=http://localhost:3001 LOGTO_ADMIN_ENDPOINT=http://localhost:3002 LOGTO_CLIENT_ID=your-app-client-id LOGTO_CLIENT_SECRET=your-app-client-secret OIDC_AUTH_SECRET=generate-a-random-32-char-string-here # GearBox GEARBOX_URL=http://localhost:3000 ``` **IMPORTANT (Pitfall 1):** The `ENDPOINT` on Logto and `OIDC_ISSUER` on the app must both use the *externally accessible* URL (e.g., `http://localhost:3001`), NOT Docker-internal hostnames. The browser redirect and server-side JWT validation must agree on the issuer string. grep -q "svhd/logto" docker-compose.yml && grep -q "svhd/logto" docker-compose.dev.yml && grep -q "CREATE DATABASE logto" docker/init-logto-db.sql && grep -q "OIDC_ISSUER" .env.example && echo "PASS" || echo "FAIL" - docker-compose.yml contains `image: svhd/logto:latest` - docker-compose.yml logto service has `depends_on: postgres: condition: service_healthy` - docker-compose.yml logto service exposes ports 3001 and 3002 - docker-compose.yml postgres service has volume mount containing `init-logto-db.sql:/docker-entrypoint-initdb.d/init-logto-db.sql` - docker-compose.yml app service has `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_AUTH_SECRET` env vars - docker-compose.dev.yml contains matching logto service definition - docker/init-logto-db.sql contains `CREATE DATABASE logto;` - .env.example contains `LOGTO_CLIENT_ID`, `LOGTO_CLIENT_SECRET`, `OIDC_AUTH_SECRET`, `LOGTO_ENDPOINT` Both docker-compose files have Logto service, init SQL creates logto database, env vars documented Task 2: Remove users and sessions tables from schema and generate migration src/db/schema.ts - src/db/schema.ts (current full schema with users, sessions, apiKeys, oauth* tables) - .planning/phases/15-external-authentication/15-CONTEXT.md (D-03: Remove users and sessions tables) **Per D-03:** Remove the `users` and `sessions` table definitions from `src/db/schema.ts`. Keep everything else: `categories`, `items`, `threads`, `threadCandidates`, `setups`, `setupItems`, `settings`, `apiKeys`, `oauthClients`, `oauthCodes`, `oauthTokens`. Specifically: 1. Delete the `users` table definition (lines defining `export const users = pgTable("users", { ... })`) 2. Delete the `sessions` table definition (lines defining `export const sessions = pgTable("sessions", { ... })`) 3. Remove the `boolean` import from `drizzle-orm/pg-core` if no longer used (check: `oauthCodes` uses `boolean` for `used` field, so keep it) 4. Do NOT remove `apiKeys` table -- it stays per D-10 After editing schema, run migration generation: ```bash bun run db:generate ``` This creates a Drizzle migration SQL file in `drizzle/` that drops the `users` and `sessions` tables. Review the generated migration to confirm it only drops `users` and `sessions` -- no other tables. **Do NOT run `bun run db:push` yet** -- that will be done when the full auth refactor is ready. ! grep -q "export const users" src/db/schema.ts && ! grep -q "export const sessions" src/db/schema.ts && grep -q "export const apiKeys" src/db/schema.ts && echo "PASS" || echo "FAIL" - src/db/schema.ts does NOT contain `export const users` - src/db/schema.ts does NOT contain `export const sessions` - src/db/schema.ts DOES contain `export const apiKeys` - src/db/schema.ts DOES contain `export const oauthClients` - src/db/schema.ts DOES contain `export const oauthCodes` - src/db/schema.ts DOES contain `export const oauthTokens` - A new migration file exists in drizzle/ directory Users and sessions tables removed from schema, migration generated to drop them - `grep -q "svhd/logto" docker-compose.yml` succeeds - `grep -q "svhd/logto" docker-compose.dev.yml` succeeds - `docker/init-logto-db.sql` exists with CREATE DATABASE logto - `src/db/schema.ts` has no `users` or `sessions` exports - `src/db/schema.ts` retains `apiKeys`, `oauthClients`, `oauthCodes`, `oauthTokens` - New Drizzle migration file exists in `drizzle/` - Logto service defined in both docker-compose files with correct ports, env vars, and Postgres dependency - Postgres init script creates the logto database - GearBox schema has users and sessions tables removed - Drizzle migration generated for the table drops - All OIDC-related environment variables documented in .env.example After completion, create `.planning/phases/15-external-authentication/15-01-SUMMARY.md`