generated from knso/webklar-preview-template
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:
1 parent
12ebf514fb
commit
ad1c2bf76c
16 files changed
+639
-18
No files matched your search
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user