docs(02-02): complete server auth boundary plan
- add execution summary with verification and deviations - update state, roadmap progress, and completed auth requirements
This commit is contained in:
@@ -9,10 +9,10 @@
|
||||
|
||||
- [ ] **AUTH-01**: User can sign in via the self-hosted Authentik instance using OIDC (authorization code flow with PKCE)
|
||||
- [ ] **AUTH-02**: Client stores access + refresh tokens securely (iOS Keychain / Android EncryptedSharedPreferences)
|
||||
- [ ] **AUTH-03**: Ktor server validates incoming access tokens via Authentik's JWKS endpoint (issuer, audience, expiry, signature, clock skew leeway)
|
||||
- [x] **AUTH-03**: Ktor server validates incoming access tokens via Authentik's JWKS endpoint (issuer, audience, expiry, signature, clock skew leeway)
|
||||
- [ ] **AUTH-04**: User session persists across app launches without re-authentication (token refresh handled transparently)
|
||||
- [ ] **AUTH-05**: User can sign out, which revokes local tokens and returns to the login screen
|
||||
- [ ] **AUTH-06**: Users are JIT-provisioned in the server database on first successful login (by OIDC `sub` claim)
|
||||
- [x] **AUTH-06**: Users are JIT-provisioned in the server database on first successful login (by OIDC `sub` claim)
|
||||
|
||||
### Household sharing
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ Plans:
|
||||
|
||||
Plans:
|
||||
- [x] 02-01-PLAN.md — Shared auth contracts, dependency aliases, Authentik setup docs, and source audit
|
||||
- [ ] 02-02-PLAN.md — Server JWT validation, JWKS hardening, JIT users, and `/api/v1/me`
|
||||
- [x] 02-02-PLAN.md — Server JWT validation, JWKS hardening, JIT users, and `/api/v1/me`
|
||||
- [ ] 02-03-PLAN.md — Common OIDC/store contracts, JVM/Wasm actuals, and store contract test
|
||||
- [ ] 02-04-PLAN.md — Android AppAuth actual, Android secure AuthState store, and manifest callback
|
||||
- [ ] 02-05-PLAN.md — iOS AppAuth actual, iOS Keychain store, URL scheme, Swift callback, and Podfile
|
||||
@@ -222,7 +222,7 @@ Plans:
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Project Infrastructure & Module Wiring | 7/7 | Complete | 2026-04-24 |
|
||||
| 2. Authentication Foundation | 0/7 | Planned | - |
|
||||
| 2. Authentication Foundation | 2/7 | Executing | - |
|
||||
| 3. Households, Membership & Server Data Foundation | 0/0 | Not started | - |
|
||||
| 4. Sync Engine Skeleton | 0/0 | Not started | - |
|
||||
| 5. Recipe Catalog (Read Path) | 0/0 | Not started | - |
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
current_plan: 0
|
||||
status: planned
|
||||
last_updated: "2026-04-28T08:30:48.000Z"
|
||||
current_plan: 2
|
||||
status: executing
|
||||
last_updated: "2026-04-28T11:44:38.794Z"
|
||||
progress:
|
||||
total_phases: 11
|
||||
completed_phases: 1
|
||||
total_plans: 14
|
||||
completed_plans: 7
|
||||
percent: 50
|
||||
completed_plans: 9
|
||||
percent: 64
|
||||
---
|
||||
|
||||
# Project State: Recipe
|
||||
@@ -25,13 +25,13 @@ progress:
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 02 — Authentication Foundation — PLANNED
|
||||
Plan: 0 of 7
|
||||
**Current focus:** Phase 2 ready for execution
|
||||
**Current plan:** 0
|
||||
**Status:** Phase 2 planning complete; ready to execute Phase 2
|
||||
**Phase progress:** 1 / 11 phases complete
|
||||
**Progress bar:** `██░░░░░░░░░░░░░░░░░░` 9%
|
||||
Phase: 02 (authentication-foundation) — EXECUTING
|
||||
Plan: 2 of 7
|
||||
**Current focus:** Phase 02 — authentication-foundation
|
||||
**Current plan:** 2
|
||||
**Status:** Ready to execute
|
||||
**Phase progress:** 2 / 7 plans complete
|
||||
**Progress bar:** `[██████░░░░] 64%`
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@@ -41,7 +41,8 @@ Plan: 0 of 7
|
||||
| v1 requirements | 72 |
|
||||
| Coverage | 100% |
|
||||
| Phases complete | 1 |
|
||||
| Plans complete | 7 |
|
||||
| Plans complete | 9 |
|
||||
| Phase 02 P02 | 13min | 3 tasks | 14 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -59,7 +60,7 @@ All locked tech-stack decisions are captured in `.planning/PROJECT.md § Key Dec
|
||||
|
||||
## Session Continuity
|
||||
|
||||
**Last session:** --stopped-at
|
||||
**Last session:** 2026-04-28T11:44:38.789Z
|
||||
|
||||
**Next action:** `/gsd-execute-phase 2` — Authentication Foundation.
|
||||
|
||||
|
||||
169
.planning/phases/02-authentication-foundation/02-02-SUMMARY.md
Normal file
169
.planning/phases/02-authentication-foundation/02-02-SUMMARY.md
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
phase: 02-authentication-foundation
|
||||
plan: 02
|
||||
subsystem: auth
|
||||
tags: [ktor, jwt, authentik, jwks, postgres, flyway, exposed, testcontainers]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 02-01
|
||||
provides: shared auth DTOs, dependency aliases, and Authentik setup context
|
||||
provides:
|
||||
- Authentik-style JWT validation with issuer, audience, expiry, RS256 signature, JWKS caching, and non-empty sub enforcement
|
||||
- Flyway users table migration keyed by OIDC sub
|
||||
- Exposed DSL JIT user upsert and protected GET /api/v1/me route
|
||||
- Server auth integration tests for JWT rejection and user provisioning
|
||||
affects: [phase-03-households, server-auth, principal-resolution, api-v1]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: [ktor-server-auth-jwt, jwks-rsa, hikari, testcontainers-postgresql]
|
||||
patterns: [Ktor jwt("authentik") provider, cached/rate-limited JWKS provider, newSuspendedTransaction for route DB work, Postgres ON CONFLICT upsert]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- server/src/main/resources/db/migration/V1__users.sql
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/auth/AuthConfig.kt
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/auth/AuthPlugin.kt
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/auth/UsersTable.kt
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/auth/PrincipalResolver.kt
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/auth/MeRoute.kt
|
||||
- server/src/test/kotlin/dev/ulfrx/recipe/auth/JwtTestSupport.kt
|
||||
- server/src/test/kotlin/dev/ulfrx/recipe/auth/AuthJwtTest.kt
|
||||
- server/src/test/kotlin/dev/ulfrx/recipe/auth/MeRouteTest.kt
|
||||
modified:
|
||||
- server/src/main/resources/application.conf
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/Database.kt
|
||||
- server/src/main/kotlin/dev/ulfrx/recipe/Application.kt
|
||||
- server/src/test/kotlin/dev/ulfrx/recipe/ApplicationTest.kt
|
||||
|
||||
key-decisions:
|
||||
- "Pinned Exposed runtime is 0.55.0; the suspend transaction import used is org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction."
|
||||
- "PrincipalResolver uses Postgres INSERT ... ON CONFLICT ... RETURNING via Exposed exec because the resolver must atomically upsert and return the generated user id."
|
||||
- "CallLogging uses a custom method/path/status format and omits all headers because Ktor 3.4.1 server CallLogging has no redactHeader API."
|
||||
|
||||
patterns-established:
|
||||
- "Protected server routes sit inside authenticate(\"authentik\") and resolve JWTPrincipal through PrincipalResolver before returning user data."
|
||||
- "Server-side user identity is derived only from JWT claims, never request bodies."
|
||||
- "Server auth tests use in-process RSA/JWKS support for JWT verifier coverage and Testcontainers Postgres for JIT provisioning coverage."
|
||||
|
||||
requirements-completed: [AUTH-03, AUTH-06]
|
||||
|
||||
# Metrics
|
||||
duration: 13min
|
||||
completed: 2026-04-28
|
||||
---
|
||||
|
||||
# Phase 02 Plan 02: Server JWT Validation and JIT Users Summary
|
||||
|
||||
**Ktor Authentik JWT validation with cached JWKS, atomic Postgres user provisioning by OIDC sub, and protected `/api/v1/me`.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 13 min for final executor verification and summary; task commits already existed on this branch when this executor resumed.
|
||||
- **Started:** 2026-04-28T11:18:15Z
|
||||
- **Completed:** 2026-04-28T11:31:08Z
|
||||
- **Tasks:** 3 completed
|
||||
- **Files modified:** 13 code/config/test files plus this summary
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Added JWT validation coverage for missing, expired, wrong-issuer, wrong-audience, blank-sub, and valid RS256 tokens.
|
||||
- Installed Ktor `jwt("authentik")` with issuer/audience checks, 30-second max leeway, non-empty `sub`, cached JWKS, and rate limiting.
|
||||
- Added `users` Flyway migration, Exposed table mapping, Hikari-backed Exposed connection, atomic JIT upsert by `sub`, and protected `/api/v1/me`.
|
||||
- Added Testcontainers Postgres integration coverage proving first request creates a user row and later requests update mutable claims without duplication.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Create JWT validation tests before auth implementation** - `614b57c` (`test`)
|
||||
2. **Task 2: Implement AuthConfig, JWT plugin, logging redaction, and verify Exposed suspend API** - `36c1b2c` (`feat`)
|
||||
3. **Task 3: Add users migration, Exposed resolver, /api/v1/me route, and JIT tests** - `8cf112a` (`feat`)
|
||||
|
||||
No tracked file deletions were present in the task commits.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `server/src/main/resources/application.conf` - Adds OIDC issuer/audience/JWKS/leeway config with env overrides.
|
||||
- `server/src/main/resources/db/migration/V1__users.sql` - Creates the `users` table and `users_sub_idx`.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/Database.kt` - Adds Hikari-backed Exposed connection after Flyway migration.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/Application.kt` - Installs safe CallLogging, authentication, DB migration/connection, and auth route wiring.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/auth/AuthConfig.kt` - Reads server OIDC config from HOCON.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/auth/AuthPlugin.kt` - Installs the Authentik JWT verifier.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/auth/UsersTable.kt` - Exposed DSL mapping for `users`.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/auth/PrincipalResolver.kt` - Resolves `JWTPrincipal` to `MeResponse` through atomic upsert.
|
||||
- `server/src/main/kotlin/dev/ulfrx/recipe/auth/MeRoute.kt` - Provides protected `GET /api/v1/me`.
|
||||
- `server/src/test/kotlin/dev/ulfrx/recipe/auth/JwtTestSupport.kt` - Generates RSA keys, JWKS provider, and configurable RS256 JWTs for tests.
|
||||
- `server/src/test/kotlin/dev/ulfrx/recipe/auth/AuthJwtTest.kt` - Covers JWT validation positive and negative cases.
|
||||
- `server/src/test/kotlin/dev/ulfrx/recipe/auth/MeRouteTest.kt` - Covers JIT provisioning against Testcontainers Postgres.
|
||||
- `server/src/test/kotlin/dev/ulfrx/recipe/ApplicationTest.kt` - Keeps `/health` test wiring compatible with authenticated route registration.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Used `newSuspendedTransaction` from `org.jetbrains.exposed.sql.transactions.experimental` after confirming `org.jetbrains.exposed:exposed-jdbc:0.55.0`.
|
||||
- Used raw SQL through Exposed `exec` for `INSERT ... ON CONFLICT ... RETURNING`, because the resolver needs the returned row and generated UUID in one atomic operation.
|
||||
- Kept logging to method, path, and status only; no header logging or bearer-token redaction API is used.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] Kept `/health` test route registration compatible with authenticated routes**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** Once `configureRouting()` registered `meRoute`, tests that installed routing without Authentication would fail route setup.
|
||||
- **Fix:** Updated `ApplicationTest` to install the test JWT authentication plugin before calling `configureRouting()`.
|
||||
- **Files modified:** `server/src/test/kotlin/dev/ulfrx/recipe/ApplicationTest.kt`
|
||||
- **Verification:** `./gradlew :server:test`
|
||||
- **Committed in:** `8cf112a`
|
||||
|
||||
**2. [Rule 3 - Blocking] Used Exposed `StatementType.SELECT` for Postgres upsert returning rows**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** `INSERT ... RETURNING` must be executed as a result-producing statement; otherwise Postgres reports that a result was returned when none was expected.
|
||||
- **Fix:** Added `explicitStatementType = StatementType.SELECT` to the Exposed `exec` call.
|
||||
- **Files modified:** `server/src/main/kotlin/dev/ulfrx/recipe/auth/PrincipalResolver.kt`
|
||||
- **Verification:** `./gradlew :server:test --tests "*MeRouteTest*" --tests "*AuthJwtTest*"`
|
||||
- **Committed in:** `8cf112a`
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 2 auto-fixed (1 bug, 1 blocking issue)
|
||||
**Impact on plan:** Both fixes were required for the planned tests and route behavior. No extra feature scope was added.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- Testcontainers Postgres made the first filtered test run take several minutes while the container image/runtime initialized. Subsequent server test runs completed from cache.
|
||||
|
||||
## Authentication Gates
|
||||
|
||||
None.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None for this plan. Real Authentik provider setup remains covered by the Phase 2 setup documentation from plan `02-01`.
|
||||
|
||||
## Verification
|
||||
|
||||
- `./gradlew :server:dependencyInsight --dependency exposed-jdbc --configuration runtimeClasspath` - passed; Exposed JDBC version is `0.55.0`.
|
||||
- `./gradlew :server:test --tests "*AuthJwtTest*" --tests "*MeRouteTest*"` - passed.
|
||||
- `./gradlew :server:test` - passed.
|
||||
- Task acceptance greps for OIDC config, JWT verifier settings, logging safety, migration shape, no DAO imports, no blocking `transaction {}` in auth code, `/api/v1/me`, Testcontainers, `postgres:16`, and Flyway all passed.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
Phase 3 can extend `PrincipalResolver` from user identity to household-scoped principal resolution. The server now has the stable `users.sub` anchor and `/api/v1/me` boundary that Phase 3 onboarding and household membership can build on.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- Created/modified key files exist.
|
||||
- Task commits found: `614b57c`, `36c1b2c`, `8cf112a`.
|
||||
- Required verification commands passed.
|
||||
- No unplanned tracked file deletions were detected in task commits.
|
||||
|
||||
---
|
||||
*Phase: 02-authentication-foundation*
|
||||
*Completed: 2026-04-28*
|
||||
Reference in New Issue
Block a user