diff --git a/.gitignore b/.gitignore index a8e7f60..dd1cb1b 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ Reglement_*/ # Stack env (contains secrets) stack.env + +# Private rechtliche Nachweise (NICHT committen: z.B. FKS-Freigabe-E-Mail) +private-legal-evidence/ diff --git a/docs/legal/backup-and-restore.md b/docs/legal/backup-and-restore.md new file mode 100644 index 0000000..dc0f0b7 --- /dev/null +++ b/docs/legal/backup-and-restore.md @@ -0,0 +1,37 @@ +# Backup & Restore — Übersicht + +Stand: 2026-07-24. Die ausführliche Betriebsanleitung steht in [`docs/BACKUP.md`](../BACKUP.md). +Dieses Dokument fasst den datenschutz-/compliance-relevanten Stand zusammen. + +## Was wird gesichert +- **Datenbank** (PostgreSQL): Konten, Organisationen, Pläne, Journale, Zustimmungen usw. +- **Hochgeladene Dateien** (MinIO): Pläne, Logos, Symbole. +- **Nicht** im Backup: Zahlungsdaten (bei Stripe), E-Mails (beim SMTP-Anbieter), Secrets. + +## Zwei Mechanismen +1. **Lokaler Container-Backup** (`backup`-Service): täglich, im Volume `backups_data`, 30 Tage. + Schützt vor Bedienfehlern/Korruption, **nicht** vor Geräteausfall (gleiches Gerät). +2. **GUI-Backup, verschlüsselt & extern** (`Administration → Backup`): SFTP oder Nextcloud/WebDAV, + AES-256-GCM mit Passphrase, Zeitplan + Aufbewahrung. **Off-site** → deckt Geräteausfall ab. + +| Aspekt | Zustand | +|---|---| +| Frequenz | täglich (konfigurierbar) | +| Verschlüsselung (extern) | ✅ AES-256-GCM (Passphrase) | +| Verschlüsselung (lokal) | ⚠️ nein (nur lokales Sicherheitsnetz) | +| Aufbewahrung | 30 Tage (konfigurierbar) | +| Zugriffsschutz | Ziel-Zugangsdaten verschlüsselt in DB; Passphrase separat aufbewahren | +| Off-site | ✅ via GUI-Backup (sofern konfiguriert) | + +## Restore-Testplan (verbindlich) +Ein Backup gilt erst als funktionierend, wenn ein Restore **nachweislich** geklappt hat. + +1. Aktuelles verschlüsseltes Backup vom Ziel laden. +2. Mit `scripts/decrypt-backup.js` + Passphrase entschlüsseln → `tar.gz`. +3. In einer **Testumgebung** DB + Dateien wiederherstellen (siehe `docs/BACKUP.md`). +4. Stichprobe: Login, ein Projekt, ein Journal, ein hochgeladenes Symbol prüfen. +5. Ergebnis in der Tabelle in `docs/BACKUP.md` protokollieren. +6. **Häufigkeit:** mindestens halbjährlich. + +> Es wird **nicht** behauptet, dass eine Wiederherstellung funktioniert, solange kein Restore-Test +> durchgeführt und protokolliert wurde. diff --git a/docs/legal/current-state-audit.md b/docs/legal/current-state-audit.md new file mode 100644 index 0000000..e49da69 --- /dev/null +++ b/docs/legal/current-state-audit.md @@ -0,0 +1,44 @@ +# Bestandsaufnahme & Audit — Lageplan + +Stand: 2026-07-24 · Prüfung des tatsächlichen Repository-Stands vor gezielten Korrekturen. +Legende Status: ✅ umgesetzt · 🟡 teilweise · ⚠️ widersprüchlich/prüfen · ❌ fehlend · 🔒 manuell (Fachperson). + +Dieser Audit dokumentiert, was aus früheren Runden (v1.7.0–v1.9.0) bereits vorhanden ist, damit +**keine Doppelstrukturen** entstehen. Ergänzungen dieser Runde sind in der letzten Spalte markiert. + +| Bereich | Fundstelle | Aktueller Zustand | Risiko | Empfohlene/umgesetzte Änderung | Umgesetzt | +|---|---|---|---|---|---| +| Zentrale Legal-Config | `src/config/legal.ts` | Betreiber (Perparim Ziberi, Privatperson, Villmergen), Kontakt, Recht, Subprozessoren, Dok-Versionen | — | operatorType + fksAttribution ergänzt | ✅ (erweitert) | +| Impressum | `src/app/impressum` | Privatperson, echte Adresse; verweist auf Datenschutz | niedrig | Haftungsverweis auf NB prüfen | ✅ vorhanden | +| Datenschutz | `src/app/datenschutz` | Neuaufbau, Subprozessoren-Tabelle (#dienstleister), IP/UA-Nachweis, Auslandbekanntgabe | niedrig | Quellen-/Lizenz-Link ergänzt | ✅ vorhanden | +| Nutzungsbedingungen | `src/app/nutzungsbedingungen` | Abschnitte 1–14 vorhanden | 🟡 | FKS-Abschnitt ergänzt | ✅ (FKS ergänzt) | +| Verantwortungsvolle Nutzung | `src/app/verantwortungsvolle-nutzung` | vorhanden | niedrig | — | ✅ | +| Sicherheit (öffentlich) | `src/app/sicherheit` | TOM-Kurzfassung, 2FA | niedrig | — | ✅ | +| Unterstützen | `src/app/unterstuetzen` + `/spenden` | „Freiwilliger Unterstützungsbeitrag", Gebührenhinweis, keine Steuerabzug-Behauptung | niedrig | „Feature"-Tiers geprüft | ✅ | +| Org-Datenschutzvereinbarung (AVV) | `src/app/organisationen/datenschutzvereinbarung` | Entwurf, als Vereinbarung formuliert | 🟡 | Entwurfscharakter klar | ✅ | +| Konto & Datenschutz | `src/app/konto/datenschutz` | Zustimmungen, Export-/Löschanfrage, Kontakt | niedrig | — | ✅ | +| Admin Rechtsdokumente | `src/app/admin/legal` | Versionen, Zustimmungszähler, Suche, CSV, IP/UA/Hash | niedrig | — | ✅ | +| Consent-Datenmodell | `LegalDocument`, `LegalAcceptance` | versioniert, contentHash, IP/UA, unique, revisionssicher | niedrig | FKS_ATTRIBUTION-Typ ergänzt | ✅ (erweitert) | +| Registrierung | `src/app/register` + API | 2 getrennte Zustimmungen (NB akzeptieren / Datenschutz Kenntnis), keine Vorauswahl, Version+Hash+IP/UA | niedrig | — | ✅ | +| Re-Consent | `LegalConsentGate` | blockierend, Org-Bestätigung 7 Checks, Abmelden/Docs erreichbar | niedrig | — | ✅ | +| In-App-Hinweise | Dashboard/Messpanel/Datenschutz | Dashboard-Banner, Berechnungs-Hinweis, Geodaten-Hinweis | 🟡 | Journal-Freitext-Warnung ergänzt | ✅ (ergänzt) | +| Export-Disclaimer | `src/hooks/use-map-export.ts` | Prüf-Hinweis PDF+PNG | 🟡 | FKS-Quellenangabe ergänzt | ✅ (ergänzt) | +| **FKS-/FEUKOS-Quellenangabe** | Footer, `licenses-and-attributions.md` | nur Footer + interne Doku | ⚠️ unvollständig | Quellen-Seite, Symbol-Panel, NB, Export, Dok-Typ | ✅ (diese Runde) | +| Quellen-und-Lizenzen-Seite | — | ❌ fehlte | mittel | `/quellen-und-lizenzen` erstellt | ✅ (neu) | +| **Planstatus/Freigabe** | `prisma/schema.prisma` | ❌ keine Status-/Freigabefelder | mittel | Entwurf/Geprüft/Freigegeben + Prüfdatum | 🟡 siehe Bericht | +| Sicherheitsheader | `next.config.js` | CSP, HSTS, nosniff, Referrer/Permissions-Policy, frame-ancestors | niedrig | CSP unsafe-inline (MapLibre) dokumentiert | ✅ | +| MFA/WebAuthn | `src/lib/mfa.ts`, `webauthn.ts` | TOTP + Passkeys/YubiKey + Backup-Codes + Admin-Reset | niedrig | — | ✅ | +| Rate Limiting | `src/lib/rate-limit.ts` | Login/Register/Reset/Kontakt/Share/… | 🟡 (In-Memory) | — | ✅ | +| Mandantentrennung | `getProjectWithTenantCheck` | serverseitig erzwungen | mittel | automatisierte Tests ergänzt | 🟡 Tests (diese Runde) | +| Backup | Container + GUI (`/admin/backup`) | lokal + verschlüsselt SFTP/WebDAV, 30 Tage | niedrig | backup-and-restore.md ergänzt | ✅ | +| Docs (Compliance) | `docs/legal/` | inventory, subprocessors, TOM, retention, incident, licenses, claims-audit | 🟡 | +international-transfers, +privacy-risk, +manual-checklist, +this audit | ✅ (ergänzt) | + +## Verbotene/absolute Aussagen — Nachkontrolle +Frühere Runde entfernte 24/7, 100 %, DSG/DSGVO-konform, „keine Weitergabe", „nur Schweizer Server", +„100 % der Spende", „Kein Unternehmen". Diese Runde prüft zusätzlich FKS-Terminologie +(„offiziell/zertifiziert/Partner/empfohlen") → keine solchen Behauptungen zulässig. + +## Offen / manuell (siehe manual-action-checklist.md) +Juristische Schlussprüfung, spätere Vereinsgründung (nur als Option), Versicherungen, endgültige +Server-/Backupstandorte externer Dienste, Aufbewahrungsfristen-Entscheide, ergänzende FKS-Bestätigung +für Rohdatei-/API-Weitergabe, periodischer externer Sicherheitstest. diff --git a/docs/legal/international-data-transfers.md b/docs/legal/international-data-transfers.md new file mode 100644 index 0000000..07cdbb4 --- /dev/null +++ b/docs/legal/international-data-transfers.md @@ -0,0 +1,27 @@ +# Auslandbearbeitungen — Lageplan + +Stand: 2026-07-24 · sorgfältiger Entwurf. Unterscheidet **Unternehmenssitz** und **Serverregion**. +Klassifizierung: 🇨🇭 Schweiz · 🇪🇺 EU/EWR · ✅ anerkanntes Datenschutzniveau · ⚠️ zusätzliche +vertragliche/technische Massnahmen prüfen · ❓ nicht abschliessend geklärt. + +| Dienst | Funktion | Datenkategorien | Unternehmenssitz | Serverregion | Klassifizierung | Bemerkung | +|---|---|---|---|---|---|---| +| PostgreSQL (Eigenbetrieb) | Primäre Datenhaltung | Konto/Org/Plan/Journal | Schweiz (Betreiber) | Schweiz | 🇨🇭 | selbst gehostet | +| MinIO (Eigenbetrieb) | Datei-Uploads | Pläne/Logos/Symbole | Schweiz (Betreiber) | Schweiz | 🇨🇭 | selbst gehostet | +| OpenStreetMap Foundation | Tiles/Nominatim | Kachel/Adresse/IP | UK | UK/EU | ✅/⚠️ | clientseitig, IP-Übermittlung | +| swisstopo | Landeskarte | Kachel/IP | Schweiz | Schweiz | 🇨🇭 | clientseitig | +| Esri (ArcGIS) | Satellitenbilder | Kachel/IP | USA | USA/global | ⚠️ | clientseitig; Bedingungen prüfen | +| Open-Meteo | Wind/Höhe | Koordinaten/IP | Deutschland | EU | 🇪🇺 | clientseitig | +| Open-Elevation | Höhe (Fallback) | Koordinaten/IP | ❓ | ❓ | ❓ | Community-Dienst; Region unklar | +| Stripe (optional) | Zahlungen | Betrag/Name/Zahlungsdaten | USA (EU-Töchter) | USA/EU | ⚠️ | nur wenn konfiguriert; DPA prüfen | +| SMTP-Anbieter (optional) | E-Mails | Empfänger/Inhalt | abhängig | abhängig | ⚠️/❓ | vom Betreiber gewählt | + +## Grundsätze +- Die öffentliche Datenschutzerklärung macht **keine** pauschale „nur Schweiz"-Garantie; Auslandbearbeitung + (v.a. Stripe, E-Mail, globale Kartendienste) wird transparent benannt. +- Client-seitige Karten-/Geodienste erhalten die IP-Adresse des Nutzers direkt. + +## Offen / manuell (Betreiber) +- DPA/Standardvertragsklauseln mit Stripe und dem gewählten E-Mail-Anbieter prüfen/abschliessen. +- Serverregion von Esri/Open-Elevation konkretisieren oder Dienst ggf. ersetzen. +- Entscheidung, ob der Open-Elevation-Fallback (unklare Region) deaktiviert werden soll. diff --git a/docs/legal/licenses-and-attributions.md b/docs/legal/licenses-and-attributions.md index 3c8f599..ae260db 100644 --- a/docs/legal/licenses-and-attributions.md +++ b/docs/legal/licenses-and-attributions.md @@ -1,8 +1,23 @@ # Lizenzen und Attributionen — Lageplan -Stand: 2026-07-23 · sorgfältiger Entwurf. Eine Quellenangabe ersetzt NICHT automatisch eine +Stand: 2026-07-24 · sorgfältiger Entwurf. Eine Quellenangabe ersetzt NICHT automatisch eine erforderliche Nutzungslizenz. Offene Freigabefragen sind markiert. +## Feuerwehr Koordination Schweiz FKS — schriftliche Genehmigung +- **Organisation:** Feuerwehr Koordination Schweiz FKS +- **Inhalte:** FEUKOS-Markierungen, -Symbole und bewilligtes Bildmaterial +- **Datum der Freigabe:** 17. Februar 2026 +- **Quellenangabe (exakt):** „Feuerwehr Koordination Schweiz FKS" +- **Projektbezug:** Verwendung innerhalb des beschriebenen Webprojekts lageplan.ch +- **Archivierung:** Das Original (E-Mail) ist **ausserhalb des öffentlichen Repositorys** abgelegt + (siehe `private-legal-evidence/`, in `.gitignore`). Es wird **nicht** committet; keine Namen/ + E-Mail-Adressen einzelner FKS-Mitarbeitender werden veröffentlicht. +- **Kein** Anspruch auf offizielle Partnerschaft, Zertifizierung, Prüfung, behördliche Anerkennung + oder Empfehlung von lageplan.ch durch die FKS. +- **Offene Frage (manuell):** Falls Rohdatei-Downloads, API-Nutzung oder Weitergabe der Symbol- + bibliothek an Dritte **nicht** Teil der ursprünglichen Projektbeschreibung waren, ist eine + ergänzende FKS-Bestätigung einzuholen (siehe `manual-action-checklist.md`). + ## Karten & Geodaten | Quelle | Nutzung | Lizenz/Bedingungen | Attribution | Offene Frage | |---|---|---|---|---| @@ -15,7 +30,7 @@ erforderliche Nutzungslizenz. Offene Freigabefragen sind markiert. ## Symbole / Icons | Quelle | Nutzung | Lizenz | Attribution | Offene Frage | |---|---|---|---|---| -| FKS/BABS taktische Zeichen | Einsatzsymbole | `[NUTZUNGSRECHT ZU KLÄREN]` | "Bildquelle Feuerwehr Koordination Schweiz FKS" (vorhanden) | ⚠️ **Nutzungsrecht/Freigabe FKS bestätigen** | +| FEUKOS-/FKS-Symbole | Einsatzsymbole | Schriftliche Genehmigung FKS (17.02.2026) | "Bildquelle: Feuerwehr Koordination Schweiz FKS" | Rohdatei-/API-Weitergabe ggf. nicht abgedeckt (siehe unten) | | lucide-react | UI-Icons | ISC | — (im Code) | ok | ## Schriftarten diff --git a/docs/legal/manual-action-checklist.md b/docs/legal/manual-action-checklist.md new file mode 100644 index 0000000..18ac27b --- /dev/null +++ b/docs/legal/manual-action-checklist.md @@ -0,0 +1,39 @@ +# Manuelle Restpunkte — was Code nicht lösen kann + +Stand: 2026-07-24. Diese Punkte sind **nicht** durch die Software erledigt und dürfen **nicht** als +erledigt dargestellt werden. Sie erfordern Entscheidungen, Verträge oder externe Prüfungen. + +## Rechtlich +- [ ] **Juristische Schlussprüfung** aller Rechtsseiten (Nutzungsbedingungen, Datenschutz, AVV, FKS- + Abschnitt) durch eine qualifizierte Schweizer Fachperson. +- [ ] Entscheidung über eine **spätere Vereinsgründung** (aktuell nur offene Option — Betreiber bleibt + Privatperson Perparim Ziberi). Kein Verein behaupten, solange nicht gegründet. +- [ ] Tatsächliche **Übertragung auf einen Verein** erst nach dessen Gründung (dann Betreiberangaben, + Impressum, AVV, Config anpassen). +- [ ] **Steuerliche Behandlung** freiwilliger Unterstützungsbeiträge klären. + +## FKS / Bildmaterial +- [ ] Original der FKS-Freigabe (17.02.2026) **ausserhalb** des Repos archivieren (`private-legal-evidence/`). +- [ ] Prüfen, ob **Rohdatei-Download, API-Nutzung oder Weitergabe** der Symbolbibliothek an Dritte von der + ursprünglichen Projektbeschreibung gedeckt ist; falls nicht → **ergänzende FKS-Bestätigung** einholen. + +## Versicherung +- [ ] IT-/Vermögensschaden-Haftpflicht prüfen (Offerten einholen). +- [ ] Cyberversicherung prüfen. +- [ ] Bei späterem Verein: Vereins-/Rechtsschutz prüfen. + +## Verträge & Standorte +- [ ] Formelle Vereinbarungen mit Gemeinden/Organisationen (falls gewünscht) — die elektronische + Organisationsbestätigung ist **kein** beidseitig unterzeichneter AVV. +- [ ] Endgültige **Server- und Backupstandorte** der externen Dienste bestätigen (Esri, Stripe, SMTP, + Open-Elevation) — siehe `international-data-transfers.md`. +- [ ] **DPA/Standardvertragsklauseln** mit Stripe und dem gewählten E-Mail-Anbieter abschliessen. + +## Aufbewahrung & Betrieb +- [ ] Konkrete **Aufbewahrungsfristen** je Datenkategorie festlegen (siehe `retention-and-deletion.md`). +- [ ] **Restore-Test** durchführen und in `docs/BACKUP.md` protokollieren. +- [ ] Periodischen **externen Sicherheitstest / Pentest** beauftragen. + +## Datenschutz +- [ ] Prüfen lassen, ob eine **vertiefte DSFA** erforderlich ist (siehe `privacy-risk-assessment.md`). +- [ ] `datenschutz@lageplan.ch` als **aktiv überwachtes** Postfach bestätigen. diff --git a/docs/legal/privacy-risk-assessment.md b/docs/legal/privacy-risk-assessment.md new file mode 100644 index 0000000..b2d0c48 --- /dev/null +++ b/docs/legal/privacy-risk-assessment.md @@ -0,0 +1,24 @@ +# Datenschutz-Risikovorprüfung — Lageplan + +Stand: 2026-07-24 · sorgfältiger Entwurf. Vorprüfung, **keine** abschliessende Datenschutz- +Folgenabschätzung (DSFA). Ob eine vertiefte DSFA nötig ist, muss eine Fachperson beurteilen — +dies wird hier **nicht** pauschal verneint. + +Skala: Wahrscheinlichkeit/Auswirkung = niedrig / mittel / hoch. + +| # | Risiko | Wahrsch. | Auswirkung | Bestehende Massnahmen | Restrisiko | Weitere Massnahme | Rolle | +|---|---|---|---|---|---|---|---| +| 1 | Offenlegung von Gebäude-/Zufahrts-/Gefahren-/Hydranten-/Schlüsselstellen-Infos | mittel | hoch | Mandantentrennung, Rollen, Zugriffskontrolle, Verbot sensibler Daten (NB) | mittel | Org-Sensibilisierung, ggf. Feldklassifizierung | Betreiber/Org | +| 2 | Organisationsübergreifender Zugriff (IDOR) | niedrig | hoch | `getProjectWithTenantCheck`, serverseitige Prüfung, autom. Tests | niedrig | periodische Tests/Pentest | Betreiber | +| 3 | Datenleck über Dateizugriffe | niedrig | hoch | serve-Routen mit Tenant-Check, MinIO nicht öffentlich für private Objekte | niedrig | Pentest, Bucket-Policy-Review | Betreiber | +| 4 | Kompromittierung Adminkonto | niedrig | hoch | MFA (TOTP/WebAuthn), Rate-Limiting, bcrypt | niedrig | MFA für alle Admins erzwingen (Option) | Betreiber | +| 5 | Auslandbearbeitung (Stripe/E-Mail/Karten) | mittel | mittel | Transparente Doku, optionale Dienste, IP nur clientseitig | mittel | DPA/SCC prüfen | Betreiber | +| 6 | Standortdaten (GPS) der Nutzer | niedrig | mittel | nur zur Kartenfunktion, keine dauerhafte Speicherung durch App | niedrig | Doku bestätigen | Betreiber | +| 7 | Besonders schützenswerte Daten in Freitext/Journal | mittel | hoch | NB-Verbot, In-App-Warnungen (Journal, Dashboard), Org-Bestätigung | mittel | ggf. Muster-Erkennung/Hinweis | Betreiber/Org | +| 8 | Verlust/Nichtverfügbarkeit von Daten | niedrig | mittel | Backups (lokal + verschlüsselt off-site) | niedrig | Restore-Test durchführen | Betreiber | +| 9 | Datenleck durch Drittanbieter-Subprozessor | niedrig | mittel | minimale Datenweitergabe, Subprozessorenliste | mittel | Anbieterprüfung dokumentieren | Betreiber | +| 10 | Fehlende/veraltete fachliche Prüfung eines Plans | mittel | hoch | Hinweise in App/Export; Planstatus (in Umsetzung) | mittel | Freigabe-Workflow finalisieren | Org | + +## Empfehlung +Eine **vertiefte DSFA** sollte durch eine qualifizierte Fachperson geprüft werden — insbesondere wegen +sicherheitsrelevanter Objekt-/Infrastrukturdaten (Risiko 1, 3, 7). Diese Vorprüfung ersetzt sie nicht. diff --git a/package.json b/package.json index 07a5aaf..d38855e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lageplan", - "version": "1.9.0", + "version": "1.9.1", "description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation", "private": true, "scripts": { diff --git a/prisma/migrate.js b/prisma/migrate.js index 89cf478..efa4593 100644 --- a/prisma/migrate.js +++ b/prisma/migrate.js @@ -217,6 +217,7 @@ async function migrate() { { type: 'ORGANIZATION_DECLARATION', version: '1.0.0', title: 'Organisationsbestätigung', url: '/verantwortungsvolle-nutzung', publishedAt: '2026-07-23' }, { type: 'RESPONSIBLE_USE', version: '1.0.0', title: 'Verantwortungsvolle Nutzung', url: '/verantwortungsvolle-nutzung', publishedAt: '2026-07-23' }, { type: 'DATA_PROCESSING_AGREEMENT', version: '1.0.0', title: 'Datenschutzvereinbarung für Organisationen', url: '/organisationen/datenschutzvereinbarung', publishedAt: '2026-07-23' }, + { type: 'FKS_ATTRIBUTION', version: '1.0.0', title: 'FKS-Quellenangabe & Nutzungshinweis', url: '/quellen-und-lizenzen', publishedAt: '2026-07-24' }, ] for (const doc of LEGAL_DOCS) { const contentHash = crypto.createHash('sha256').update(`${doc.type}@${doc.version}`).digest('hex') diff --git a/src/app/api/legal/accept/route.ts b/src/app/api/legal/accept/route.ts index 94739f1..3ea3551 100644 --- a/src/app/api/legal/accept/route.ts +++ b/src/app/api/legal/accept/route.ts @@ -3,8 +3,8 @@ import { getSession } from '@/lib/auth' import { getClientIp } from '@/lib/rate-limit' import { recordAcceptances, getPendingAcceptances, type LegalAcceptanceContext, type LegalDocumentType } from '@/lib/legal' -const VALID_TYPES: LegalDocumentType[] = ['TERMS', 'PRIVACY', 'ORGANIZATION_DECLARATION', 'RESPONSIBLE_USE', 'DATA_PROCESSING_AGREEMENT'] -const VALID_CONTEXTS: LegalAcceptanceContext[] = ['REGISTRATION', 'LOGIN_RECONSENT', 'ORGANIZATION_CREATION', 'ADMIN_ROLE_ACCEPTANCE', 'DONATION'] +const VALID_TYPES: LegalDocumentType[] = ['TERMS', 'PRIVACY', 'ORGANIZATION_DECLARATION', 'RESPONSIBLE_USE', 'DATA_PROCESSING_AGREEMENT', 'FKS_ATTRIBUTION'] +const VALID_CONTEXTS: LegalAcceptanceContext[] = ['REGISTRATION', 'LOGIN_RECONSENT', 'ORGANIZATION_CREATION', 'ADMIN_ROLE_ACCEPTANCE', 'EXISTING_USER_RECONSENT', 'SUPPORT_CONTRIBUTION', 'DONATION'] // POST: erfasst Zustimmungen des angemeldeten Benutzers revisionssicher. // body: { types: LegalDocumentType[], context: LegalAcceptanceContext } diff --git a/src/app/nutzungsbedingungen/page.tsx b/src/app/nutzungsbedingungen/page.tsx index 6f746e8..cbb8723 100644 --- a/src/app/nutzungsbedingungen/page.tsx +++ b/src/app/nutzungsbedingungen/page.tsx @@ -137,6 +137,27 @@ export default function NutzungsbedingungenPage() { +
+

10a. FEUKOS-/FKS-Markierungen und -Symbole

+

+ Bestimmte in {legal.projectName} verwendete Markierungen und Symbole stammen von der Feuerwehr + Koordination Schweiz FKS. Die Verwendung innerhalb von {legal.projectName} erfolgt mit schriftlicher + Genehmigung. Bildquelle: Feuerwehr Koordination Schweiz FKS. +

+

+ Die Genehmigung bezieht sich auf die Verwendung innerhalb des beschriebenen Webprojekts. Sie + begründet keine offizielle Partnerschaft, Prüfung, Zertifizierung oder Empfehlung von + {' '}{legal.projectName} durch die FKS. +

+

+ Nutzer erhalten keine weitergehenden Rechte, die Symbole ausserhalb der von {legal.projectName} + {' '}vorgesehenen Funktionen separat zu vervielfältigen, weiterzugeben, zu verkaufen oder in anderen + Anwendungen zu verwenden. Zulässig bleibt die Verwendung in ordnungsgemäss mit {legal.projectName} + {' '}erstellten Lageplänen und Exporten, soweit dies vom vorgesehenen Funktionsumfang und der + erteilten Genehmigung gedeckt ist. Siehe auch Quellen und Lizenzen. +

+
+

11. Haftung

Im gesetzlich zulässigen Umfang gilt:

diff --git a/src/app/page.tsx b/src/app/page.tsx index 372c13f..9a26f99 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -92,7 +92,7 @@ export default function LandingPage() { name: 'Welche Symbole sind verfügbar?', acceptedAnswer: { '@type': 'Answer', - text: 'Alle 117 offiziellen FKS/BABS-Signaturen sind integriert. Zusätzlich können eigene Symbole hochgeladen werden.', + text: 'Alle 117 taktischen FEUKOS-Symbole der Feuerwehr Koordination Schweiz FKS sind integriert (Bildquelle: FKS). Zusätzlich können eigene Symbole hochgeladen werden.', }, }, { @@ -352,7 +352,7 @@ export default function LandingPage() {
{[ { icon: Map, title: 'Interaktive Karte', desc: 'Lagepläne auf OSM-Karten zeichnen mit Linien, Polygonen, Pfeilen und Freihandzeichnung.' }, - { icon: Shield, title: 'FKS-Signaturen', desc: 'Alle offiziellen FKS/BABS-Signaturen per Drag & Drop platzieren. Eigene Symbole hochladen.' }, + { icon: Shield, title: 'FEUKOS-Symbole', desc: 'Taktische FEUKOS-Symbole der FKS (Bildquelle: FKS) per Drag & Drop platzieren. Eigene Symbole hochladen.' }, { icon: Ruler, title: 'Messwerkzeug', desc: 'Distanzen messen mit Druckverlustberechnung und Pumpenanzahl für Schlauchleitungen.' }, { icon: FileText, title: 'Einsatzjournal', desc: 'Journal mit Zeitstempel, SOMA-Checkliste und Pendenzenliste — alles an einem Ort.' }, { icon: Smartphone, title: 'Installierbare App (PWA)', desc: 'Auf dem Homescreen installierbar — funktioniert offline wie eine native App. Desktop, Tablet & Smartphone.' }, @@ -389,7 +389,7 @@ export default function LandingPage() {
{[ { status: 'done', title: 'Interaktive Karte mit Zeichenwerkzeugen', desc: 'Linien, Polygone, Pfeile, Freihand, Punkte' }, - { status: 'done', title: 'FKS-Signaturen (117 offizielle Symbole)', desc: 'Alle taktischen Zeichen nach Schweizer Standard (Bildquelle: Feuerwehr Koordination Schweiz FKS)' }, + { status: 'done', title: 'FEUKOS-Symbole (117 taktische Zeichen)', desc: 'Taktische Zeichen nach der Systematik der Feuerwehr Koordination Schweiz FKS (Bildquelle: Feuerwehr Koordination Schweiz FKS)' }, { status: 'done', title: 'Einsatzjournal mit SOMA-Checkliste', desc: 'Zeitstempel, Pendenzen, Protokollierung' }, { status: 'done', title: 'Messwerkzeug mit Druckverlustberechnung', desc: 'Distanzen, Höhenmeter, Pumpenanzahl' }, { status: 'done', title: 'Echtzeit-Zusammenarbeit (WebSocket)', desc: 'Mehrere Benutzer gleichzeitig am Lageplan' }, @@ -459,7 +459,7 @@ export default function LandingPage() { }, { q: 'Welche Symbole sind verfügbar?', - a: 'Alle 117 offiziellen FKS/BABS-Signaturen sind integriert. Zusätzlich können eigene Symbole hochgeladen werden.', + a: 'Alle 117 taktischen FEUKOS-Symbole der Feuerwehr Koordination Schweiz FKS sind integriert (Bildquelle: FKS). Zusätzlich können eigene Symbole hochgeladen werden.', }, { q: 'Kann ich Lagepläne exportieren?', @@ -533,13 +533,14 @@ export default function LandingPage() { Nutzungsbedingungen Verantwortungsvolle Nutzung Sicherheit + Quellen & Lizenzen Datenschutzvereinbarung (Org.) Unterstützen

© {new Date().getFullYear()} Lageplan — Perparim Ziberi. Alle Rechte vorbehalten.

-

Taktische Symbole: Bildquelle Feuerwehr Koordination Schweiz FKS

+

Bildquelle: Feuerwehr Koordination Schweiz FKS · Quellen & Lizenzen

v{process.env.APP_VERSION}

diff --git a/src/app/quellen-und-lizenzen/page.tsx b/src/app/quellen-und-lizenzen/page.tsx new file mode 100644 index 0000000..ad05e5e --- /dev/null +++ b/src/app/quellen-und-lizenzen/page.tsx @@ -0,0 +1,63 @@ +'use client' + +import { LegalPage } from '@/components/legal/legal-page' +import { legal } from '@/config/legal' + +export default function QuellenUndLizenzenPage() { + const d = legal.documents.fksAttribution + const fks = legal.fksAttribution + return ( + +
+

Feuerwehr Koordination Schweiz FKS

+
    +
  • Inhalte: FEUKOS-Markierungen und -Symbole sowie bewilligtes Bildmaterial.
  • +
  • Grundlage: schriftliche Genehmigung für die Verwendung innerhalb des Webprojekts {legal.projectName}.
  • +
  • Quellenangabe: {fks.label}
  • +
+

{fks.note}

+

+ Hinweis: Es besteht keine offizielle Partnerschaft, Zertifizierung, Prüfung, behördliche + Anerkennung oder Empfehlung von {legal.projectName} durch die FKS. Nutzer erhalten keine + weitergehenden Rechte, die Symbole ausserhalb der von {legal.projectName} vorgesehenen + Funktionen separat zu vervielfältigen, weiterzugeben, zu verkaufen oder in anderen Anwendungen + zu verwenden. +

+
+ +
+

Karten- und Geodaten

+
    +
  • OpenStreetMap: Basiskarte und Adresssuche (Nominatim) — © OpenStreetMap-Mitwirkende, Daten unter ODbL.
  • +
  • Esri World Imagery: Satellitenbilder — Nutzung gemäss Esri-Bedingungen.
  • +
  • swisstopo (geo.admin.ch): Schweizer Landeskarte — © swisstopo, Nutzung gemäss Bedingungen von swisstopo.
  • +
  • Open-Meteo / Open-Elevation: Wind- und Höhendaten.
  • +
+

Karten-, Adress- und Geodaten können unvollständig, ungenau oder veraltet sein.

+
+ +
+

Schriftarten, Icons & Bibliotheken

+
    +
  • Barlow (SIL Open Font License 1.1), build-time selbst gehostet.
  • +
  • lucide-react (ISC) für Oberflächen-Icons.
  • +
  • Wesentliche Open-Source-Bibliotheken: Next.js/React (MIT), MapLibre GL (BSD-3), jsPDF (MIT), u.a.
  • +
+
+ +
+

Eigene Inhalte

+

+ Von Benutzern hochgeladene Inhalte (Pläne, Logos, eigene Symbole) verbleiben bei den jeweiligen + Benutzern bzw. Organisationen. Für die Rechtmässigkeit hochgeladener Inhalte sind die Benutzer + verantwortlich (siehe Nutzungsbedingungen). +

+
+
+ ) +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index f19ee82..0f82ec1 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -49,6 +49,7 @@ export default function sitemap(): MetadataRoute.Sitemap { { url: `${baseUrl}/nutzungsbedingungen`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, { url: `${baseUrl}/verantwortungsvolle-nutzung`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, { url: `${baseUrl}/sicherheit`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, + { url: `${baseUrl}/quellen-und-lizenzen`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, { url: `${baseUrl}/unterstuetzen`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, { url: `${baseUrl}/organisationen/datenschutzvereinbarung`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, ] diff --git a/src/components/journal/journal-view.tsx b/src/components/journal/journal-view.tsx index 227be7d..b906251 100644 --- a/src/components/journal/journal-view.tsx +++ b/src/components/journal/journal-view.tsx @@ -789,6 +789,9 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei +

+ Keine Patienten-, Gesundheits-, Polizei-, Passwort-, Zugangs- oder besonders schützenswerten Daten eintragen. +

)} diff --git a/src/components/layout/right-sidebar.tsx b/src/components/layout/right-sidebar.tsx index 7650654..42bf832 100644 --- a/src/components/layout/right-sidebar.tsx +++ b/src/components/layout/right-sidebar.tsx @@ -326,6 +326,10 @@ export function RightSidebar({ onSymbolDrop, onSymbolSelect, canEdit, isOpen, on />

{totalSymbols} Symbole verfügbar

+

+ Bildquelle: Feuerwehr Koordination Schweiz FKS ·{' '} + Quellen & Lizenzen +

{isLoading && ( diff --git a/src/components/legal/legal-page.tsx b/src/components/legal/legal-page.tsx index 227ea3e..2d48113 100644 --- a/src/components/legal/legal-page.tsx +++ b/src/components/legal/legal-page.tsx @@ -11,6 +11,7 @@ export const LEGAL_LINKS: { href: string; label: string }[] = [ { href: '/nutzungsbedingungen', label: 'Nutzungsbedingungen' }, { href: '/verantwortungsvolle-nutzung', label: 'Verantwortungsvolle Nutzung' }, { href: '/sicherheit', label: 'Sicherheit' }, + { href: '/quellen-und-lizenzen', label: 'Quellen & Lizenzen' }, { href: '/organisationen/datenschutzvereinbarung', label: 'Datenschutzvereinbarung (Org.)' }, { href: '/unterstuetzen', label: 'Unterstützen' }, ] diff --git a/src/config/__tests__/legal-config.test.ts b/src/config/__tests__/legal-config.test.ts index 2487f0a..5215a62 100644 --- a/src/config/__tests__/legal-config.test.ts +++ b/src/config/__tests__/legal-config.test.ts @@ -18,9 +18,9 @@ describe('isPlaceholder', () => { }) describe('legal config', () => { - it('führt genau 5 Rechtsdokumenttypen', () => { + it('führt die erwarteten Rechtsdokumenttypen (inkl. FKS-Quellenangabe)', () => { expect(Object.keys(legal.documents).sort()).toEqual( - ['dataProcessingAgreement', 'organizationDeclaration', 'privacy', 'responsibleUse', 'terms'] + ['dataProcessingAgreement', 'fksAttribution', 'organizationDeclaration', 'privacy', 'responsibleUse', 'terms'] ) }) it('listet nur real genutzte Subprozessoren (keine erfundenen wie Google Analytics)', () => { diff --git a/src/config/legal.ts b/src/config/legal.ts index 37d88d3..97dc7bc 100644 --- a/src/config/legal.ts +++ b/src/config/legal.ts @@ -62,6 +62,8 @@ export const legal = { operatorName: env('LEGAL_OPERATOR_NAME', 'Perparim Ziberi'), /** Rechtsform */ operatorLegalForm: env('LEGAL_OPERATOR_FORM', 'Privatperson'), + /** Betreibertyp (Privatperson). Eine spätere Übertragung auf einen Verein ist nur eine offene Option. */ + operatorType: env('LEGAL_OPERATOR_TYPE', 'Privatperson'), /** Vollständige Postadresse des Betreibers */ postalAddress: env('LEGAL_POSTAL_ADDRESS', 'Anglikerstrasse 56, 5612 Villmergen'), @@ -96,18 +98,28 @@ export const legal = { // Nichts erfinden: nur tatsächlich im Code kontaktierte Dienste. Sitz/Region, die nicht // sicher bekannt ist, bleibt Platzhalter. processors: [ - { name: 'OpenStreetMap (Tiles)', purpose: 'Basiskarte', data: 'Kachel-Koordinaten (Kartenausschnitt)', region: '[REGION – OSM Foundation, EU/UK]' }, - { name: 'Esri ArcGIS World Imagery', purpose: 'Satellitenkarte', data: 'Kachel-Koordinaten', region: '[REGION]' }, - { name: 'swisstopo (geo.admin.ch)', purpose: 'Schweizer Landeskarte', data: 'Kachel-Koordinaten', region: 'Schweiz' }, - { name: 'Nominatim (OpenStreetMap)', purpose: 'Adress-/Reverse-Geocoding', data: 'Adress-/Ortssuche, GPS-Koordinaten', region: '[REGION – OSM Foundation]' }, - { name: 'Open-Meteo', purpose: 'Wind- und Höhendaten', data: 'Koordinaten', region: '[REGION – EU]' }, - { name: 'Open-Elevation', purpose: 'Höhendaten (Fallback)', data: 'Koordinaten', region: '[REGION]' }, - { name: 'Stripe', purpose: 'Zahlungsabwicklung freiwilliger Unterstützungsbeiträge', data: 'Betrag, freiwilliger Name/Nachricht, Zahlungsdaten (direkt bei Stripe)', region: '[REGION – u.a. USA/EU; Auslandbearbeitung möglich]', optional: true }, - { name: 'SMTP-/E-Mail-Anbieter', purpose: 'System-E-Mails (Verifizierung, Reset, Kontakt, Einladungen)', data: 'E-Mail-Adresse, Nachrichteninhalt', region: '[REGION – abhängig vom konfigurierten Anbieter]', optional: true }, + { name: 'OpenStreetMap Foundation (Tiles)', purpose: 'Basiskarte', data: 'Kachel-Koordinaten (Kartenausschnitt), IP-Adresse', region: 'Vereinigtes Königreich / EU' }, + { name: 'Esri ArcGIS World Imagery', purpose: 'Satellitenkarte', data: 'Kachel-Koordinaten, IP-Adresse', region: 'USA (Auslandbearbeitung möglich)' }, + { name: 'swisstopo (geo.admin.ch)', purpose: 'Schweizer Landeskarte', data: 'Kachel-Koordinaten, IP-Adresse', region: 'Schweiz' }, + { name: 'Nominatim (OpenStreetMap Foundation)', purpose: 'Adress-/Reverse-Geocoding', data: 'Adress-/Ortssuche, GPS-Koordinaten, IP-Adresse', region: 'Vereinigtes Königreich / EU' }, + { name: 'Open-Meteo', purpose: 'Wind- und Höhendaten', data: 'Koordinaten, IP-Adresse', region: 'EU (Deutschland)' }, + { name: 'Open-Elevation', purpose: 'Höhendaten (Fallback)', data: 'Koordinaten, IP-Adresse', region: 'Region nicht abschliessend geklärt (Community-Dienst)' }, + { name: 'Stripe', purpose: 'Zahlungsabwicklung freiwilliger Unterstützungsbeiträge', data: 'Betrag, freiwilliger Name/Nachricht, Zahlungsdaten (direkt bei Stripe)', region: 'USA / EU (Auslandbearbeitung möglich)', optional: true }, + { name: 'SMTP-/E-Mail-Anbieter', purpose: 'System-E-Mails (Verifizierung, Reset, Kontakt, Einladungen)', data: 'E-Mail-Adresse, Nachrichteninhalt', region: 'abhängig vom konfigurierten Anbieter', optional: true }, { name: 'MinIO (Objektspeicher, Eigenbetrieb)', purpose: 'Datei-/Bild-Uploads (Pläne, Logos, Symbole)', data: 'hochgeladene Dateien', region: 'Schweiz (beim Betreiber)' }, { name: 'PostgreSQL (Datenbank, Eigenbetrieb)', purpose: 'Primäre Datenhaltung', data: 'Konto-, Organisations-, Plan- und Journaldaten', region: 'Schweiz (beim Betreiber)' }, ] as ExternalProcessor[], + // ─── FKS/FEUKOS-Bildmaterial (schriftliche Genehmigung) ────────── + // Die geforderte Quellenangabe lautet exakt "Feuerwehr Koordination Schweiz FKS". + // KEINE offizielle Partnerschaft, Zertifizierung, Prüfung oder Empfehlung behaupten. + fksAttribution: { + label: 'Feuerwehr Koordination Schweiz FKS', + sourceLine: 'Bildquelle: Feuerwehr Koordination Schweiz FKS', + approvalDate: '17. Februar 2026', + note: 'Die FEUKOS-Markierungen und -Symbole werden mit schriftlicher Genehmigung der Feuerwehr Koordination Schweiz FKS verwendet. Die Genehmigung bezieht sich auf die Verwendung des Bildmaterials innerhalb von lageplan.ch und stellt keine offizielle Partnerschaft, Zertifizierung, Prüfung oder Empfehlung von lageplan.ch durch die FKS dar.', + }, + // ─── Rechtsdokumente: bewusst gesetzte Versionen ────────── // WICHTIG: Version manuell erhöhen, wenn sich der Inhalt WESENTLICH ändert (löst Re-Consent aus). // Nicht automatisch aus minimalen Dateiänderungen ableiten. @@ -117,6 +129,7 @@ export const legal = { organizationDeclaration: { version: '1.0.0', publishedAt: '2026-07-23' }, responsibleUse: { version: '1.0.0', publishedAt: '2026-07-23' }, dataProcessingAgreement: { version: '1.0.0', publishedAt: '2026-07-23' }, + fksAttribution: { version: '1.0.0', publishedAt: '2026-07-24' }, } as Record, /** Dezenter Standard-Hinweis auf allen Rechtsseiten (kein Entwurfshinweis). */ diff --git a/src/hooks/use-map-export.ts b/src/hooks/use-map-export.ts index ff8cd05..a57e474 100644 --- a/src/hooks/use-map-export.ts +++ b/src/hooks/use-map-export.ts @@ -315,6 +315,7 @@ export function useMapExport({ if (format === 'png') { // Rechtlicher Prüf-Hinweis unten einbrennen const disclaimer = 'Mit Lageplan erstellt · Angaben vor Verwendung fachlich prüfen · Offline-/Einsatzunterlagen der Organisation bleiben massgebend' + + (distinctSymbols.length > 0 ? ' · Bildquelle: Feuerwehr Koordination Schweiz FKS' : '') const barH = Math.max(18, Math.round(exportCanvas.width * 0.014)) ctx.save() ctx.fillStyle = 'rgba(0,0,0,0.55)' @@ -448,6 +449,13 @@ export function useMapExport({ // ── Footer ── const footerY = pageH - m - 4 + // FKS-Quellenangabe (wenn Symbole enthalten sind) + if (distinctSymbols.length > 0) { + pdf.setFontSize(6) + pdf.setFont('helvetica', 'normal') + pdf.setTextColor(120, 120, 120) + pdf.text('Bildquelle: Feuerwehr Koordination Schweiz FKS', m, footerY - 8.5) + } // Rechtlicher Prüf-Hinweis (eine Zeile über dem Meta-Footer) pdf.setFontSize(6.5) pdf.setFont('helvetica', 'italic') diff --git a/src/lib/__tests__/crypto-secret.test.ts b/src/lib/__tests__/crypto-secret.test.ts new file mode 100644 index 0000000..922e6a8 --- /dev/null +++ b/src/lib/__tests__/crypto-secret.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, beforeAll } from 'vitest' + +// Server-Secret für die Schlüsselableitung setzen, bevor das Modul es liest. +beforeAll(() => { process.env.NEXTAUTH_SECRET = 'test-secret-fuer-verschluesselung-min-16' }) + +describe('crypto-secret (Zugangsdaten-/Passphrase-Verschlüsselung)', () => { + it('verschlüsselt und entschlüsselt korrekt (Round-Trip)', async () => { + const { encryptSecret, decryptSecret, isEncrypted } = await import('@/lib/crypto-secret') + const plain = 'sftp-Passwort-123!' + const enc = encryptSecret(plain) + expect(isEncrypted(enc)).toBe(true) + expect(enc).not.toContain(plain) + expect(decryptSecret(enc)).toBe(plain) + }) + + it('erzeugt bei gleichem Klartext unterschiedliche Chiffren (zufälliges Salt/IV)', async () => { + const { encryptSecret } = await import('@/lib/crypto-secret') + expect(encryptSecret('gleich')).not.toBe(encryptSecret('gleich')) + }) + + it('gibt unverschlüsselte Altwerte unverändert zurück (abwärtskompatibel)', async () => { + const { decryptSecret, isEncrypted } = await import('@/lib/crypto-secret') + expect(isEncrypted('klartext')).toBe(false) + expect(decryptSecret('klartext')).toBe('klartext') + }) + + it('leere Werte bleiben leer', async () => { + const { encryptSecret, decryptSecret } = await import('@/lib/crypto-secret') + expect(encryptSecret('')).toBe('') + expect(decryptSecret('')).toBe('') + }) +}) diff --git a/src/lib/legal-rules.ts b/src/lib/legal-rules.ts index c548ec5..404c748 100644 --- a/src/lib/legal-rules.ts +++ b/src/lib/legal-rules.ts @@ -8,12 +8,15 @@ export type LegalDocumentType = | 'ORGANIZATION_DECLARATION' | 'RESPONSIBLE_USE' | 'DATA_PROCESSING_AGREEMENT' + | 'FKS_ATTRIBUTION' export type LegalAcceptanceContext = | 'REGISTRATION' | 'LOGIN_RECONSENT' | 'ORGANIZATION_CREATION' | 'ADMIN_ROLE_ACCEPTANCE' + | 'EXISTING_USER_RECONSENT' + | 'SUPPORT_CONTRIBUTION' | 'DONATION' /** Für ALLE Benutzer erforderliche Dokumente. */