b848d6eab9
Step 4 of docs/calendar-auth-migration.md, and the close of
DEFERRED_SECURITY.md item 1: no calendar route reads sessionId/sessionKey from
the query string any more, so a live credential no longer travels through
access logs, browser history and Referer headers.
The four write routes sit behind requireAppAccess('calendar'), which also
narrows who may edit from "any activated @nachklang.art account" to an
explicit per-user permission. They answer 401 signed out and 403 without the
permission, where they previously answered 403 for both.
The three read routes cannot use the middleware: one URL serves an anonymous
visitor, an iCal subscription holding a shared password, and a signed-in
editor who should see drafts. They resolve the session optionally instead, and
a signed-in user without the calendar permission is treated as anonymous
rather than refused - so they keep the public calendar access anyone has.
That public calendar staying anonymous is load-bearing: nachklang.art reads it
to show the next upcoming event. It is now pinned at both the password-table
and the route level, and so is the rule that a shared password can never be
used to write.
credentials.service.ts loses its session half and becomes the password table
it always wanted to be. The shared passwords survive only for iCal clients,
which cannot send a cookie.
Writes record the author as an admin user id and no longer have a legacy int
to write, which is what migration 003 makes room for.
/calendar/users/* is left in place: nothing calls it and a session it mints
opens nothing, but they are still live password-accepting endpoints, so
removing them belongs with the rest of the legacy path in step 5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
5.9 KiB
SQL
108 lines
5.9 KiB
SQL
-- 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.
|
|
USE nachklang_calendar;
|
|
|
|
CREATE TABLE `calendars` (
|
|
`calendar_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`name` text NOT NULL,
|
|
`includes_calendars` text DEFAULT NULL,
|
|
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.
|
|
`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`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
CREATE TABLE `event_versions` (
|
|
`event_version_id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`event_id` int(11) NOT NULL,
|
|
`name` text DEFAULT NULL,
|
|
`description` text DEFAULT NULL,
|
|
`start_datetime` datetime DEFAULT NULL,
|
|
`end_datetime` datetime DEFAULT NULL,
|
|
`whole_day` tinyint(1) DEFAULT NULL,
|
|
`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.
|
|
`version_created_by_name` varchar(255) DEFAULT NULL,
|
|
`status` text DEFAULT NULL,
|
|
`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`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
|
|
INSERT INTO calendars (calendar_id, name, includes_calendars) VALUES
|
|
(1, 'public', '[]'),
|
|
(2, 'members', '[]'),
|
|
(3, 'management', '[]'),
|
|
(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 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');
|