Die Instanz soll öffentlich erreichbar werden. createAccount hatte bis
jetzt keinerlei Schranke: kein Code, kein Rate-Limit, describeServer
meldete invite_code_required hartkodiert false. Jeder hätte beliebig viele
Konten anlegen können, jedes mit eigenem Repo, Blöcken und
Firehose-Events.
Zwei Tabellen: invite_codes trägt Zähler und Sperre, invite_code_uses
protokolliert, welcher DID welchen Code eingelöst hat — ein Code kann
mehrere Konten wert sein, also ist "wer hat ihn benutzt" eine Menge. Die
DID hat bewusst keinen Fremdschlüssel: das Protokoll soll das Konto
überleben.
Der Kern ist das Einlösen ohne Rennen. Geprüft wird nicht vorher, sondern
im Schreiben selbst:
UPDATE invite_codes SET used_count = used_count + 1
WHERE code = $1 AND NOT disabled AND used_count < max_uses
RETURNING used_count, max_uses
Der Verlierer zweier gleichzeitiger Registrierungen blockiert auf der
Zeilensperre, liest danach die committete Zeile neu, wertet die
WHERE-Klausel erneut aus, trifft nichts und bekommt 400. Ein
Lesen-dann-Schreiben hätte beide durchgelassen — ich habe genau das
probeweise eingebaut, woraufhin die Nebenläufigkeitstests umfielen
("a one-use code let 5 concurrent registrations through").
Das Einlösen ist die erste Anweisung in der bestehenden Transaktion von
create_account: die Zeilensperre hält über die Konto-Inserts, und ein
Rollback gibt den Code wieder frei. Wer ein Handle-Rennen verliert,
verliert nicht auch noch seine Einladung.
Alle Fehlerfälle — fehlend, leer, unbekannt, gesperrt, aufgebraucht —
liefern dieselbe Meldung, damit der unauthentifizierte Endpoint kein
Orakel zum Abtasten des Code-Raums wird.
Codes erzeugt das Binary selbst, statt dafür einen Endpoint zu öffnen:
`pds-server invite create --count 5 --uses 1`, dazu list und disable.
PDS_INVITE_REQUIRED steht auf false per Default — sonst brechen die
Integrationstests und jede Dev-Instanz. Der Server warnt beim Start
deutlich, solange es aus ist, und describeServer meldet jetzt den
tatsächlichen Wert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
106 lines
4.9 KiB
Bash
106 lines
4.9 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
|
|
|
|
# --- Registrierung ---
|
|
# Verlangt createAccount einen Einladungscode? Default false, damit Dev-
|
|
# Instanzen und die Integrationstests frei Konten anlegen können — eine
|
|
# öffentlich erreichbare PDS gehört auf true gestellt, sonst kann jeder
|
|
# beliebig viele Repos anlegen. Der Server warnt beim Start, solange es
|
|
# aus ist. Codes erzeugen: `pds-server invite create --count 5 --uses 1`
|
|
PDS_INVITE_REQUIRED=false
|
|
|
|
# --- 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
|