From 8eb5f0a3edbafda4a9d5a8c104a9f5770f70479c Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Mon, 17 Aug 2026 16:10:36 +0200 Subject: [PATCH] docs: record phase 02 completion --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 4362861..f2cf9cc 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,24 @@ Production Docker Compose (`compose.yml`) publishes **exactly one** host port, o - `GET /api/v1/version` — safe build metadata only (`appVersion`, `teamCityBuildNumber`, `sourceRevision`); never database/Redis URLs. - The compiled no-op migration entry point (`backend/dist/apps/api/src/migration.js`) gives `deploy.sh` a stable container command contract; Phase 02 replaces its body with the real versioned migration runner. - Verified end-to-end: `docker compose -f compose.yml up` with a real TLS certificate serves `/health/live`, `/health/ready`, `/`, and `/api/v1/version` through the single published edge port, with `postgres`/`redis`/`api`/`worker` unreachable from the host. + +## Phase 02 status: OIDC auth, users, and trip core complete + +- Authentication: OIDC Authorization Code + PKCE against an external IdP (`oidc-client-ts` on the frontend, `jose`-based bearer-token verification on the backend). No local password storage; users are keyed by the OIDC `sub` claim and just-in-time provisioned on first login. +- New required backend env vars: `OIDC_ISSUER`, `OIDC_AUDIENCE` (validated fail-fast like `DATABASE_URL`/`REDIS_URL`). New frontend build-time values: `OIDC_ISSUER`, `OIDC_CLIENT_ID` (baked into the production bundle by `docker/edge.Dockerfile`, never read from the container at runtime). +- Real, versioned database migrations (`node-pg-migrate`, files under `backend/migrations/`) replace the Phase 01 no-op `migration.ts` body; the container command contract (`node backend/dist/apps/api/src/migration.js`) is unchanged. Database access goes through `kysely` (a type-safe query builder, not an ORM) over the existing `pg.Pool`; there is no schema auto-sync anywhere. +- New routes: `GET/PUT /api/v1/users/me`(`/preferences`), `GET/POST /api/v1/trips`, `GET/PATCH/DELETE /api/v1/trips/:tripId`, `GET/PUT /api/v1/trips/:tripId/settings`, `GET/PATCH/DELETE /api/v1/trips/:tripId/members(/:memberId)`, `POST/GET/DELETE /api/v1/trips/:tripId/invitations(/:invitationId)`, `POST /api/v1/invitations/:token/accept`, `GET/POST/PATCH/DELETE /api/v1/trips/:tripId/travelers(/:travelerId)`, `GET/PUT/DELETE /api/v1/trips/:tripId/preference-overrides(/:overrideId)`. +- Authorization is enforced backend-side by `OidcAuthGuard` (who) and `TripMembershipGuard` (trip access + `@TripRoles('OWNER')`), never only in the frontend. `TripMember` and `Traveler` are independent tables — a `Traveler` never implies or requires trip membership. +- `Trip.version` optimistic locking: a stale `PATCH` (mismatched `version`) returns HTTP 409 and never silently overwrites; proven by both a mocked unit test and a real-database integration test. +- Run backend integration tests (migration idempotency + optimistic-locking conflict) against `compose.dev.yml`: + + ```bash + pnpm dev:infra + DATABASE_URL=postgresql://travel_planner:travel_planner_dev@localhost:5432/travel_planner \ + REDIS_URL=redis://localhost:6379 \ + OIDC_ISSUER=https://idp.example.invalid/realms/travel-planner \ + OIDC_AUDIENCE=travel-planner-api \ + pnpm --filter backend test:integration + ``` + +- Verified end-to-end against the real running API and a mocked IdP (local JWKS + discovery document): missing token → 401, valid token → `/users/me` succeeds and JIT-provisions the user, trip create/read, first `PATCH` with the correct version succeeds, a second `PATCH` reusing the stale version → 409, and a user with no `trip_members` row for the trip → 403.