Files
Aether/CLAUDE.md
T
marcandClaude Opus 5 9f44215698 feat(oidc): Profilbild vom Identitätsanbieter übernehmen
Schickt der Anbieter ein `picture`, wird es bei der Anmeldung geholt,
mittig auf 256x256 zugeschnitten und oben rechts angezeigt. Fehlt es
oder scheitert das Holen, bleibt es beim Buchstabenkreis - die
Anmeldung selbst hängt nicht davon ab.

Die Bildadresse kommt aus einem Token, also von außen. Erlaubt ist
deshalb ausschließlich der Ursprung aus OIDC_ISSUER, Weiterleitungen
eingeschlossen; dazu 4 MB und 5 Sekunden als Deckel. Ohne das wäre es
eine Aufforderung an den Server, für einen Fremden beliebige Adressen
im internen Netz anzufragen.

Neu: Migration 000019 (avatar_key/-source/-updated_at), Paket
internal/avatar, Route GET /api/v1/users/{id}/avatar. Der GC durchsucht
"avatars/" mit und kennt die Schlüssel - sonst räumte er die Bilder
nach einem Tag weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:43:22 +02:00

167 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Aether – Projektkonventionen für Claude Code
## Projekt-Überblick
Self-hosted Cloud-Alternative zu Nextcloud. Go-Backend + React-Frontend, modularer Monolith.
Projektplan: `cloud-projekt-plan.md`
## Monorepo-Struktur
```
aether/
├── backend/ # Go – modularer Monolith
│ ├── cmd/aether/ # main.go – Einstiegspunkt
│ ├── internal/ # Domänen-Pakete (nie von außen importieren)
│ │ ├── auth/ # Login, Sessions, CSRF, Argon2id, OIDC-User-Anlage
│ │ ├── storage/ # Storage-Interface + LocalFS (atomar, SHA-256)
│ │ ├── files/ # Datei-/Ordner-CRUD, Trash, Versionen, Favoriten
│ │ ├── sharing/ # Öffentliche Share-Links
│ │ ├── admin/ # Admin-Panel: User, Gruppen(-mitgliedschaft), Settings/Theming
│ │ ├── oidc/ # OIDC-Login (optional, via .env)
│ │ ├── avatar/ # Profilbild: zuschneiden, ablegen, ausliefern
│ │ ├── webdav/ # WebDAV-Endpoint /dav/ (Basic Auth – nur App-Passwörter)
│ │ ├── acl/ # Interne Freigaben an User/Gruppen (Rechteauflösung)
│ │ ├── audit/ # Audit-Log (Schreibpfad; Endpunkte liegen in admin/)
│ │ ├── gc/ # Blob-GC: Platte gegen DB abgleichen
│ │ ├── thumbnails/ # Vorschaubild-Generierung
│ │ ├── groups/ # LEERER STUB – Gruppenordner liegen in files/groupfolder.go
│ │ ├── documents/ # Volltext: OCR-Warteschlange + Arbeiter (Phase 6, Teil 1)
│ │ ├── tags/ # Ordnungsmerkmale einer Datei: Etiketten, Belegdatum, Absender
│ │ └── office/ # WOPI-Host: Collabora/OnlyOffice, ein Adapter fuer beide
│ ├── db/migrations/ # SQL-Migrations – Spiegelkopie (siehe Migrations-Regeln!)
│ └── pkg/ # Hilfspakete: config, middleware, response, db (embed migrations)
├── frontend/ # React + TS + Vite + Tailwind
│ └── src/
│ ├── components/
│ ├── pages/
│ ├── api/ # Typed API-Client
│ └── hooks/
├── deploy/ # Docker Compose, Dockerfile, .env.example
└── CLAUDE.md
```
## Backend-Konventionen (Go)
- **Module-Pfad**: `aether`
- **Go-Version**: 1.25 (siehe `go.mod`)
- **Router**: `github.com/go-chi/chi/v5`
- **DB-Treiber**: `github.com/jackc/pgx/v5` (pgxpool für Connection-Pool)
- **Migrations**: `github.com/golang-migrate/migrate/v4`
- **Logging**: `github.com/rs/zerolog` – strukturiert, immer mit Kontext (`log.Ctx(ctx)`)
- **Hashing**: `golang.org/x/crypto/argon2` (Argon2id) – App-Passwörter dagegen SHA-256 (zufälliges Token, jeder WebDAV-Request authentifiziert einzeln)
- **Sessions**: `github.com/gorilla/sessions` mit securecookie
### Paket-Regeln
- Alle Domänen-Pakete unter `internal/` – kein Import von außen
- Keine zirkulären Abhängigkeiten; Dependency-Richtung: `cmd → internal/* → pkg/*`
- Handler in `internal/<domain>/handler.go`, Business-Logic in `internal/<domain>/service.go`, DB in `internal/<domain>/store.go`
- Interfaces für alle externen Abhängigkeiten (Storage, DB) → leicht mockbar
### Code-Stil
- `gofmt` + `goimports` – immer formatiert
- Fehler nie ignorieren (`_`), immer wrappen mit `fmt.Errorf("context: %w", err)`
- Keine Magic-Numbers – Konstanten mit sprechenden Namen
- HTTP-Handler signieren: `func (h *Handler) MethodName(w http.ResponseWriter, r *http.Request)`
- Middleware-Chain: Auth → CSRF → RateLimit → Handler
### Tests
- Testdatei neben der Quelldatei: `auth_test.go` neben `auth.go`
- Integrationstests mit echtem Postgres via `testcontainers-go`
- Pflicht-Tests: Path-Traversal, Upload-Abbruch, Checksum-Verifikation, Soft-Delete-Recovery
- `go test ./...` muss grün sein vor jedem Commit
## Frontend-Konventionen (React + TS)
- **Bundler**: Vite
- **Styling**: Tailwind CSS + CSS-Variablen für Design-Tokens
- **Testing**: Vitest + Testing Library; E2E: Playwright
- **API-Client**: typed fetch-Wrapper unter `src/api/`
### Design-Tokens (CSS-Variablen)
Alle Farben laufen über CSS-Custom-Properties:
```css
:root {
--color-primary: ...;
--color-accent: ...;
--color-background: ...;
--color-surface: ...;
--color-text: ...;
}
```
Admin setzt globale Tokens; User-Override nur für eigene Palette (Scope: `[data-user-theme]`).
### Komponenten-Regeln
- Komponenten in `PascalCase.tsx`
- Kein `dangerouslySetInnerHTML` mit User-Input (XSS)
- API-Calls nur über `src/api/`-Funktionen, nie direkt `fetch` in Komponenten
## Sicherheits-Pflichten
- Path-Traversal: jeden Dateipfad kanonisieren und gegen Storage-Root prüfen
- CSRF: Double-Submit-Cookie oder synchronizer-Token
- Rate-Limiting: Login-Endpunkt + Admin-Login strenger
- Auth-Check: jede Route serverseitig prüfen – nie Frontend vertrauen
- SQL: ausschließlich parametrisierte Queries (pgx unterstützt das nativ)
- Uploads: außerhalb Webroot, keine ausführbaren Rechte, MIME-Prüfung
- WebDAV: **nur App-Passwörter**, nie das Kontopasswort – es ist zugleich der Admin-Zugang
- Kein Zugriff auf fremde IDs → `404`, nicht `403`: sonst verrät die Antwort die Existenz
- Listen und Suche schneiden den erreichbaren Bereich **in der Abfrage** zu, nie nachträglich im Handler
- Hochgeladene Dateien nur für eine Positivliste harmloser Typen `inline` ausliefern (sonst läuft fremdes HTML im eigenen Ursprung)
## Datenintegrität (nicht verhandelbar)
1. **Atomares Schreiben**: Temp-Datei → `fsync` → `rename`. Niemals in-place.
2. **SHA-256**: beim Upload berechnen + in DB; beim Download verifizieren.
3. **Soft-Delete only**: kein hartes Löschen. Bytes erst per GC entfernen.
4. **DB-Commit nach Blob-Write**: Metadaten erst committen, wenn Blob erfolgreich.
## Migrations-Regeln
- Dateien: `000001_init.up.sql` / `000001_init.down.sql`
- **Jede Migration in BEIDE Verzeichnisse legen:** `backend/pkg/db/migrations/` (wird ins Binary embedded und beim Start ausgeführt) UND `backend/db/migrations/` (Spiegelkopie). Beide müssen identisch sein.
- Keine destruktiven Änderungen ohne explizites Down-Migration
- Migration beim App-Start automatisch ausführen (via `migrate.Up()`)
- Aktueller Stand: bis `000019_user_avatar` (Profilbild eines Kontos)
## Commit-Regeln
Format: `<typ>(<scope>): <kurze Beschreibung>`
Typen: `feat`, `fix`, `test`, `refactor`, `docs`, `chore`, `security`
Beispiele:
- `feat(auth): add argon2id password hashing`
- `test(storage): add checksum verification test`
- `security(admin): enforce server-side admin check on all routes`
Kein Commit ohne grüne Tests.
## Reverse Proxy & Proxy-Awareness
- **Kein Proxy-Container** in docker-compose.yml – TLS und Domain gehören zum Wirt,
nicht ins Projekt. Nichts hier soll einen mitgelieferten Proxy voraussetzen.
- App lauscht auf reinem HTTP (`APP_LISTEN_ADDR`). TLS = externer Proxy.
- `X-Forwarded-For` / `X-Forwarded-Proto` werden **nur** von `TRUSTED_PROXIES` (IPs/CIDRs) akzeptiert.
- Echte Client-IP immer über `middleware.RealIP(r)` abrufen – niemals `r.RemoteAddr` direkt.
- `Secure`-Cookie-Flag: über `middleware.IsHTTPS(r)` setzen (berücksichtigt Forwarded-Proto).
- `PUBLIC_URL` für OIDC-Redirects, WOPI-Callbacks und Share-Links verwenden.
- HSTS gehört auf den Edge-Proxy, **nicht** in die App-Header.
## Docker & Secrets
- **Backend immer via Docker starten/laufen lassen (`docker compose ...` in `deploy/`), nie `go run`.** Der Host hat kein Go-Toolchain installiert; Builds/Compile-Checks laufen über das `golang:1.25-alpine`-Image. Migrations + DB-Verbindung sind auf die Compose-Umgebung ausgelegt.
- Secrets **nur** über `.env` / Compose-Env – nie im Image oder Repo
- Multi-Stage-Build: Builder-Stage (Go-Compiler) → Alpine final
- `.env` ist in `.gitignore`; `.env.example` zeigt alle Keys ohne Werte
## Phasen-Stopp
Nach Phase 1: **STOPP** – Zusammenfassung + Phase-2-Vorschlag, erst nach OK weitermachen.