Drop the calendar's legacy authentication path

Step 5, the last one, of docs/calendar-auth-migration.md. Step 4 is deployed
and verified, which is what this was waiting on: it removes the fallbacks that
step 4 still leaned on.

Gone: src/models/calendar/users/ entirely - registration, login, activation,
both password-reset routes, and the session checking that the feedback and
tickets admin areas used to authenticate against - along with its mount. That
was the API's last unauthenticated account-creation and mail-sending endpoint.
A survey confirmed nothing outside that directory imported it and nothing else
touched its tables.

Also gone: the two joins against the calendar users table in events.service.ts
and the created_by_id / version_created_by_id columns they read, from the SQL,
the row mapper, the Event interface and the swagger schema; and X-Session-Id /
X-Session-Key from the CORS allowedHeaders, which nothing has read since the
first cutover and nothing has sent since the second.

An event's author still renders, because migration 002 snapshotted the names
before this could erase them. That was brought forward from this step on
purpose, and it is the reason 004 can rename the accounts aside at all.

The accounts are renamed rather than dropped - they still hold e-mail addresses
and password hashes, and a rename makes them unreachable without destroying
anything. InnoDB rewires the sessions foreign key to the new name; verified on
MariaDB 11, along with the whole 001-004 chain from the pre-cutover production
schema, which lands byte-identical to a fresh dev database.

Migration 004 must be applied AFTER deploying, not before - the reverse of step
4, whose migration only added things. Its own header and the runbook both say
so, since getting it wrong by analogy is the obvious mistake.

DEFERRED_SECURITY.md items 3 and 4 close with it: the activation and reset
tokens that never expired are gone along with the code that issued them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 23:32:59 +02:00
parent 13a0c07d1b
commit aa95ab2745
17 changed files with 253 additions and 1370 deletions
+27 -55
View File
@@ -1,8 +1,13 @@
-- Local dev only. Real schema, provided directly by the repo owner
-- (calendars, events, event_versions, sessions, users) - not a guess.
-- Columns added by this repo's own migrations under sql/calendar/ are folded
-- in here rather than appended, so a fresh dev container matches production
-- after every migration has been applied. Keep the two in step.
-- Local dev only. Derived from the real schema, which was provided directly by
-- the repo owner (calendars, events, event_versions, and the sessions/users
-- tables that step 5 of docs/calendar-auth-migration.md renamed aside).
--
-- There is no users or sessions table here: a fresh dev database has no legacy
-- accounts to archive, so it starts where production ends up.
--
-- Changes made by this repo's own migrations under sql/calendar/ are folded in
-- here rather than appended, so a fresh dev container matches production once
-- every migration has been applied. Keep the two in step.
USE nachklang_calendar;
CREATE TABLE `calendars` (
@@ -12,47 +17,20 @@ CREATE TABLE `calendars` (
PRIMARY KEY (`calendar_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `users` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`full_name` text NOT NULL,
`password_hash` text DEFAULT NULL,
`email` text NOT NULL,
`is_active` tinyint(1) DEFAULT 0,
`pw_reset_token_hash` text DEFAULT NULL,
`activation_token` text DEFAULT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `email` (`email`) USING HASH
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `sessions` (
`session_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`session_key_hash` text DEFAULT NULL,
`created_date` datetime DEFAULT current_timestamp(),
`valid_until` datetime DEFAULT (current_timestamp() + interval 30 day),
`last_ip` text DEFAULT NULL,
PRIMARY KEY (`session_id`),
KEY `sessions_users_user_id_fk` (`user_id`),
CONSTRAINT `sessions_users_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `events` (
`event_id` int(11) NOT NULL AUTO_INCREMENT,
`calendar_id` int(11) NOT NULL,
`uuid` text NOT NULL,
`created_date` datetime DEFAULT current_timestamp(),
-- Nullable since the cutover; see sql/calendar/003_allow_null_legacy_creator.sql.
`created_by_id` int(11) DEFAULT NULL,
-- Bridge to the admin module's user ids; see sql/calendar/001_add_admin_user_bridge.sql.
`created_by_user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
-- Archived creator name; see sql/calendar/002_snapshot_legacy_creator_names.sql.
-- Creator name, archived before the legacy users table went away;
-- see sql/calendar/002_snapshot_legacy_creator_names.sql.
`created_by_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`event_id`),
KEY `events_calendars_calendar_id_fk` (`calendar_id`),
KEY `events_users_user_id_fk` (`created_by_id`),
KEY `events_created_by_user_idx` (`created_by_user_id`),
CONSTRAINT `events_calendars_calendar_id_fk` FOREIGN KEY (`calendar_id`) REFERENCES `calendars` (`calendar_id`),
CONSTRAINT `events_users_user_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `users` (`user_id`)
CONSTRAINT `events_calendars_calendar_id_fk` FOREIGN KEY (`calendar_id`) REFERENCES `calendars` (`calendar_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `event_versions` (
@@ -66,7 +44,6 @@ CREATE TABLE `event_versions` (
`repeat_frequency` text DEFAULT NULL,
`location` text DEFAULT NULL,
`url` text DEFAULT NULL,
`version_created_by_id` int(11) DEFAULT NULL,
-- Bridge to the admin module's user ids; see sql/calendar/001_add_admin_user_bridge.sql.
`version_created_by_user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
-- Archived editor name; see sql/calendar/002_snapshot_legacy_creator_names.sql.
@@ -75,10 +52,8 @@ CREATE TABLE `event_versions` (
`version_created_at` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`event_version_id`),
KEY `event_versions_events_event_id_fk` (`event_id`),
KEY `event_versions_users_user_id_fk` (`version_created_by_id`),
KEY `event_versions_created_by_user_idx` (`version_created_by_user_id`),
CONSTRAINT `event_versions_events_event_id_fk` FOREIGN KEY (`event_id`) REFERENCES `events` (`event_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `event_versions_users_user_id_fk` FOREIGN KEY (`version_created_by_id`) REFERENCES `users` (`user_id`)
CONSTRAINT `event_versions_events_event_id_fk` FOREIGN KEY (`event_id`) REFERENCES `events` (`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO calendars (calendar_id, name, includes_calendars) VALUES
@@ -88,20 +63,17 @@ INSERT INTO calendars (calendar_id, name, includes_calendars) VALUES
(4, 'choir', '[]'),
(5, 'birthdays', '[]');
-- Dev admin, password: devpassword
INSERT INTO users (email, password_hash, full_name, is_active) VALUES
('dev@nachklang.art', '$2b$10$vmj7POS/68SGE.eI7pGjMegrw0vNNZ2HVSUTra5NRsl8iOLwiMgZK', 'Dev Admin', 1);
-- Two events carry only an archived creator name, as every pre-cutover event
-- does, and one carries an admin user id whose name is resolved live. Dev
-- therefore exercises both name sources rather than only one. The one with an
-- id is deliberately a PUBLIC event, so the anonymous listing the website uses
-- covers both. The id is the dev admin from 04-admin-schema.sql.
INSERT INTO events (calendar_id, uuid, created_by_user_id, created_by_name) VALUES
(1, UUID(), NULL, 'Dev Admin'),
(1, UUID(), 'dev-user-0000-0000-0000-000000000001', NULL),
(1, UUID(), NULL, 'Dev Admin');
-- Two rows are left on the legacy path and one carries an admin user id, so
-- dev exercises both branches of the step 3 dual-read rather than only the
-- happy one. It is deliberately a PUBLIC event, so the anonymous listing the
-- website uses covers both. The id is the dev admin from 04-admin-schema.sql.
INSERT INTO events (calendar_id, uuid, created_by_id, created_by_user_id, created_by_name) VALUES
(1, UUID(), 1, NULL, 'Dev Admin'),
(1, UUID(), 1, 'dev-user-0000-0000-0000-000000000001', NULL),
(1, UUID(), 1, NULL, 'Dev Admin');
INSERT INTO event_versions (event_id, name, description, start_datetime, end_datetime, whole_day, location, url, status, version_created_by_id, version_created_by_user_id, version_created_by_name) VALUES
(1, 'Frühlingskonzert 2026', 'Erstes Konzert der Reihe', '2026-04-18 19:00:00', '2026-04-18 21:00:00', 0, 'Musikhochschule, Karlsruhe', 'https://www.nachklang.art/events/fruehlingskonzert-2026', 'PUBLIC', 1, NULL, 'Dev Admin'),
(2, 'Sommerkonzert 2026', 'Zweites Konzert der Reihe', '2026-07-11 19:00:00', '2026-07-11 21:00:00', 0, 'Christuskirche, Karlsruhe', 'https://www.nachklang.art/events/sommerkonzert-2026', 'PUBLIC', 1, 'dev-user-0000-0000-0000-000000000001', NULL),
(3, 'Adventskonzert 2026', 'Drittes Konzert der Reihe', '2026-12-05 19:00:00', '2026-12-05 21:00:00', 0, 'Stadtkirche, Karlsruhe', 'https://www.nachklang.art/events/adventskonzert-2026', 'DRAFT', 1, NULL, 'Dev Admin');
INSERT INTO event_versions (event_id, name, description, start_datetime, end_datetime, whole_day, location, url, status, version_created_by_user_id, version_created_by_name) VALUES
(1, 'Frühlingskonzert 2026', 'Erstes Konzert der Reihe', '2026-04-18 19:00:00', '2026-04-18 21:00:00', 0, 'Musikhochschule, Karlsruhe', 'https://www.nachklang.art/events/fruehlingskonzert-2026', 'PUBLIC', NULL, 'Dev Admin'),
(2, 'Sommerkonzert 2026', 'Zweites Konzert der Reihe', '2026-07-11 19:00:00', '2026-07-11 21:00:00', 0, 'Christuskirche, Karlsruhe', 'https://www.nachklang.art/events/sommerkonzert-2026', 'PUBLIC', 'dev-user-0000-0000-0000-000000000001', NULL),
(3, 'Adventskonzert 2026', 'Drittes Konzert der Reihe', '2026-12-05 19:00:00', '2026-12-05 21:00:00', 0, 'Stadtkirche, Karlsruhe', 'https://www.nachklang.art/events/adventskonzert-2026', 'DRAFT', NULL, 'Dev Admin');