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

+109 -2
View File
@@ -4,16 +4,30 @@
* im Produktionsmodus läuft der KI-Zugang über den Backend-Proxy.
*/
import React, { useEffect, useState } from 'react';
import { Alert, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
import {
Alert,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import * as Notifications from 'expo-notifications';
import * as LocalAuthentication from 'expo-local-authentication';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { RootStackParamList } from '../types';
import {
BONUS_ANALYSEN,
holeApiKey,
holeAppSperre,
holeAutoLoeschTage,
holeBonusAktiv,
loeseBonusCodeEin,
loescheAlleDaten,
setzeAppSperre,
setzeAutoLoeschTage,
speichereApiKey,
} from '../services/storage';
import { NUTZT_PROXY } from '../services/claudeClient';
@@ -30,12 +44,45 @@ export function EinstellungenScreen({ navigation }: Props) {
const [gespeichert, setGespeichert] = useState(false);
const [bonusCode, setBonusCode] = useState('');
const [bonusAktiv, setBonusAktiv] = useState(false);
const [sperreAktiv, setSperreAktiv] = useState(false);
const [autoTage, setAutoTage] = useState(0);
useEffect(() => {
holeApiKey().then((k) => setKeyVorhanden(!!k));
holeBonusAktiv().then(setBonusAktiv);
holeAppSperre().then(setSperreAktiv);
holeAutoLoeschTage().then(setAutoTage);
}, []);
/** App-Sperre umschalten — beide Richtungen erfordern eine Authentifizierung. */
const sperreUmschalten = async (an: boolean) => {
if (an) {
const stufe = await LocalAuthentication.getEnrolledLevelAsync();
if (stufe === LocalAuthentication.SecurityLevel.NONE) {
Alert.alert(
'Keine Geräte-Sperre eingerichtet',
'Bitte richten Sie zuerst in den Geräte-Einstellungen einen Code, Face ID oder Fingerabdruck ein.'
);
return;
}
}
const ergebnis = await LocalAuthentication.authenticateAsync({
promptMessage: an ? 'App-Sperre aktivieren' : 'App-Sperre deaktivieren',
cancelLabel: 'Abbrechen',
});
if (!ergebnis.success) return;
await setzeAppSperre(an);
setSperreAktiv(an);
};
/** Aufbewahrungsdauer setzen und sofort anwenden. */
const autoTageSetzen = async (tage: number) => {
await setzeAutoLoeschTage(tage);
setAutoTage(tage);
// Sofort anwenden statt erst beim nächsten App-Start
await useAppStore.getState().initialisiere();
};
const codeEinloesen = async () => {
const erfolg = await loeseBonusCodeEin(bonusCode);
if (erfolg) {
@@ -155,13 +202,57 @@ export function EinstellungenScreen({ navigation }: Props) {
<View style={styles.trenner} />
<Text style={styles.abschnittTitel}>Sicherheit</Text>
<View style={styles.schalterZeile}>
<View style={{ flex: 1 }}>
<Text style={styles.text}>App-Sperre</Text>
<Text style={styles.hinweis}>
Face ID / Geräte-Code beim Öffnen der App — schützt Ihre Briefe,
falls jemand Ihr Handy in die Hand bekommt.
</Text>
</View>
<Switch
value={sperreAktiv}
onValueChange={sperreUmschalten}
accessibilityLabel="App-Sperre umschalten"
/>
</View>
<Text style={[styles.text, { marginTop: abstand.m, fontWeight: '700' }]}>
Briefe automatisch löschen
</Text>
<Text style={styles.hinweis}>
Alte Briefe werden dann von selbst vom Gerät entfernt.
</Text>
{[
{ tage: 0, label: 'Nie (Standard)' },
{ tage: 30, label: 'Nach 30 Tagen' },
{ tage: 90, label: 'Nach 90 Tagen' },
].map(({ tage, label }) => (
<TouchableOpacity
key={tage}
style={[styles.option, autoTage === tage && styles.optionAktiv]}
onPress={() => autoTageSetzen(tage)}
accessibilityRole="radio"
accessibilityState={{ selected: autoTage === tage }}
>
<Text style={[styles.text, autoTage === tage && styles.optionAktivText]}>
{autoTage === tage ? '● ' : '○ '}
{label}
</Text>
</TouchableOpacity>
))}
<View style={styles.trenner} />
<Text style={styles.abschnittTitel}>Datenschutz</Text>
<View style={styles.karte}>
<Text style={styles.text}>
• Brieffotos werden nur zur Analyse an den KI-Dienst Anthropic (USA)
gesendet und dort nach spätestens 30 Tagen gelöscht. Sie werden nicht
zum Training der KI verwendet.{'\n\n'}
• Alle Ergebnisse liegen ausschließlich auf Ihrem Gerät.{'\n\n'}
• Alle Ergebnisse liegen ausschließlich auf Ihrem Gerät — verschlüsselt
gespeichert (AES-256).{'\n\n'}
• Diese App ersetzt keine Rechtsberatung.
</Text>
</View>
@@ -202,4 +293,20 @@ const styles = StyleSheet.create({
},
karte: { backgroundColor: farben.flaeche, borderRadius: 14, padding: abstand.m },
trenner: { height: 1, backgroundColor: farben.rand, marginVertical: abstand.l },
schalterZeile: {
flexDirection: 'row',
alignItems: 'center',
gap: abstand.m,
},
option: {
borderWidth: 1,
borderColor: farben.rand,
borderRadius: 12,
padding: abstand.s,
minHeight: 56,
justifyContent: 'center',
marginTop: abstand.s,
},
optionAktiv: { borderColor: farben.primaer, backgroundColor: farben.flaeche },
optionAktivText: { fontWeight: '700', color: farben.primaer },
});