Plan Android-App (notes/android.md)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
764ad50b8e
commit
a8f392edba
@@ -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://<domain>/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 `<input type="file" capture>`
|
||||
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
|
||||
Reference in New Issue
Block a user