Files
maarcadetweet/.env.example
T
tomdeboneandClaude Opus 5 f04d63dd7b feat(pds): Einladungscodes für createAccount
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
2026-09-10 21:06:45 +02:00

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