Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
50 changes: 30 additions & 20 deletions docs/architektur/privacy-modus.md
Original file line number Diff line number Diff line change
@@ -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 über eine auf 15 Sekunden begrenzte, abbrechbare Anfrage serverseitig getrennt, die Sitzung zurückgesetzt und der lokale Finance-Cache gelöscht; erst danach entfernt Accura PIN und App-Schutz. Vor der Cache-Löschung rotiert Accura eine profilweite Cache-Generation. Noch laufende Antworten aus anderen Tabs dürfen deshalb keinen vor dem Reset gestarteten Finance-Snapshot erneut speichern. 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.

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.

- 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.
## Fehlerfälle und Barrierefreiheit

## Fehlerfälle und Accessibility
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.

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.
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)
6 changes: 4 additions & 2 deletions docs/architektur/synchronisation-und-offline.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,22 @@ 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]
CG[localStorage finance-cache-generation-v1\nzufaellige Cache-Invalidierung ohne Fachdaten]
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

Workbox precacht statische HTML-, JavaScript-, CSS-, SVG-, PNG- und WOFF2-Artefakte. Navigation fällt auf `index.html` zurück. `/api/*` ist von diesem Fallback ausgeschlossen und verwendet `NetworkOnly`. Der fachliche Cache liegt separat in `finance-overview`, Object Store `last-good`, Schlüssel `finance-data-v1`, und wird beim Lesen erneut mit Zod validiert.

## 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. Die Recovery rotiert davor die profilweite Cache-Generation. Jeder Sync darf nur mit der bei seinem Start gelesenen Generation persistieren, sodass verspätete Antworten anderer Tabs den gelöschten Snapshot nicht wiederherstellen.

## Begründung und Nachweis

Expand Down
6 changes: 3 additions & 3 deletions docs/architektur/ueberblick.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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.
Loading
Loading