Datenschutz-Ausbau: Archiv-Verschlüsselung, App-Sperre, Auto-Löschen

- Brief-Archiv jetzt AES-256-GCM-verschlüsselt (expo-crypto, Schlüssel im
  SecureStore); Migration alter Klartext-Bestände beim ersten Laden;
  im Simulator auf der Platte verifiziert (nur Chiffrat, kein Klartext)
- AppSchutz-Komponente: optionale App-Sperre (Face ID / Geräte-Code,
  expo-local-authentication) + Sichtschutz im App-Umschalter
- Auto-Löschen alter Briefe (30/90 Tage, Einstellungen), pure Filter-
  Funktion mit Tests
- UTF-8-Helfer (Umlaute/Arabisch/Emoji-sicher) mit Tests; gesamt 30 Tests
- Einstellungen: neuer Abschnitt "Sicherheit"
- Datenschutzerklärung (Web + Entwurf) um die neuen Maßnahmen ergänzt
- Neu: rechtliches/datenschutz-factsheet.md für Kooperations-Pitches
- EU-Verarbeitung geprüft: inference_geo bietet aktuell nur us/global,
  keine EU-Option (Haiku 4.5 gar nicht) — als Ausbaupfad dokumentiert

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
JUSNandClaude Fable 5 committed 2026-07-15 15:30:19 +02:00
1 parent 12ebf514fb
commit ad1c2bf76c
16 files changed
+639 -18

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
/**
* Schützt den App-Inhalt auf zwei Ebenen:
* 1. App-Sperre (optional, Einstellungen): Beim Öffnen und nach jedem
* Wechsel in den Hintergrund muss das Gerät entsperrt werden
* (Face ID / Touch ID / Geräte-Code).
* 2. Sichtschutz (immer): Im App-Umschalter wird der Inhalt verdeckt,
* damit Briefe dort nicht als Vorschau lesbar sind.
*
* Der Inhalt bleibt gemountet (Navigation geht nicht verloren) — die
* Schutzschichten legen sich als Overlay darüber.
*/
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { AppState, AppStateStatus, StyleSheet, Text, View } from 'react-native';
import * as LocalAuthentication from 'expo-local-authentication';
import { holeAppSperre } from '../services/storage';
import { GrossButton } from './GrossButton';
import { farben, schrift, abstand } from '../theme';
export function AppSchutz({ children }: { children: React.ReactNode }) {
const [entsperrt, setEntsperrt] = useState(false);
const [bereit, setBereit] = useState(false);
const [verdeckt, setVerdeckt] = useState(false);
const authLaeuft = useRef(false);
const entsperren = useCallback(async () => {
if (authLaeuft.current) return;
authLaeuft.current = true;
try {
const ergebnis = await LocalAuthentication.authenticateAsync({
promptMessage: 'BehördenKlar entsperren',
cancelLabel: 'Abbrechen',
});
if (ergebnis.success) setEntsperrt(true);
} finally {
authLaeuft.current = false;
}
}, []);
// Beim Start: Sperre-Einstellung prüfen und ggf. direkt entsperren lassen
useEffect(() => {
holeAppSperre().then((aktiv) => {
if (!aktiv) {
setEntsperrt(true);
} else {
entsperren();
}
setBereit(true);
});
}, [entsperren]);
// Hintergrund-Wechsel: Sichtschutz an/aus, Sperre ggf. neu scharf stellen.
// Die Einstellung wird hier frisch gelesen, damit ein Umschalten in den
// Einstellungen sofort wirkt.
useEffect(() => {
const abo = AppState.addEventListener('change', (status: AppStateStatus) => {
if (status === 'active') {
setVerdeckt(false);
} else {
setVerdeckt(true);
if (status === 'background') {
holeAppSperre().then((aktiv) => {
if (aktiv) setEntsperrt(false);
});
}
}
});
return () => abo.remove();
}, []);
if (!bereit) return <View style={styles.deckschicht} />;
return (
<View style={{ flex: 1 }}>
{children}
{!entsperrt && (
<View style={[StyleSheet.absoluteFill, styles.deckschicht, styles.sperrschirm]}>
<Text style={styles.symbol}>🔒</Text>
<Text style={styles.titel}>BehördenKlar ist gesperrt</Text>
<Text style={styles.text}>Ihre Briefe sind geschützt.</Text>
<GrossButton titel="Entsperren" onPress={entsperren} />
</View>
)}
{entsperrt && verdeckt && (
<View style={[StyleSheet.absoluteFill, styles.deckschicht, styles.sichtschutz]}>
<Text style={styles.symbol}>📬</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
deckschicht: { flex: 1, backgroundColor: farben.hintergrund },
sperrschirm: {
justifyContent: 'center',
padding: abstand.xl,
gap: abstand.m,
},
sichtschutz: { justifyContent: 'center', alignItems: 'center' },
symbol: { fontSize: 56, textAlign: 'center' },
titel: {
fontSize: schrift.titel,
fontWeight: '700',
color: farben.text,
textAlign: 'center',
},
text: {
fontSize: schrift.basis,
color: farben.textSekundaer,
textAlign: 'center',
marginBottom: abstand.m,
},
});