Files
FireAdministration/docker/postgres/10-init-app-role.sh
Florian WildeandClaude Opus 5 845942f36c Phase 0: Fundament der Feuerwehr-Verwaltungsplattform
Legt den kompletten Unterbau an, auf dem die Fachmodule aufsetzen.

Betrieb:
- docker-compose.yml mit Traefik, App, Worker, PostgreSQL 17 + PostGIS,
  Redis, MinIO und Gotenberg; Entwicklungs-Override ohne Traefik, dafuer
  mit Mailpit und Adminer
- Mehrstufiges Dockerfile: eigener Benutzer, tini als PID 1, Healthcheck
- .env.example mit einem Kommentar zu jeder Variable

Mandantentrennung:
- Row Level Security auf allen mandantenpflichtigen Tabellen
- Zwei getrennte Datenbankverbindungen: florian_app mit aktiver RLS fuer
  die Anwendung, Eigentuemer-Zugang nur fuer Migration, Seed und Anmeldung
- fuerOrganisation() als einziger Weg zu Fachdaten

Nachvollziehbarkeit:
- AuditLog append-only per Trigger; UPDATE ist dauerhaft gesperrt, DELETE
  nur im gekennzeichneten Aufbewahrungslauf
- Geheimnisse werden vor dem Schreiben entfernt

Anmeldung:
- OIDC gegen ein extern betriebenes authentik, Rollen aus Gruppen nur als
  Vorbelegung beim ersten Login
- Lokaler E-Mail/Passwort-Login als Rueckfallweg
- Terminal per PIN und RFID mit zwei Schranken (Geraet, dann Person)

Weiteres:
- lib/authz.ts mit 9 Rollen und 38 Einzelrechten
- lib/storage.ts (MinIO), lib/audit.ts, lib/warteschlange.ts (BullMQ)
- Integrationen fuer Mail, Telegram und LLM hinter abschaltbaren Interfaces
- tRPC v11, Basis-Layout mit Seitenleiste und dunkler Darstellung
- Seed mit einer Beispielwehr: 16 Personen, 3 Fahrzeuge, 40 Geraete
- 32 Vitest-Tests (davon 8 gegen eine echte Datenbank), 10 Playwright-Tests
- README, Anleitung mit Screenshots, Roadmap, Aufwandsschaetzung, Devlog,
  Versionierungsregeln und Changelog

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 00:13:53 +02:00

80 lines
3.8 KiB
Bash

#!/bin/bash
# =============================================================================
# Läuft EINMALIG beim allerersten Start des Postgres-Containers (leeres
# Datenverzeichnis). Richtet ein:
# - die benötigten Erweiterungen (PostGIS, pgcrypto, citext, unaccent, pg_trgm)
# - den Anwendungsbenutzer "florian_app" OHNE BYPASSRLS
# - Vorgabeprivilegien, damit künftige Tabellen automatisch nutzbar sind
#
# Wichtig zum Verständnis der Mandantentrennung:
# Der Eigentümer der Tabellen (POSTGRES_USER) umgeht Row Level Security —
# das ist gewollt, damit Migrationen und das Seed-Skript arbeiten können.
# Die Anwendung verbindet sich ausschließlich als "florian_app"; für den
# greift RLS, und ohne gesetztes app.current_organisation sieht er nichts.
# =============================================================================
set -euo pipefail
APP_ROLE="florian_app"
APP_PASSWORD="${POSTGRES_APP_PASSWORD:?POSTGRES_APP_PASSWORD muss gesetzt sein}"
# Einfache Anführungszeichen im Passwort für SQL verdoppeln.
APP_PASSWORD_SQL="${APP_PASSWORD//\'/\'\'}"
echo "[florian-init] Richte Erweiterungen und Anwendungsrolle ein ..."
# Erweiterungen. Reines SQL, keine Variablen nötig.
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-'EOSQL'
CREATE EXTENSION IF NOT EXISTS postgis; -- Koordinaten, Einsatzorte
CREATE EXTENSION IF NOT EXISTS pgcrypto; -- gen_random_uuid(), digest()
CREATE EXTENSION IF NOT EXISTS citext; -- E-Mail ohne Gross/Klein
CREATE EXTENSION IF NOT EXISTS unaccent; -- Suche ueber Umlaute hinweg
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- unscharfe Namenssuche
EOSQL
# Anwendungsrolle anlegen. Bewusst OHNE DO-Block: psql ersetzt seine Variablen
# nicht innerhalb dollar-quotierter Zeichenketten. Stattdessen erzeugt die
# Abfrage den CREATE-Befehl als Text, den \gexec dann ausführt — und zwar nur,
# wenn die Rolle noch nicht existiert.
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
SELECT format(
'CREATE ROLE %I LOGIN PASSWORD %L NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS',
'${APP_ROLE}', '${APP_PASSWORD_SQL}'
)
WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${APP_ROLE}')
\gexec
ALTER ROLE "${APP_ROLE}" PASSWORD '${APP_PASSWORD_SQL}';
ALTER ROLE "${APP_ROLE}" SET timezone TO 'Europe/Berlin';
EOSQL
# Rechtevergabe. Hier funktionieren psql-Variablen, weil kein DO-Block im Spiel
# ist.
psql -v ON_ERROR_STOP=1 \
--username "$POSTGRES_USER" \
--dbname "$POSTGRES_DB" \
-v app_role="$APP_ROLE" \
-v datenbank="$POSTGRES_DB" \
-v owner="$POSTGRES_USER" <<-'EOSQL'
GRANT CONNECT ON DATABASE :"datenbank" TO :"app_role";
GRANT USAGE ON SCHEMA public TO :"app_role";
-- Rechte auf alles, was heute schon existiert (bei Erstinstallation leer).
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO :"app_role";
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :"app_role";
-- Und auf alles, was der Eigentuemer kuenftig anlegt (Prisma-Migrationen).
ALTER DEFAULT PRIVILEGES FOR ROLE :"owner" IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO :"app_role";
ALTER DEFAULT PRIVILEGES FOR ROLE :"owner" IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO :"app_role";
ALTER DEFAULT PRIVILEGES FOR ROLE :"owner" IN SCHEMA public
GRANT EXECUTE ON FUNCTIONS TO :"app_role";
-- Die Anwendung darf keine Tabellen anlegen oder loeschen.
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
REVOKE CREATE ON SCHEMA public FROM :"app_role";
ALTER DATABASE :"datenbank" SET timezone TO 'Europe/Berlin';
EOSQL
echo "[florian-init] Anwendungsrolle '$APP_ROLE' eingerichtet (RLS wirksam)."