Files
maarcadetweet/.env.example
T
tomdeboneandClaude Opus 5 a2a371b7d9 feat(appview): Bearer-Auth für Timeline und Notifications
Die AppView hatte keinerlei Authentifizierung: jeder konnte
/api/notifications?did=<beliebig> lesen und per /seen als gelesen
markieren. Mit Phase 8 sind das die ersten privaten Daten im System.

Das Access-JWT der PDS trug von Anfang an sub, scope
"com.atproto.access" und aud "did:web:appview…" — es war für die
AppView ausgestellt, nur hat sie es nie geprüft. Neu ist deshalb vor
allem die Schlüsselbeschaffung: auth.rs holt das DID-Dokument der PDS
(PDS_INTERNAL_URL, sonst PDS_PUBLIC_URL), cached den Schlüssel und lädt
ihn bei einem Verifikationsfehler nach — höchstens einmal pro Minute,
damit Müll-Tokens kein Werkzeug werden, die PDS zu fluten. Ein
Schlüsselwechsel braucht damit keinen Neustart.

Ist die PDS beim Start weg, warnt die AppView nur und startet trotzdem
(sie indiziert den Firehose, der von der lokalen PDS unabhängig ist).
Ist der Schlüssel beim Prüfen eines Tokens nicht zu beschaffen, gibt es
503 — fail closed.

Geschützt: /api/timeline/home und die drei Notification-Endpoints, jeweils
mit sub == did. Öffentlich bleiben Profile, Suche, Posts, Threads und die
Follower-Listen; das sind in AT Proto öffentliche Records.

401 AuthMissing / 401 TokenInvalid / 403 Forbidden / 503 AuthUnavailable.
TokenInvalid ist ein Vertrag mit dem Client: daran erkennt er, dass er
sein Token erneuern und einmal wiederholen muss.

Dazu CORS: statt Any für alles jetzt eine Allowlist über
APPVIEW_CORS_ORIGINS (unset = altes Verhalten plus Warnung), und
/internal/ingest-commit liegt außerhalb der CORS-Schicht — die Route
wird server-zu-server aufgerufen, ein Allow-Origin darauf würde nur
einer Webseite helfen, in den Index zu schreiben.

APPVIEW_AUTH_REQUIRED=false stellt das alte Verhalten her (VPN-Instanz,
fail-open-Tests) und warnt beim Start in Großbuchstaben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
2026-09-09 23:02:27 +02:00

98 lines
4.5 KiB
Bash

# =====================================================
# maarcadetweet — environment
# =====================================================
# Copy to .env and adjust. Beide Binaries laden `.env` beim Start
# selbst (dotenvy); echte Umgebungsvariablen haben Vorrang.
# --- General ---
RUST_LOG=info,maarcadetweet=debug,sqlx=warn
# --- PDS server ---
PDS_HOST=127.0.0.1
PDS_PORT=2583
PDS_PUBLIC_URL=http://127.0.0.1:2583
PDS_HANDLE_DNS_ZONE=.maarcadetweet.local
# MUSS Hex sein, >= 32 Bytes ergeben und ein gültiger P-256-Skalar
# sein (also nicht lauter Nullen) — `jwt_issuer.rs` macht hex::decode()
# und p256::SecretKey::from_bytes(). Ein ungültiger Wert lässt den
# Server zwar starten, aber alles, was den Serverschlüssel ableitet,
# antwortet mit 500: createAccount/createSession/refreshSession UND
# jeder Record-Write (repo.rs, feed.rs, blob.rs, profile.rs).
# Der Wert hier ist ein Zufallswert fürs Beispiel — für echte
# Instanzen einen eigenen erzeugen:
# openssl rand -hex 32
PDS_JWT_SECRET=522084586f3d3abb835d43b7c4726735d78803dd58490f62a92571cd29033a95
# Wohin die PDS ihre Commits pusht (POST /internal/ingest-commit).
# Default: APPVIEW_PUBLIC_URL.
# APPVIEW_INTERNAL_URL=http://127.0.0.1:2584
# --- AppView service ---
APPVIEW_HOST=127.0.0.1
APPVIEW_PORT=2584
APPVIEW_PUBLIC_URL=http://127.0.0.1:2584
JETSTREAM_URL=wss://jetstream1.us-east.bsky.network/subscribe
# Collections, die die AppView indexiert. `app.twi.post` ist das
# eigene 160-Zeichen-Lexicon und muss mit rein, sonst tauchen eigene
# Posts nur über den PDS-Push auf; `app.bsky.actor.profile` füttert
# den Profil-Cache.
JETSTREAM_COLLECTIONS=app.twi.post,app.bsky.feed.post,app.bsky.feed.like,app.bsky.feed.repost,app.bsky.graph.follow,app.bsky.actor.profile
# Für den Handle-Sync-Worker: welche PDS nach lokalen Handles gefragt
# wird. Default: PDS_PUBLIC_URL.
# PDS_INTERNAL_URL=http://127.0.0.1:2583
# Intervall des Handle-Sync-Workers in Sekunden (Default: 300).
# APPVIEW_HANDLE_SYNC_INTERVAL_SECS=60
# --- Databases ---
DATABASE_URL_PDS=postgres://pds:pds@127.0.0.1:5434/pds
DATABASE_URL_APPVIEW=postgres://appview:appview@127.0.0.1:5435/appview
# --- Blob store (S3 / MinIO) ---
S3_ENDPOINT=http://127.0.0.1:9100
S3_REGION=us-east-1
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET_PDS=maarcadetweet-pds
# Pflichtvariable in AppConfig::from_env(), wird derzeit von keinem
# Code-Pfad gelesen — muss gesetzt sein, damit der Start nicht
# fehlschlägt.
S3_BUCKET_APPVIEW=maarcadetweet-appview
# --- PLC Directory (dev: leave default; can mock) ---
PLC_DIRECTORY_URL=https://plc.directory
# PLC_DIRECTORY_URL=http://127.0.0.1:2582
# --- AppView ingest auth (optional, dev ok if unset) ---
# Wenn gesetzt, muss die PDS denselben Wert als Header
# `X-Ingest-Secret` mitschicken; ist er nicht gesetzt, nimmt
# /internal/ingest-commit unauthentifizierte Writes entgegen (jeder,
# der den Port erreicht, kann Posts/Follows/Notifications fälschen).
# Die AppView warnt beim Start, solange er fehlt.
# APPVIEW_INGEST_SECRET=change-me-to-a-shared-secret-between-pds-and-appview
# --- AppView auth (Bearer-Token der PDS) ---
# Erzwingt ein gültiges Access-JWT auf den privaten Endpoints
# (/api/notifications, /api/notifications/count,
# /api/notifications/seen, /api/timeline/home); `sub` im Token muss
# dem `did`-Parameter entsprechen, sonst 403. Default: true.
# Den öffentlichen P-256-Schlüssel holt sich die AppView von
# `PDS_INTERNAL_URL` (sonst `PDS_PUBLIC_URL`) unter
# /.well-known/did.json — `PDS_JWT_SECRET` verlässt die PDS nie.
# Auf `false` verhält sich die AppView wie vor der Auth-Einführung
# (alles öffentlich): nötig für die fail-open-Integrationstests mit
# synthetischen DIDs und für eine Instanz, die schon per VPN
# abgeschottet ist. Die AppView warnt beim Start laut, wenn er aus ist.
# APPVIEW_AUTH_REQUIRED=true
# Kommaseparierte Allowlist der Browser-Origins, die `/api/*` aufrufen
# dürfen. Nicht gesetzt = bisheriges Verhalten
# (`Access-Control-Allow-Origin: *`) plus Startup-Warnung.
# Der Tauri-Webview hat je nach Plattform eine eigene Origin:
# macOS/iOS `tauri://localhost`, Windows `http://tauri.localhost`,
# im Dev-Modus der Vite-Server `http://127.0.0.1:1430`.
# `Authorization` steht immer in den erlaubten Headern (sonst
# scheitert der Preflight), `allow_credentials` bleibt aus.
# APPVIEW_CORS_ORIGINS=tauri://localhost,http://127.0.0.1:1430
# --- Tauri-Client (Build-/Laufzeit-Overrides des Desktop-Clients) ---
# MAARCADETWEET_PDS_URL=http://127.0.0.1:2583
# MAARCADETWEET_APPVIEW_URL=http://127.0.0.1:2584