Files
hammy-backend/migrations/001_types.sql
T
2026-09-05 19:57:41 +02:00

103 lines
4.4 KiB
SQL

-- Hammy backend bootstrap. Run ONCE, as a superuser, before any migration:
--
-- psql -U postgres -f scripts/bootstrap.sql
--
-- Deliberately NOT in migrations/. Three reasons:
--
-- * CREATE DATABASE cannot run inside a transaction block, and migration
-- tools wrap each file in one.
-- * \c is a psql meta-command, not SQL. A migration tool has no idea what it
-- means.
-- * Roles are cluster-wide, not per-database, so they are not part of any one
-- database's schema history.
--
-- Order matters: the schemas and ALTER DEFAULT PRIVILEGES must exist BEFORE any
-- table is created, because default privileges only apply to objects created
-- afterwards. Get this right here and no migration ever needs a GRANT.
-- ---------------------------------------------------------------------------
-- Roles (cluster-wide)
-- ---------------------------------------------------------------------------
-- Owns every object and runs migrations. Never connects at runtime.
CREATE ROLE hammy_owner NOLOGIN;
-- What the backend connects as. DML only: it can read and write rows but not
-- create, alter or drop anything, so a compromised backend cannot drop the
-- logbook.
CREATE ROLE hammy_app LOGIN PASSWORD 'CHANGE_ME';
-- Bulk loaders for FCC ULS, 44net allocations and the like.
CREATE ROLE hammy_ingest LOGIN PASSWORD 'CHANGE_ME';
-- Analytics and debugging. Deliberately no access to the api schema - a
-- reporting query has no business reading key material.
CREATE ROLE hammy_readonly LOGIN PASSWORD 'CHANGE_ME';
-- ---------------------------------------------------------------------------
-- Database
-- ---------------------------------------------------------------------------
CREATE DATABASE hammy OWNER hammy_owner ENCODING 'UTF8';
\c hammy
-- ---------------------------------------------------------------------------
-- Schemas
-- ---------------------------------------------------------------------------
-- Before PostgreSQL 15, PUBLIC held CREATE on the public schema, so any role
-- could add objects to it. Revoke it and do not use public for anything.
REVOKE ALL ON SCHEMA public FROM PUBLIC;
CREATE SCHEMA core AUTHORIZATION hammy_owner;
CREATE SCHEMA api AUTHORIZATION hammy_owner;
CREATE SCHEMA logbook AUTHORIZATION hammy_owner;
CREATE SCHEMA ingest AUTHORIZATION hammy_owner;
COMMENT ON SCHEMA core IS 'Discord-linked user identity and callsign verification.';
COMMENT ON SCHEMA api IS 'API tenancy: owners, keys, usage. Most sensitive schema.';
COMMENT ON SCHEMA logbook IS 'Per-user QSO logs and import batches.';
COMMENT ON SCHEMA ingest IS 'Bulk-loaded third-party reference data.';
GRANT USAGE ON SCHEMA core, api, logbook, ingest TO hammy_app;
GRANT USAGE ON SCHEMA ingest TO hammy_ingest;
GRANT USAGE ON SCHEMA core, logbook TO hammy_readonly;
-- ---------------------------------------------------------------------------
-- Default privileges
--
-- Apply to objects hammy_owner creates FROM NOW ON. Every migration runs as
-- hammy_owner, so tables pick these up automatically.
-- ---------------------------------------------------------------------------
ALTER DEFAULT PRIVILEGES FOR ROLE hammy_owner IN SCHEMA core, api, logbook
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hammy_app;
ALTER DEFAULT PRIVILEGES FOR ROLE hammy_owner IN SCHEMA core, api, logbook
GRANT USAGE ON SEQUENCES TO hammy_app;
ALTER DEFAULT PRIVILEGES FOR ROLE hammy_owner IN SCHEMA ingest
GRANT SELECT ON TABLES TO hammy_app;
ALTER DEFAULT PRIVILEGES FOR ROLE hammy_owner IN SCHEMA ingest
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hammy_ingest;
ALTER DEFAULT PRIVILEGES FOR ROLE hammy_owner IN SCHEMA core, logbook
GRANT SELECT ON TABLES TO hammy_readonly;
-- Postgres grants EXECUTE on new functions to PUBLIC by default, which would
-- make forget_user() callable by anyone who can connect. Revoke it; 005 grants
-- it back to hammy_app explicitly.
ALTER DEFAULT PRIVILEGES FOR ROLE hammy_owner IN SCHEMA core, api
REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
-- ---------------------------------------------------------------------------
-- search_path per role, so queries need not schema-qualify everything.
-- ---------------------------------------------------------------------------
ALTER ROLE hammy_owner SET search_path = core, api, logbook, ingest;
ALTER ROLE hammy_app SET search_path = core, api, logbook, ingest;
ALTER ROLE hammy_ingest SET search_path = ingest;
ALTER ROLE hammy_readonly SET search_path = core, logbook;