From 98201c78d2c76df94985f6d746b5ffd5a011f1c2 Mon Sep 17 00:00:00 2001 From: Steven Hernandez Date: Thu, 13 Aug 2026 15:39:58 +0200 Subject: [PATCH 1/2] feat: add ACC-14 app privacy and PIN lock --- docs/README.md | 2 +- docs/architektur/privacy-modus.md | 48 +- .../synchronisation-und-offline.md | 5 +- docs/architektur/ueberblick.md | 6 +- .../0012-app-vorschau-und-lokaler-pin-lock.md | 44 ++ docs/entscheidungen/README.md | 1 + .../daten-validierung-und-speicher.md | 4 +- docs/produkt/ablaeufe-und-zustaende.md | 8 +- docs/produkt/entwicklungsstand.md | 3 +- docs/produkt/funktionen.md | 1 + docs/produkt/ueberblick.md | 4 +- docs/referenz/quellcode-karte.md | 3 +- index.html | 35 +- package-lock.json | 15 + package.json | 1 + public/THIRD_PARTY_NOTICES.txt | 38 +- scripts/browser-smoke.mjs | 32 +- src/App.tsx | 2 + src/components/AppLockScreen.tsx | 179 ++++++ src/components/Icon.tsx | 8 +- src/components/PinManagementDialog.tsx | 198 +++++++ src/components/PinPad.test.tsx | 25 + src/components/PinPad.tsx | 148 +++++ src/components/PrivacyToggle.tsx | 8 +- src/components/SettingsDialog.tsx | 103 +++- src/data/FinanceDataProvider.tsx | 35 +- src/main.tsx | 4 +- src/privacy/PrivacyProvider.tsx | 289 +++++++++- src/privacy/appProtectionStore.test.ts | 90 +++ src/privacy/appProtectionStore.ts | 252 +++++++++ src/privacy/expressivePinShapes.test.ts | 21 + src/privacy/expressivePinShapes.ts | 29 + src/styles.css | 1 + src/styles/lockscreen.css | 527 ++++++++++++++++++ .../chromium/412-dark-info-dialog.png | Bin 104106 -> 112152 bytes .../chromium/412-dark-pin-lockscreen.png | Bin 0 -> 29401 bytes .../412-light-app-preview-protection.png | Bin 0 -> 16517 bytes .../412-light-disconnect-confirmation.png | Bin 110176 -> 111194 bytes .../chromium/412-light-info-dialog.png | Bin 103050 -> 110685 bytes .../chromium/412-light-pin-lockscreen.png | Bin 0 -> 28904 bytes tests/visual/finance-ui.spec.ts | 224 ++++++++ 41 files changed, 2324 insertions(+), 69 deletions(-) create mode 100644 docs/entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md create mode 100644 src/components/AppLockScreen.tsx create mode 100644 src/components/PinManagementDialog.tsx create mode 100644 src/components/PinPad.test.tsx create mode 100644 src/components/PinPad.tsx create mode 100644 src/privacy/appProtectionStore.test.ts create mode 100644 src/privacy/appProtectionStore.ts create mode 100644 src/privacy/expressivePinShapes.test.ts create mode 100644 src/privacy/expressivePinShapes.ts create mode 100644 src/styles/lockscreen.css create mode 100644 tests/visual/__screenshots__/chromium/412-dark-pin-lockscreen.png create mode 100644 tests/visual/__screenshots__/chromium/412-light-app-preview-protection.png create mode 100644 tests/visual/__screenshots__/chromium/412-light-pin-lockscreen.png diff --git a/docs/README.md b/docs/README.md index 8221c04..241317c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -88,7 +88,7 @@ Diese Seite ist der zentrale Index und damit die Single Source of Truth (SSOT) f ### Entscheidungen, Vorlagen und historische Pfade -- [ADR-Index](entscheidungen/README.md), [0001](entscheidungen/0001-google-sheets-als-datenquelle.md), [0002](entscheidungen/0002-versionierte-domaenengrenze-und-integer-cents.md), [0003](entscheidungen/0003-serverseitiger-google-zugriff-und-drive-file.md), [0004](entscheidungen/0004-single-user-sicherheitsmodell.md), [0005](entscheidungen/0005-last-known-good-und-offline.md), [0006](entscheidungen/0006-provider-selektoren-und-view-model.md), [0007](entscheidungen/0007-vite-pwa-und-vercel-functions.md), [0008](entscheidungen/0008-material-design-und-dynamische-farben.md), [0009](entscheidungen/0009-ereignisgesteuerte-aktualisierung.md), [0010](entscheidungen/0010-gehaltsbezogene-faelligkeitsprojektion.md), [0011](entscheidungen/0011-lokaler-privacy-modus.md) +- [ADR-Index](entscheidungen/README.md), [0001](entscheidungen/0001-google-sheets-als-datenquelle.md), [0002](entscheidungen/0002-versionierte-domaenengrenze-und-integer-cents.md), [0003](entscheidungen/0003-serverseitiger-google-zugriff-und-drive-file.md), [0004](entscheidungen/0004-single-user-sicherheitsmodell.md), [0005](entscheidungen/0005-last-known-good-und-offline.md), [0006](entscheidungen/0006-provider-selektoren-und-view-model.md), [0007](entscheidungen/0007-vite-pwa-und-vercel-functions.md), [0008](entscheidungen/0008-material-design-und-dynamische-farben.md), [0009](entscheidungen/0009-ereignisgesteuerte-aktualisierung.md), [0010](entscheidungen/0010-gehaltsbezogene-faelligkeitsprojektion.md), [0011](entscheidungen/0011-lokaler-privacy-modus.md), [0012](entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md) - [Dokumentationsseite](vorlagen/dokumentationsseite.md), [ADR](vorlagen/adr.md), [Fonts](fonts/README.md) - Historische Einstiegspunkte: [Designsystem](design-system.md), [Sicherheit und Datenfluss](security-and-data-flow.md), [Schema](finance-data-schema-v1.md), [Google-Setup](google-oauth-vercel-setup.md) diff --git a/docs/architektur/privacy-modus.md b/docs/architektur/privacy-modus.md index 3891e83..224a0eb 100644 --- a/docs/architektur/privacy-modus.md +++ b/docs/architektur/privacy-modus.md @@ -1,38 +1,48 @@ -# Privacy-Modus +# Privacy-Modus und App-Schutz -> **Zielgruppe:** Nutzer, Accessibility- und Frontend-Entwickler. -> **Zweck und Lernziel:** Wirkung, Persistenz und bewusste Sicherheitsgrenze des Privacy-Modus korrekt erklären. +> **Zielgruppe:** Nutzer, Accessibility-, Frontend- und Security-Entwickler. +> **Zweck und Lernziel:** Wirkung, Persistenz und bewusste Sicherheitsgrenzen der lokalen Sichtschutzfunktionen korrekt erklären. > **Voraussetzungen:** [Produktüberblick](../produkt/ueberblick.md) -> **Kanonisch für:** Lokale Privacy-Maskierung, Speicherformat und Tab-Synchronisierung. -> **Verwandte Dokumente:** [Frontend](frontend.md), [Synchronisation und Offline](synchronisation-und-offline.md), [ADR 0011](../entscheidungen/0011-lokaler-privacy-modus.md) +> **Kanonisch für:** Geldmaskierung, App-Vorschau-Schutz, lokaler PIN-Lock und Tab-Synchronisierung. +> **Verwandte Dokumente:** [Frontend](frontend.md), [Synchronisation und Offline](synchronisation-und-offline.md), [ADR 0011](../entscheidungen/0011-lokaler-privacy-modus.md), [ADR 0012](../entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md) ## Mentales Modell -Privacy ist ein schneller Sichtschutz gegen Shoulder Surfing, also beiläufiges Mitlesen. Der Umschalter lässt Geldwerte unkenntlich erscheinen und ersetzt zugängliche Geldtexte durch eine neutrale Beschreibung. Die zugrunde liegenden React-Daten bleiben unverändert. +Accura trennt drei lokale Schutzebenen: -## Umsetzung +- **Privacy-Modus:** maskiert Geldbeträge in der laufenden App gegen beiläufiges Mitlesen. +- **App-Vorschau schützen:** verdeckt die gesamte App nach `visibilitychange` zu `hidden` oder `pagehide`; nach der Rückkehr muss der Nutzer die Inhalte bewusst wieder anzeigen. +- **Mit PIN entsperren:** erweitert den App-Vorschau-Schutz um eine sechsstellige lokale PIN und sperrt zusätzlich jeden Kaltstart und Reload. -Vor dem ersten Render liest `initializePrivacyBeforeRender()` den String `true` aus `localStorage` unter `finance-privacy-v1` und setzt `data-privacy-mode="true"` am Dokument. `PrivacyProvider` stellt `isPrivacyMode`, `togglePrivacy` und `setPrivacyMode` per Context bereit. Ein `storage`-Listener übernimmt Änderungen anderer Tabs desselben Origins. Speicherfehler fallen sicher auf „aus“ beziehungsweise rein flüchtigen Zustand zurück. +Beide App-Schutz-Schalter liegen unter **Einstellungen → App-Schutz** und sind standardmäßig aus. Ein eingerichteter PIN-Lock hält den App-Vorschau-Schutz zwingend aktiv. Der manuelle Privacy-Modus bleibt davon unabhängig: Nach dem Entsperren gilt wieder genau dessen vorheriger Zustand. -`MoneyValue` kontrolliert sichtbare Darstellung und Accessibility-Text. CSS reagiert auf das Dokumentattribut. Die Einstellung ist geräte-/browserprofilbezogen, unabhängig von der Google-Sitzung und bleibt bei Logout sowie Disconnect erhalten. +## Umsetzung und Lebenszyklus + +Vor dem ersten React-Render lesen `index.html` und `src/main.tsx` die validierten lokalen Präferenzen. Bei einem PIN oder beschädigten App-Schutz-Daten wird `data-app-covered="true"` synchron gesetzt; CSS verbirgt die App-Shell, bevor vertrauliche Inhalte aufblitzen können. `PrivacyProvider` koordiniert Dokumentattribute, Lifecycle-Ereignisse und Tab-Synchronisierung. Eine neue oder geänderte PIN sperrt andere Tabs sofort; ein Recovery-Reset lädt sie neu, damit kein alter Finance-Zustand im Arbeitsspeicher offenbleibt. Während der Sperre ist die App-Shell unsichtbar, `inert` und `aria-hidden`; nur der modale Lockscreen bleibt fokussierbar. + +Der Lockscreen übernimmt eine einzelne flächige Hintergrundfarbe und alle weiteren Rollen aus dem aktiven Theme; er verwendet weder Verlauf noch Logo. Seine Anordnung orientiert sich an einem Android-PIN-Screen. Vor der Eingabe sind keine leeren PIN-Slots sichtbar. Jede eingegebene Ziffer erscheint aus der Mitte kurz als zufällig ausgewählte Material-3-Expressive-Form aus [`shape-morph`](https://github.com/Thereallo1026/shape-morph), morpht klar zum Kreis und landet bei 16 × 16 Pixeln. Reduced Motion zeigt den Kreis ohne Eingangsanimation; Forced Colors erhält sichtbare Begrenzungen und native Kontraste. + +Der bestehende Privacy-Modus liegt als String unter `finance-privacy-v1`. Der versionierte App-Schutz liegt unter `finance-app-protection-v1` und enthält nur Schalter, PIN-Verifier, Fehlversuchszähler und Sperrfrist. Die PIN selbst wird nie gespeichert: Web Crypto leitet mit PBKDF2-HMAC-SHA-256, zufälligem 128-Bit-Salt und 600.000 Iterationen einen 256-Bit-Verifier ab. Nach fünf Fehlversuchen beginnt eine persistierte, exponentiell steigende Wartezeit von 30 Sekunden bis höchstens 15 Minuten. + +## Vergessene PIN + +Der Reset bleibt ohne Netzwerk bewusst gesperrt. Online wird eine vorhandene Google-Verbindung serverseitig getrennt, die Sitzung zurückgesetzt und der lokale Finance-Cache gelöscht; erst danach entfernt Accura PIN und App-Schutz. Die Google-Sheets-Datei selbst bleibt unverändert. Schlägt ein Schritt fehl, bleibt die Sperre aktiv. Als äußerste lokale Alternative kann der Nutzer sämtliche Accura-Sitedaten über Browser- oder Android-Einstellungen löschen. ## Sicherheitsgrenze -Der Modus: +Die Funktionen reduzieren Shoulder Surfing und verdecken die App beim Hintergrundwechsel best effort. Eine Web-PWA kann jedoch kein natives Android-`FLAG_SECURE` setzen und deshalb weder Betriebssystem-Screenshots noch die Darstellung im App-Switcher auf jedem Gerät und Browser garantieren. -- maskiert sichtbare Geldbeträge und deren Accessibility-Texte; -- reduziert beiläufiges Mitlesen; -- verschlüsselt weder JavaScript-Arbeitsspeicher noch DOM-/React-Daten, IndexedDB, Netzwerkantworten oder Screenshots aus einem unmaskierten Zustand; -- versteckt nicht automatisch alle indirekten Finanzinformationen wie Namen, Diagrammformen oder Kategorien; -- ersetzt weder Gerätesperre, Browserprofil-Trennung noch Betriebssystemschutz. +Der lokale PIN ist eine Zugriffshürde innerhalb desselben Browserprofils, keine Verschlüsselung. Er schützt weder JavaScript-Arbeitsspeicher, DOM-/React-Daten, IndexedDB, Netzwerkantworten noch ein bereits kompromittiertes Gerät. Nutzer mit DevTools-, Dateisystem- oder Profilzugriff können lokale Daten lesen oder löschen. App-Schutz und Privacy ersetzen daher weder Gerätesperre, getrennte Browserprofile noch Betriebssystemschutz. ## Fehlerfälle und Accessibility -Blockiertes `localStorage` verhindert dauerhafte oder tabübergreifende Einstellung, nicht die aktuelle UI-Aktion. Eine Maskierung darf Screenreadern nicht weiterhin den Betrag vorlesen; darum muss jeder neue Geldwert die gemeinsame `MoneyValue`-Abstraktion verwenden. Reine CSS-Unschärfe ohne zugängliche Textanpassung wäre unzureichend. +Beschädigte App-Schutz-Daten fallen geschlossen auf den Recovery-Screen zurück. Kann eine Schutzänderung oder ein Fehlversuch nicht dauerhaft gespeichert werden, wird nicht entsperrt. Blockiertes `localStorage` verhindert die PIN-Einrichtung. Screenreader erhalten PIN-Länge und Fehlerstatus, niemals die eingegebenen Ziffern; die numerischen Tasten bleiben echte Buttons und die Eingabe ist zusätzlich per Tastatur bedienbar. + +Bei der Geldmaskierung kontrolliert `MoneyValue` sichtbare Darstellung und Accessibility-Text gemeinsam. Neue Geldausgaben müssen diese Abstraktion verwenden; reine CSS-Unschärfe würde zugängliche Texte weiter preisgeben. ## Begründung und Nachweis -Siehe [ADR 0011](../entscheidungen/0011-lokaler-privacy-modus.md). +Siehe [ADR 0011](../entscheidungen/0011-lokaler-privacy-modus.md) und [ADR 0012](../entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md). -- Implementierung: [src/privacy/PrivacyProvider.tsx](../../src/privacy/PrivacyProvider.tsx), [src/privacy/privacyStore.ts](../../src/privacy/privacyStore.ts), [src/components/MoneyValue.tsx](../../src/components/MoneyValue.tsx), [src/components/PrivacyToggle.tsx](../../src/components/PrivacyToggle.tsx) -- Tests: [src/privacy/privacy.test.tsx](../../src/privacy/privacy.test.tsx), [src/branding.test.ts](../../src/branding.test.ts) +- Implementierung: [src/privacy/PrivacyProvider.tsx](../../src/privacy/PrivacyProvider.tsx), [src/privacy/privacyStore.ts](../../src/privacy/privacyStore.ts), [src/privacy/appProtectionStore.ts](../../src/privacy/appProtectionStore.ts), [src/components/AppLockScreen.tsx](../../src/components/AppLockScreen.tsx), [src/components/PinManagementDialog.tsx](../../src/components/PinManagementDialog.tsx), [src/components/MoneyValue.tsx](../../src/components/MoneyValue.tsx) +- Tests: [src/privacy/privacy.test.tsx](../../src/privacy/privacy.test.tsx), [src/privacy/appProtectionStore.test.ts](../../src/privacy/appProtectionStore.test.ts), [src/privacy/expressivePinShapes.test.ts](../../src/privacy/expressivePinShapes.test.ts), [tests/visual/finance-ui.spec.ts](../../tests/visual/finance-ui.spec.ts), [scripts/browser-smoke.mjs](../../scripts/browser-smoke.mjs) diff --git a/docs/architektur/synchronisation-und-offline.md b/docs/architektur/synchronisation-und-offline.md index 16dbcd7..7a158df 100644 --- a/docs/architektur/synchronisation-und-offline.md +++ b/docs/architektur/synchronisation-und-offline.md @@ -32,12 +32,13 @@ flowchart TB AP[localStorage finance-appearance-v1\nbis Reset/Browserloeschung] WP[(IndexedDB finance-appearance-v1\n0 oder 1 WebP-Vorschau)] PR[localStorage finance-privacy-v1\nbis Aenderung/Browserloeschung] + LK[localStorage finance-app-protection-v1\nbis Reset/Browserloeschung] SV[sessionStorage finance-screen-visits-v1\nbis Tab-Ende] SW[(Service-Worker-Cache\nversionierte App-Shell)] end ``` -Implementierung und Tests: [api/_lib/repository.ts](../../api/_lib/repository.ts), [api/_lib/security.ts](../../api/_lib/security.ts), [src/data/financeCache.ts](../../src/data/financeCache.ts), [src/appearance/wallpaperStore.ts](../../src/appearance/wallpaperStore.ts), [src/privacy/privacyStore.ts](../../src/privacy/privacyStore.ts), [scripts/offline-smoke.mjs](../../scripts/offline-smoke.mjs). +Implementierung und Tests: [api/_lib/repository.ts](../../api/_lib/repository.ts), [api/_lib/security.ts](../../api/_lib/security.ts), [src/data/financeCache.ts](../../src/data/financeCache.ts), [src/appearance/wallpaperStore.ts](../../src/appearance/wallpaperStore.ts), [src/privacy/privacyStore.ts](../../src/privacy/privacyStore.ts), [src/privacy/appProtectionStore.ts](../../src/privacy/appProtectionStore.ts), [scripts/offline-smoke.mjs](../../scripts/offline-smoke.mjs). ## Service-Worker-Grenze @@ -45,7 +46,7 @@ Workbox precacht statische HTML-, JavaScript-, CSS-, SVG-, PNG- und WOFF2-Artefa ## Fehler und Sicherheitsannahmen -Wenn IndexedDB nicht verfügbar ist, funktioniert Online-Nutzung weiter, aber kein fachlicher Offline-Start. Ein Last-known-good-Snapshot kann vertrauliche Finanzdaten enthalten und ist nicht verschlüsselt. Browserbereinigung oder Speicherdruck können ihn entfernen. Logout lässt ihn bewusst für späteren Offline-/Wiederanmeldestart bestehen; Disconnect löscht ihn nur auf dem aktuellen Gerät. +Wenn IndexedDB nicht verfügbar ist, funktioniert Online-Nutzung weiter, aber kein fachlicher Offline-Start. Ein Last-known-good-Snapshot kann vertrauliche Finanzdaten enthalten und ist nicht verschlüsselt. Browserbereinigung oder Speicherdruck können ihn entfernen. Logout lässt ihn bewusst für späteren Offline-/Wiederanmeldestart bestehen; Disconnect löscht ihn nur auf dem aktuellen Gerät. Ein vergessener PIN wird nur online zurückgesetzt und löscht zuerst Verbindung, Sitzung und diesen Finance-Cache; ohne bestätigte Bereinigung bleibt die Sperre aktiv. ## Begründung und Nachweis diff --git a/docs/architektur/ueberblick.md b/docs/architektur/ueberblick.md index 24f3cfb..0c0b256 100644 --- a/docs/architektur/ueberblick.md +++ b/docs/architektur/ueberblick.md @@ -21,7 +21,7 @@ flowchart LR G -->|zehn Tabellenbereiche| V V -->|FinanceDataV1| B B -->|Last-known-good| I[(IndexedDB)] - B -->|Appearance / Privacy| L[(localStorage)] + B -->|Appearance / Privacy / App-Schutz| L[(localStorage)] subgraph Geraet[Vertrauensbereich: Gerät und Browserprofil] B @@ -39,7 +39,7 @@ Implementierung und Tests: [src/main.tsx](../../src/main.tsx), [api/_lib/http.ts ## Startvorgang 1. `index.html` stellt Root-Element, Manifest und frühe Theme-Metadaten bereit. -2. `src/main.tsx` registriert den Service Worker und liest Appearance sowie Privacy vor dem ersten React-Render, damit kein sichtbarer Moduswechsel aufblitzt. +2. `src/main.tsx` registriert den Service Worker und liest Appearance, Privacy und App-Schutz vor dem ersten React-Render, damit weder Theme noch eine konfigurierte Sperre sichtbar nachladen. 3. React mountet unter `StrictMode` die Provider in der Reihenfolge Privacy → Appearance → FinanceData. 4. `FinanceDataProvider` lädt parallel fachlich zuerst den Cache und prüft danach die Sitzung. Eine vorhandene Auswahl löst einen Sync aus. 5. `App` zeigt eine Connection-State-Seite oder die vier Ziele. Nur die Übersicht ist initial geladen; weitere Ziele werden lazy importiert. @@ -54,4 +54,4 @@ Die Gründe sind in [ADRs](../entscheidungen/README.md) festgehalten. Besonders ## Grenzen und Sicherheitsannahmen -Das Modell setzt ein vertrauenswürdiges Betreiberkonto, korrekte Secrets, HTTPS sowie ein geschütztes Endgerät/Browserprofil voraus. Lokale Finance-Daten sind nicht durch Privacy oder Appearance verschlüsselt. Es gibt keine Mandantentrennung, weil nur eine Identität erlaubt ist. +Das Modell setzt ein vertrauenswürdiges Betreiberkonto, korrekte Secrets, HTTPS sowie ein geschütztes Endgerät/Browserprofil voraus. Lokale Finance-Daten sind weder durch Privacy, App-Schutz noch Appearance verschlüsselt. Es gibt keine Mandantentrennung, weil nur eine Identität erlaubt ist. diff --git a/docs/entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md b/docs/entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md new file mode 100644 index 0000000..33f76a8 --- /dev/null +++ b/docs/entscheidungen/0012-app-vorschau-und-lokaler-pin-lock.md @@ -0,0 +1,44 @@ +# ADR 0012: App-Vorschau und lokaler PIN-Lock + +> **Zielgruppe:** Produkt-, Frontend- und Security-Entwickler. +> **Zweck und Lernziel:** Lifecycle-Sichtschutz, lokale PIN-Prüfung und Recovery-Grenze begründen. +> **Voraussetzungen:** [Privacy-Modus und App-Schutz](../architektur/privacy-modus.md) +> **Kanonisch für:** Begründung des optionalen App-Vorschau-Schutzes und PIN-Locks. +> **Verwandte Dokumente:** [Backend und Sicherheit](../architektur/backend-und-sicherheit.md), [ADR-Index](README.md) + +- **Status:** Angenommen + +## Kontext + +Finanzdaten können in der Betriebssystem-App-Vorschau oder unmittelbar nach der Rückkehr aus dem Hintergrund sichtbar werden. Nutzer sollen diesen Übergang optional verdecken und den erneuten Zugriff auf Wunsch mit einer PIN erschweren können, ohne dass eine Online-Anmeldung für jeden normalen Unlock nötig wird. + +## Entscheidung + +Accura bietet zwei standardmäßig deaktivierte Einstellungen: einen Lifecycle-gesteuerten App-Vorschau-Schutz und einen darauf aufbauenden sechsstelligen PIN-Lock. `visibilitychange` zu `hidden` und `pagehide` verdecken die App synchron; der reine Sichtschutz verlangt eine bewusste Freigabe. Ein PIN sperrt zusätzlich Kaltstart und Reload. + +Gespeichert wird ausschließlich ein versionierter PBKDF2-HMAC-SHA-256-Verifier mit zufälligem Salt, 600.000 Iterationen, Fehlversuchszähler und exponentieller Sperrfrist. PIN und Finance-Daten werden nicht gemeinsam verschlüsselt. Eine vergessene PIN kann nur online zurückgesetzt werden: vorhandene Google-Verbindung und Sitzung werden bereinigt, der lokale Finance-Cache wird gelöscht und erst danach fällt die lokale Sperre. Die Google-Sheets-Datei wird nicht verändert. + +Der Lockscreen verwendet eine einzelne flächige Theme-Hintergrundfarbe ohne Logo und eine Android-orientierte Ziffernanordnung. Noch nicht eingegebene PIN-Stellen bleiben unsichtbar. Neue Stellen erscheinen aus der Mitte als zufällige Material-3-Expressive-Formen der MIT-lizenzierten Bibliothek [`shape-morph`](https://github.com/Thereallo1026/shape-morph), morphen kurz zum Kreis und enden bei 16 × 16 Pixeln; Reduced Motion und Forced Colors besitzen explizite Fallbacks. + +## Begründung + +Die Trennung lässt Nutzer zwischen schneller Hintergrundabdeckung und einer stärkeren lokalen Zugriffshürde wählen. Frühe Dokumentattribute vermeiden einen sichtbaren Daten-Flash. Ein abgeleiteter Verifier verhindert Klartext-PINs; persistierte Wartezeiten erschweren triviales Durchprobieren. Die Recovery-Reihenfolge verhindert, dass das Entfernen der Sperre einen weiterhin lokal verfügbaren Finance-Snapshot offenlegt. + +## Erwogene Alternativen + +Erwogen wurden ein verpflichtender PIN, biometrische WebAuthn-Anmeldung, Verschlüsselung des gesamten Finance-Caches, ausschließliches CSS-Blur und ein Offline-Reset ohne Datenlöschung. Sie verändern Onboarding, Schlüsselverwaltung oder Sicherheitsmodell deutlich beziehungsweise würden Daten weiterhin zugänglich machen. Native Android-Screenshot-Flags sind für die installierbare Web-PWA nicht verlässlich verfügbar. + +## Konsequenzen + +### Positiv + +Optionaler Schutz ohne Server-Roundtrip beim normalen Entsperren, konsistente Theme-/Accessibility-Ausgabe, kein Klartext-PIN und fail-closed Recovery. + +### Negativ + +Best-effort-App-Switcher-Schutz statt Betriebssystemgarantie; keine Verschlüsselung oder Geräteeigentümerprüfung. Browserdatenlöschung entfernt die Sperre zusammen mit lokalen Daten. PBKDF2 verursacht bewusst messbare Rechenzeit bei Einrichtung und Prüfung. + +## Implementierung und Tests + +- Implementierung: [src/privacy/appProtectionStore.ts](../../src/privacy/appProtectionStore.ts), [src/privacy/PrivacyProvider.tsx](../../src/privacy/PrivacyProvider.tsx), [src/components/AppLockScreen.tsx](../../src/components/AppLockScreen.tsx), [src/components/PinManagementDialog.tsx](../../src/components/PinManagementDialog.tsx), [src/data/FinanceDataProvider.tsx](../../src/data/FinanceDataProvider.tsx) +- Tests: [src/privacy/appProtectionStore.test.ts](../../src/privacy/appProtectionStore.test.ts), [src/components/PinPad.test.tsx](../../src/components/PinPad.test.tsx), [tests/visual/finance-ui.spec.ts](../../tests/visual/finance-ui.spec.ts), [scripts/browser-smoke.mjs](../../scripts/browser-smoke.mjs) diff --git a/docs/entscheidungen/README.md b/docs/entscheidungen/README.md index c70df83..3a9d638 100644 --- a/docs/entscheidungen/README.md +++ b/docs/entscheidungen/README.md @@ -21,3 +21,4 @@ Zulässige Statuswerte sind `Vorgeschlagen`, `Angenommen`, `Ersetzt` und `Verwor | [0009](0009-ereignisgesteuerte-aktualisierung.md) | Ereignisgesteuerte Aktualisierung | Angenommen | | [0010](0010-gehaltsbezogene-faelligkeitsprojektion.md) | Gehaltsbezogene Fälligkeitsprojektion | Angenommen | | [0011](0011-lokaler-privacy-modus.md) | Lokaler Privacy-Modus | Angenommen | +| [0012](0012-app-vorschau-und-lokaler-pin-lock.md) | App-Vorschau und lokaler PIN-Lock | Angenommen | diff --git a/docs/grundlagen/daten-validierung-und-speicher.md b/docs/grundlagen/daten-validierung-und-speicher.md index d36ba44..75ee99b 100644 --- a/docs/grundlagen/daten-validierung-und-speicher.md +++ b/docs/grundlagen/daten-validierung-und-speicher.md @@ -18,7 +18,7 @@ Jede Systemgrenze liefert zunächst unbekannte Daten: Google Sheets, HTTP-Antwor | --- | --- | --- | | signiertes `HttpOnly`-Cookie | Sitzung | Browser sendet es same-origin; JavaScript kann es nicht lesen; Logout/Disconnect löschen es | | kurzlebiges OAuth-Cookie | State, Nonce, PKCE-Verifier | etwa zehn Minuten, nur während Anmeldung | -| `localStorage` | kleine Geräteeinstellungen | Appearance und Privacy; bleibt bei Logout/Disconnect | +| `localStorage` | kleine Geräteeinstellungen | Appearance, Privacy und versionierter App-Schutz/PIN-Verifier; bleibt bei Logout/Disconnect | | `sessionStorage` | Tab-Sitzung | besuchte Screens für einmalige Entrance-Motion | | IndexedDB | strukturierte größere lokale Daten | ein Finance-Snapshot und optional eine reduzierte Wallpaper-Vorschau in getrennten Datenbanken | | Service-Worker-Cache | statische App-Shell | keine `/api/*`-Antworten | @@ -34,6 +34,6 @@ Browserdaten sind nicht automatisch verschlüsselt und können durch Gerätezugr - HTTP-Laufzeitprüfung: [src/data/financeApi.ts](../../src/data/financeApi.ts) - Finance-Cache: [src/data/financeCache.ts](../../src/data/financeCache.ts), [Cache-Tests](../../src/data/financeCache.test.ts) - Appearance-Store: [src/appearance/appearanceStore.ts](../../src/appearance/appearanceStore.ts) -- Privacy-Store: [src/privacy/privacyStore.ts](../../src/privacy/privacyStore.ts) +- Privacy- und App-Schutz-Stores: [src/privacy/privacyStore.ts](../../src/privacy/privacyStore.ts), [src/privacy/appProtectionStore.ts](../../src/privacy/appProtectionStore.ts) Primärquellen: [MDN IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API), [MDN Web Storage](https://developer.mozilla.org/docs/Web/API/Web_Storage_API), [MDN Cookies](https://developer.mozilla.org/docs/Web/HTTP/Cookies). diff --git a/docs/produkt/ablaeufe-und-zustaende.md b/docs/produkt/ablaeufe-und-zustaende.md index a0abc5b..8ede115 100644 --- a/docs/produkt/ablaeufe-und-zustaende.md +++ b/docs/produkt/ablaeufe-und-zustaende.md @@ -25,6 +25,10 @@ | Netzwerk-/Serverfehler mit Daten | Daten bleiben sichtbar und als veraltet markiert | später erneut laden | | Netzwerk-/Serverfehler ohne Daten | zentrale Fehler-/Einrichtungsansicht | Ursache beheben | | Privacy aus/ein | Geldbeträge sichtbar/maskiert | Umschalter betätigen | +| App-Vorschau geschützt | gesamte App nach Hintergrundwechsel verdeckt | bewusst Inhalte anzeigen | +| PIN-Lock | Start, Reload oder Hintergrundrückkehr gesperrt | sechsstellige PIN eingeben | +| PIN-Wartezeit | zu viele Fehlversuche; Sperre bleibt aktiv | angezeigte Wartezeit abwarten | +| PIN-Recovery offline/fehlgeschlagen | Sperre und lokale Daten bleiben erhalten | Netzwerk wiederherstellen/erneut versuchen | | Appearance-Entwurf | Vorschau im Dialog, noch nicht gespeichert | anwenden oder abbrechen | | Appearance angewandt | Tokens und optionale Vorschau lokal gespeichert | weiter nutzen/resetten | | Bild entfernt | Vorschau aus IndexedDB gelöscht; Nicht-Bild-Palette aktiv | neues Bild wählen oder Palette nutzen | @@ -65,7 +69,7 @@ Bei Anmeldung oder erneuter Google-Verbindung sendet der Client nur einen der vi ## Abmelden und Trennen -Abmelden löscht nur das signierte Session-Cookie im Browser. Trennen widerruft nach Möglichkeit das Google-Token und löscht selbst bei fehlgeschlagener Widerruf-Anfrage den Verbindungsdatensatz; im Client wird danach der Finance-Cache gelöscht. Appearance und Privacy sind unabhängige Geräteeinstellungen und bleiben in beiden Fällen erhalten. +Abmelden löscht nur das signierte Session-Cookie im Browser. Trennen widerruft nach Möglichkeit das Google-Token und löscht selbst bei fehlgeschlagener Widerruf-Anfrage den Verbindungsdatensatz; im Client wird danach der Finance-Cache gelöscht. Appearance, Privacy und App-Schutz sind unabhängige Geräteeinstellungen und bleiben in beiden Fällen erhalten. Nur die ausdrücklich bestätigte Recovery einer vergessenen PIN entfernt den App-Schutz nach erfolgreicher Online-Bereinigung. ## Appearance-Transaktion @@ -74,6 +78,6 @@ Der Farben-Dialog hält Modus, Quelle, Palette und Bild zunächst als Entwurf. * ## Implementierung und Tests - Reducer und Übergänge: [src/data/FinanceDataProvider.tsx](../../src/data/FinanceDataProvider.tsx) -- Privacy-Tabsynchronisierung: [src/privacy/PrivacyProvider.tsx](../../src/privacy/PrivacyProvider.tsx) +- Privacy-, App-Schutz- und Lifecycle-Zustand: [src/privacy/PrivacyProvider.tsx](../../src/privacy/PrivacyProvider.tsx) - Appearance-Dialog: [src/components/ColorThemeDialog.tsx](../../src/components/ColorThemeDialog.tsx) - Zustands-Golden-Screens: [tests/visual/finance-ui.spec.ts](../../tests/visual/finance-ui.spec.ts) diff --git a/docs/produkt/entwicklungsstand.md b/docs/produkt/entwicklungsstand.md index 6803d7a..7a1d0e6 100644 --- a/docs/produkt/entwicklungsstand.md +++ b/docs/produkt/entwicklungsstand.md @@ -17,6 +17,7 @@ - Kontrollierter PWA-Versionswechsel mit verständlichem „Jetzt neu laden“/„Später“-Hinweis, stabilem Installationsmanifest und automatisierten Android-orientierten Icon-/Systemfarben-Verträgen. - Appearance mit Systemmodus, Hell/Dunkel, Browser-Akzent, neun Presets, lokaler Bildanalyse im Worker und lokaler WebP-Vorschau. - Lokaler Privacy-Modus einschließlich Tabsynchronisierung und Maskierung von sichtbaren sowie zugänglichen Geldtexten. +- Optionaler App-Vorschau-Schutz und lokaler sechsstelliger PIN-Lock mit Android-orientiertem, thematisiertem Lockscreen, Expressive-PIN-Formen, Fehlversuchs-Wartezeit und fail-closed Recovery. - Wiederverwendbare MD3-Komponenten, Responsive/Reflow, Reduced Motion, Forced Colors, Fokusmanagement und lokale Google-Sans-Flex-Schrift. - GitHub-CI für Lint, Unit-Tests, Build und Smoke; aktuell 179 Vitest-Tests plus ein Node-ESM-Test sowie PWA-, Offline-, Golden- und Axe-Prüfungen. @@ -28,7 +29,7 @@ Die genaue Commit-Historie bleibt in Git; diese Seite ist kein tägliches Journa ## Bekannte Abdeckungslücken -Demnächst und Privacy besitzen Unit-/Komponententests, sind aber noch nicht vollständig in Golden-Screenshots und Axe-Szenarien abgebildet. Reale Produktionsabläufe mit persönlichen externen Diensten können im Repository nicht automatisiert bewiesen werden und benötigen eine Eigentümer-Abnahme. Androids tatsächlich gerenderter Installationsdialog, Launcher und Splash liegen ebenfalls außerhalb der gewählten Desktop-Chromium-Automation; Manifest, Installierbarkeit, Icon-Pixelverträge und Worker-Update sind automatisiert abgedeckt. +Reale Produktionsabläufe mit persönlichen externen Diensten können im Repository nicht automatisiert bewiesen werden und benötigen eine Eigentümer-Abnahme. Androids tatsächlich gerenderter Installationsdialog, Launcher, Splash und App-Switcher liegen ebenfalls außerhalb der gewählten Desktop-Chromium-Automation; Manifest, Installierbarkeit, Icon-Pixelverträge, Worker-Update und der Web-Lockscreen sind automatisiert abgedeckt. ## Nachweis diff --git a/docs/produkt/funktionen.md b/docs/produkt/funktionen.md index 7f77ebe..53b8b95 100644 --- a/docs/produkt/funktionen.md +++ b/docs/produkt/funktionen.md @@ -34,6 +34,7 @@ Schulden stellt Ablösesumme, planmäßige Gesamtkosten, daraus abgeleitete Mehr - **Google-Verbindung trennen:** versucht den Google-Grant zu widerrufen, löscht die Postgres-Verbindung, beendet die Sitzung und entfernt den Finance-Cache auf diesem Gerät. - **Darstellung:** System-, Hell- und Dunkelmodus; Browser-/Systemfarbe, kuratierte Presets oder lokal analysiertes Bild. Entwürfe werden erst durch Anwenden dauerhaft. - **Privacy:** maskiert oder zeigt Geldbeträge; die Einstellung bleibt lokal über Logout und Disconnect hinweg erhalten und wird zwischen Tabs synchronisiert. +- **App-Schutz:** verdeckt Accura optional nach einem Hintergrundwechsel. Eine zusätzliche sechsstellige lokale PIN sperrt außerdem Start und Reload; beide Schalter liegen in den Einstellungen. - **Begrüßung:** Die Übersicht wählt abhängig von der lokalen Uhr „Guten Morgen“, „Guten Tag“ oder „Guten Abend“ und kombiniert dies mit dem `accura`-Branding. ## Offline-Nutzung diff --git a/docs/produkt/ueberblick.md b/docs/produkt/ueberblick.md index e6aa001..e93f504 100644 --- a/docs/produkt/ueberblick.md +++ b/docs/produkt/ueberblick.md @@ -59,7 +59,7 @@ Ein erfolgreich validierter Stand wird auf dem Gerät in IndexedDB gespeichert. Refresh-Token, Google-Client-Secret, Datenbank-URL, Token-Schlüssel und Session-Secret bleiben auf dem Server. Der Browser erhält nur die Finanzantwort sowie beim bewussten Öffnen des Pickers kurzzeitig ein Zugriffstoken. Details und Vertrauensgrenzen stehen unter [Backend und Sicherheit](../architektur/backend-und-sicherheit.md). -Der [Privacy-Modus](../architektur/privacy-modus.md) maskiert Geldbeträge in sichtbarer UI und Accessibility-Texten. Er schützt gegen beiläufiges Mitlesen, verschlüsselt jedoch weder Arbeitsspeicher noch IndexedDB und ersetzt keine Gerätesperre oder getrennte Browserprofile. +Der [Privacy-Modus und App-Schutz](../architektur/privacy-modus.md) maskiert wahlweise Geldbeträge und kann die gesamte App nach einem Hintergrundwechsel verdecken oder mit einer lokalen PIN sperren. Diese Funktionen reduzieren beiläufiges Mitlesen, verschlüsseln jedoch weder Arbeitsspeicher noch IndexedDB und ersetzen keine Gerätesperre oder getrennte Browserprofile. ## Strategische Nicht-Ziele @@ -80,7 +80,7 @@ Diese Grenzen sind strategisch. Eine zusätzliche Funktion ist nur dann sinnvoll - kein Bearbeiten der Google-Tabelle durch die App; - keine Bankanbindung, Überweisung oder automatische Kategorisierung im aktuellen Produkt; - kein garantierter Echtzeitstand und kein Hintergrund-Polling; -- keine Verschlüsselung lokaler Finance-Daten durch den Privacy-Modus; +- keine Verschlüsselung lokaler Finance-Daten durch Privacy- oder App-Schutz; - keine produktive persönliche Fixture im Repository. ## Produkt, Vision und Markt getrennt halten diff --git a/docs/referenz/quellcode-karte.md b/docs/referenz/quellcode-karte.md index a844c0c..c625ca4 100644 --- a/docs/referenz/quellcode-karte.md +++ b/docs/referenz/quellcode-karte.md @@ -16,7 +16,7 @@ | `src/data/` | Browser-API, Picker, Finance-Provider, IndexedDB-Finance-Cache | | `src/finance/` | Schemaheader, Parser, Laufzeitschema, Typen, Selektoren, Upcoming, View-Model | | `src/appearance/` | Präferenz, Paletten, Tokens, Worker und Wallpaper-IndexedDB | -| `src/privacy/` | lokaler Privacy-Store und Context | +| `src/privacy/` | Geldmaskierung, App-Schutz-/PIN-Store, Expressive-PIN-Formen und gemeinsamer Context | | `src/navigation/` | kanonische App-Pfade, History-/Startauflösung und letzte Destination | | `src/design/` | zentrale CSS-Tokens, Schriftimport, Diagramm-/Motion-Helfer | | `src/styles/` | Basis, Shell, Primitives, Screens, Zustände, Responsive Regeln | @@ -41,4 +41,5 @@ Für einen Geldwert beginnt die Spur in einem Header aus [src/finance/schema.ts] - Neue Geldanzeige: View-Model/Formatierung und `MoneyValue` verwenden, damit Privacy greift. - Neue API-Aktion: Methode, Auth, Origin/CSRF, Zod-Grenze und öffentliche Fehlerform berücksichtigen. - Neue Appearance-Eigenschaft: versioniertes Speicherparsing, Pre-Render und Cross-Tab-Verhalten berücksichtigen. +- Neue App-Schutz-Eigenschaft: fail-closed Parsing, Pre-Render-Abdeckung, Lifecycle und Recovery gemeinsam prüfen. - Neue Dokumentseite: im [zentralen Index](../README.md) und in der Themenmatrix aufnehmen. diff --git a/index.html b/index.html index 5dcca18..f734970 100644 --- a/index.html +++ b/index.html @@ -33,6 +33,37 @@