diff --git a/notes/android.md b/notes/android.md new file mode 100644 index 0000000..131f6aa --- /dev/null +++ b/notes/android.md @@ -0,0 +1,112 @@ +# Android-App (Plan, zurückgestellt) + +Stand 2026-10-02. Noch nichts davon ist umgesetzt; das hier ist die +Entscheidungsgrundlage für später. + +## Anforderungen + +- PGP clientseitig: signieren, verifizieren, ver-/entschlüsseln +- private Schlüssel lokal auf dem Gerät speichern +- Push-Benachrichtigungen +- Kamera-Zugriff + +## Entscheidung: Capacitor + +Der bestehende React-Code wird mit Capacitor als APK gebündelt. Eine native +Kotlin-App ist nicht nötig; nativer Code beschränkt sich auf (fertige oder +kleine eigene) Plugins. + +### Verworfen: PWA / Trusted Web Activity + +Wäre fast ohne Umbau machbar (Manifest, Service Worker, +`/.well-known/assetlinks.json`, Bubblewrap), scheitert aber am PGP-Ziel: +Bei TWA/PWA liefert der Server den Code bei jedem Start neu aus. Wer den +Server kontrolliert (Angreifer, kompromittiertes PROD, Anordnung), kann +JavaScript nachschieben, das den privaten Schlüssel ausliest. Clientseitige +Kryptographie soll gerade das Vertrauen in den Server überflüssig machen. + +Mit Capacitor steckt der Code signiert im APK; Updates laufen über den +App-Signaturschlüssel, nicht über einen PROD-Deploy. + +### Verworfen: native App (Kotlin/Compose) + +Kompletter Neubau des Frontends ohne Mehrwert für die Anforderungen; passt +außerdem schlecht zum bewusst rohen Design, das in der Web-Version schon +existiert. + +## Umbau Frontend + +- **API-Basis-URL:** `frontend/src/api.ts` ruft fest `/api…` auf. Das wird + konfigurierbar (z. B. `import.meta.env.VITE_API_BASE`), weil die App unter + `https://localhost` läuft, nicht auf der Domain. +- **Absolute URLs:** Bild-URLs aus der API und geteilte Links + (`/entry/123`) müssen die Domain enthalten. +- **Deep Links:** Android App Links für `https:///entry/…`. +- **Routing:** `createBrowserRouter` funktioniert unter Capacitor weiter. + +## Umbau Backend + +- **Bearer-Token:** Der Session-Cookie (`auth.go`, `HttpOnly`, + `SameSite=Lax`) kommt im WebView cross-origin nicht zuverlässig an. + Zweiter Login-Modus: Token im JSON-Body zurückgeben, Client schickt + `Authorization: Bearer …`, `getSession` akzeptiert beides. Die Web-Version + bleibt beim Cookie. +- **CORS:** Middleware für `Access-Control-Allow-Origin` mit den App-Origins + (`https://localhost`, `capacitor://localhost`) inkl. Preflight. +- **`checkOrigin`** (`main.go`): App-Origins zulassen bzw. Requests mit + Bearer-Token ausnehmen. Ohne Cookie gibt es dort kein CSRF-Risiko. + +## PGP + +- **Bibliothek:** OpenPGP.js im Bundle, kein nativer Code. Kommt mit der + CSP `default-src 'self'` aus. +- **Web-Version:** Derselbe Code kann dort laufen, aber mit der schwächeren + Garantie (servergelieferter Code, siehe oben). Sollte in der UI kenntlich + sein. +- **Schlüsselablage:** Der Android Keystore kann PGP-Schlüssel nicht direkt + halten (keine Curve25519/PGP-Formate, gibt Schlüssel nie heraus). Daher: + 1. Im Keystore liegt ein hardwaregestützter AES-Schlüssel, optional an + Biometrie/PIN gebunden. + 2. Mit ihm wird der PGP-Private-Key verschlüsselt. + 3. Das Ergebnis liegt im App-Speicher. + Dafür gibt es Capacitor-Plugins (Secure Storage, Biometrie); notfalls + ein eigenes Plugin, ca. 100 Zeilen Kotlin. IndexedDB allein ist die + schwächere Alternative (nicht hardwaregebunden, XSS-lesbar). +- **Optional OpenKeychain:** Über dessen OpenPGP-API (Intents) kann die App + Signieren/Entschlüsseln delegieren und hält selbst nie einen Schlüssel. + Eigenes Capacitor-Plugin; nur als Zusatzoption, nicht als einziger Weg. + +### Vorab klären (betrifft das Backend) + +Signiert der Client Beiträge, muss der Server das abbilden: + +- Tabelle mit öffentlichen Schlüsseln pro Account (Upload, Wechsel, + Widerruf) +- Signaturfeld an `entry` +- API liefert Signatur und Schlüssel mit, damit andere Clients + verifizieren können + +Kann unabhängig von der App vorbereitet werden. + +## Push + +- Firebase Cloud Messaging über das Capacitor-Push-Plugin. +- Backend: Tabelle für Device-Tokens pro Account, FCM-Sender. +- Inhalt nur „es gibt etwas Neues“ oder PGP-verschlüsselt, sonst liest + Google mit. + +## Kamera + +Capacitor-Kamera-Plugin; alternativ reicht `` +ganz ohne Plugin. + +## Reihenfolge (Vorschlag) + +1. Backend: Bearer-Token, CORS, `checkOrigin` +2. Frontend: API-Basis-URL, absolute URLs +3. Capacitor-Projekt, APK-Build, App Links +4. Kamera +5. Push (FCM, Device-Tokens) +6. PGP: Schlüsseltabelle/Signaturfeld im Backend, OpenPGP.js, + Keystore-gestützte Ablage +7. optional OpenKeychain-Plugin diff --git a/notes/todo.md b/notes/todo.md index 7da01ec..0e2f93d 100644 --- a/notes/todo.md +++ b/notes/todo.md @@ -12,3 +12,4 @@ - Feed ohne JS (früherer SSR-Stand: Tag `ssr-archiv`) - robots.txt / Sitemap - Ja/Nein Auswertung +- Android-App mit clientseitigem PGP, Push, Kamera (Plan: `notes/android.md`)