From b4c8c9179512b06d01c4d708b8f7e2183b6f98e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Sun, 6 Sep 2026 23:01:44 +0200 Subject: [PATCH] Scope step 5 of the calendar migration, and gate it on step 4 being live Step 5 removes the legacy path, so it removes the fallback step 4 still leans on: the join that renders the author of every pre-cutover event, and the routes an old cached bundle talks to. Building it before step 4 has been deployed and watched turns a recoverable deploy into an unrecoverable one, so this records the shape rather than implementing it. Two decisions worth having in the runbook rather than in someone's memory. The legacy calendar user module gets deleted outright rather than unmounted - a survey confirmed nothing outside that directory imports it, and it is the API's last unauthenticated account-creation and mail-sending endpoint. The users and sessions tables get renamed aside rather than dropped: the display names are already snapshotted so nothing visible depends on those rows, but they still hold e-mail addresses and password hashes, and a rename makes them unreachable without destroying anything. Also notes the two consequences worth accepting deliberately: activation links already in inboxes become 404s, and createdById leaves the wire format. Co-Authored-By: Claude Opus 5 --- docs/calendar-auth-migration.md | 67 ++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/docs/calendar-auth-migration.md b/docs/calendar-auth-migration.md index 6da4cdd..080e2ce 100644 --- a/docs/calendar-auth-migration.md +++ b/docs/calendar-auth-migration.md @@ -6,7 +6,8 @@ left to write. > Read the deploy checklist under step 4 before applying anything. "Done" below means the > code exists on a branch, **not** that production has it - and in particular production has -> none of the three migrations. +> none of the three migrations. Step 5 is scoped but deliberately unstarted: it must not be +> built on top of a step 4 that has not been deployed and watched. Written 2026-09-05 alongside the admin module (step 2 of `docs/plan-admin-auth.md` in the nachklang-admin repo), which deliberately left the calendar alone. Steps 1-4 of that plan @@ -156,14 +157,62 @@ Each step is meant to leave production working on its own. **One-way door:** any iCal subscription whose URL carries `?sessionId=&sessionKey=` rather than `?password=` stops working permanently. The shared-password URLs are unaffected. -5. **Drop the legacy path.** Remove `users.service.ts`'s session handling, the `sessions` - table, `created_by_id`, and the legacy half of the step 3 read (the `users` join and its - `legacy_*` aliases - the snapshot fallback stays, it is what makes dropping the table - safe). The names were archived ahead of time by - `sql/calendar/002_snapshot_legacy_creator_names.sql`, so nothing is lost here. Legacy `/calendar/users/*` stays - only if something still calls it - otherwise delete it too. `X-Session-Id` / - `X-Session-Key` can then come out of the CORS `allowedHeaders` list in - `src/app.factory.ts`. +5. **Drop the legacy path.** Not started - and deliberately not started until step 4 has been + deployed and watched, because it removes the fallback step 4 still leans on. Scoped and + decided 2026-09-06; what follows is the agreed shape, not a suggestion. + + **Prerequisite: step 4 live in production and behaving.** Until then the legacy join is + what renders the author of every pre-cutover event, and the legacy routes are what an old + cached bundle talks to. Doing this first turns a recoverable deploy into an unrecoverable + one. + + Code, in one branch: + + - **Delete `src/models/calendar/users/` entirely** - `users.router.ts`, `users.service.ts`, + `session.interface.ts`, `user.interface.ts` - and the `calendarRouter.use('/users', ...)` + line in `Calendar.router.ts`. *(Decided: delete outright rather than unmount.)* This + removes the last unauthenticated account-creation and mail-sending endpoint in the API. + A survey on 2026-09-06 confirmed nothing outside that directory imports it, and nothing + outside it touches the `users`/`sessions` tables except the two joins below. + - **Drop the legacy half of the read** in `events.service.ts`: the two + `LEFT OUTER JOIN users` clauses, the `legacy_*` aliases, and `created_by_id` / + `version_created_by_id` from the SELECT and the row mapper. The snapshot fallback stays - + it is what makes this safe. Remove `createdById` / `lastModifiedById` from + `event.interface.ts` and their (already deprecated) swagger properties. + - **Remove `X-Session-Id` / `X-Session-Key`** from the CORS `allowedHeaders` in + `src/app.factory.ts`. Nothing has sent them since the tickets and feedback frontends were + redeployed. + - **Drop the obsolete test mocks**: `test/feedback/feedback.auth.test.ts`, + `test/tickets/tickets.auth.test.ts` and `test/admin/auth-binding.ts` each mock + `calendar/users/users.service.js` and assert `checkSession` is never called. That + tripwire is meaningless once the module does not exist; remove the mock and the + assertion, keep the rest. + + Database, as `sql/calendar/004_*.sql`: + + - Drop the foreign keys `events_users_user_id_fk` and `event_versions_users_user_id_fk`, + then the `created_by_id` and `version_created_by_id` columns. + - **`RENAME TABLE users TO users_legacy_archive`**, same for `sessions`. *(Decided: rename + rather than drop.)* The reasoning: the display names are already snapshotted so nothing + visible depends on these rows, but they still hold the old e-mail addresses and password + hashes, and a rename makes the tables unreachable without destroying anything. Dropping + them later is one statement, at a moment when nobody is mid-deploy. + - Mirror all of it in `docker/init/01-calendar-schema-dev.sql` (the archive tables need no + mirror - a fresh dev database has nothing to archive). + + Documentation: `DEFERRED_SECURITY.md` items **3** (activation token has no expiry) and + **4** (password reset token has no expiry) close outright - both describe code that ceases + to exist. Item 2 (no event ownership check) stays open. + + Two consequences to accept explicitly rather than discover: + + - Any activation or password-reset e-mail already sent points at + `api.nachklang.art/calendar/users/activate` and becomes a 404. Those links were only ever + valid for legacy accounts, which no longer open anything. + - `Event.createdById` disappears from the API response. The Angular frontend never read it + (its `Event` model has only `createdBy`, the name), so this is not a breaking change for + the only known consumer - but it is a wire-format removal, so check anything else that + reads `/calendar/events/*/json` first. ## What the code actually looks like (surveyed 2026-09-06)