mitbringsl/AGENTS.md
Tronax 7b1c18590e
Backend Phase B (1/2): password auth + sessions
Argon2id password hashing (PHC format, self-encoded/decoded without an
external lib) with constant-time verification, UserStore (create/get by
email and id) and SessionStore (opaque crypto/rand tokens, SHA-256 hashed
in DB, create/lookup/revoke, last_seen_at bump on lookup).

HTTP layer: Register/Login/Logout handlers + RequireAuth middleware.
Login uses a dummy-hash path so unknown-email and wrong-password yield the
same timing/shape, narrowing user enumeration. Tokens accepted via Bearer
header (native clients) or session cookie (HttpOnly, SameSite=Lax).

Routes wired in api.go: POST /auth/register, /auth/login, /auth/logout.
Verified with go test, go vet and an end-to-end smoke test against a real
PostgreSQL container (register/login/logout/duplicate/short-pw/wrong-pw).

OIDC (Phase B part 2) follows next; the issueSession helper is reused.
2026-08-05 19:05:07 +02:00

11 KiB
Raw Blame History

AGENTS.md Kontext & Fortschritt für KI-Agenten

Diese Datei hält den Projekt-Stand und die Architekturentscheidungen fest, damit ein Agent (z.B. zuhause) sofort weiterarbeiten kann. Sie ist Teil des Repos.

Projekt: Mitbringsl

Local-First Einkaufslisten-App (Bring-Alternative, werbefrei). Backend: Go (net/http, pgx, golang-migrate, go-oidc) als Docker-Container. App: Kotlin + Jetpack Compose, Room, Hilt, WorkManager, offline-first. Deployment: docker-compose (Caddy + backend + migrate + postgres:16), auto-HTTPS.

Vollständiger Plan liegt als genehmigtem Plan zugrunde (siehe Abschnitt "Roadmap").


Getroffene Architekturentscheidungen (verbindlich)

  • Backend-Sprache: Go (nicht Rust).
  • DB: PostgreSQL (postgres:16-alpine), Extensions pgcrypto + pg_trgm.
  • Sync: Local-First, keine Datenverluste Append-only op_log (SOURCE OF TRUTH), items/lists sind Projektionen; Konfliktlösung per LWW-Register (hlc_ts, client_id)
    • Tombstones (deleted_at). HLC = Hybrid Logical Clock.
    • Push: POST /api/lists/{id}/ops (idempotent via UNIQUE(client_id, client_seq)).
    • Pull: GET /api/lists/{id}/ops?since={seq}.
  • Auth:
    • Eigene User: E-Mail/Passwort, Argon2id (PHC-Format).
    • OIDC: Google + Generic OIDC. Die App macht den Code+PKCE-Flow selbst und schickt nur das id_token ans Backend (POST /auth/oidc). Backend verifiziert via github.com/coreos/go-oidc/v3 (Signatur gegen JWKS, iss/aud/exp) und stellt eigene opaque Session-Tokens aus (sessions-Tabelle, SHA-256-Hash gespeichert).
    • AppAuth-Android bewusst NICHT verwendet (seit 2021 verwaist). Stattdessen: Google via Credential Manager, Generic OIDC via Custom Tabs + eigenes PKCE.
  • Vorschläge: aggregiert aus Item-Namen aller User (item_names-Tabelle, pg_trgm fuzzy search). Endpoint GET /api/suggestions?q=.
  • MVP-Scope: Single-Owner-Listen (list_members existiert, wird in Phase 2 für geteilte Listen genutzt); keine Echtzeit-Push (nur Periodic-Pull 15 min + Pull-on-Online); keine Web-UI.

Technologie-Stacks (final)

Backend

Bereich Wahl
Go-Version 1.26 (go.mod hat go 1.26; Docker-Image golang:1.26-alpine)
HTTP stdlib net/http (Go 1.22+ Routing mit Methoden + Path-Vars)
DB-Driver github.com/jackc/pgx/v5 (pgxpool)
Migrationen github.com/golang-migrate/migrate/v4 + source/iofs (eingebettet via embed.FS)
OIDC github.com/coreos/go-oidc/v3 + golang.org/x/oauth2
Passwörter Argon2id (golang.org/x/crypto/argon2)
Sessions opaque Tokens (32 B base64url), sessions-Tabelle, SHA-256-Hash
Config github.com/caarlos0/env/v11 (struct-tag env)
Logging log/slog mit NewJSONHandler → stdout
Docker Multi-Stage, CGO_ENABLED=0, gcr.io/distroless/static-debian12:nonroot
Queries direkt mit pgx (sqlc wurde aus Skalierbarkeit bewusst auf später verschoben)

Android (noch nicht begonnen)

Bereich Wahl
Build Kotlin DSL + libs.versions.toml, Single-Module :app
Kotlin/AGP/Gradle Kotlin 2.x (K2), AGP 8.9+, Gradle 8.11+, JDK 17
UI Compose BOM (2025.x) + Material 3
Arch MVVM + ViewModel + StateFlow, UDF
DB Room (KSP), Source of Truth via Flow
Netzwerk Retrofit + OkHttp + kotlinx.serialization
Sync WorkManager CoroutineWorker (Outbox-Drain + Cursor-Pull)
DI Hilt (KSP) + hilt-navigation-compose + hilt-work
IDs Client-seitige UUIDs
Auth Google: Credential Manager; Generic OIDC: Custom Tabs + PKCE selbst
Suche OutlinedTextField + DropdownMenu, Room-FTS, Server-Fallback
SDK minSdk 26, compile/target 36

Repository-Struktur

mitbringsl/
├── AGENTS.md                          ← DIES DATEI
├── README.md
├── .gitignore
├── backend/
│   ├── cmd/
│   │   ├── server/main.go             # HTTP-Server-Einstieg (verdrahtet cfg/logger/pool/api)
│   │   └── migrate/main.go            # Migrations-Runner (iofs-embedded)
│   ├── internal/
│   │   ├── config/config.go           # Config (caarlos0/env)
│   │   ├── logging/logging.go         # slog JSON-Setup
│   │   ├── store/db.go                # pgxpool-Setup
│   │   ├── auth/                      # PHASE B  Password + Session
│   │   │   ├── password.go            # Argon2id im PHC-Format (HashPassword/VerifyPassword)
│   │   │   ├── password_test.go       # PHC-Roundtrip-Tests
│   │   │   ├── user.go                # UserStore: CreateUser/GetByEmail/GetByID
│   │   │   ├── session.go             # SessionStore: opaque Tokens, SHA-256-Hash, Create/Lookup/Revoke
│   │   │   └── pgcode.go              # isUniqueViolation (SQLSTATE 23505)
│   │   └── httpapi/
│   │       ├── api.go                 # API-Objekt + Router (Health + /auth/* aktiv, Rest auskommentiert)
│   │       ├── render.go              # JSON-Render + Problem + Fehler-Sentinale + decodeJSON
│   │       ├── middleware.go          # requestID/logging/recover/cors + Chain
│   │       ├── health.go              # /healthz + /readyz
│   │       └── auth.go                # Register/Login/Logout-Handler + RequireAuth-Middleware
│   ├── migrations/
│   │   ├── embed.go                   # //go:embed *.sql
│   │   ├── 000001_init_schema.up.sql  # users/sessions/lists/list_members/items/op_log/item_names
│   │   └── 000001_init_schema.down.sql
│   ├── Dockerfile                     # Multi-Stage, baut server + migrate
│   ├── .dockerignore
│   ├── go.mod / go.sum
├── deploy/
│   ├── docker-compose.yml             # caddy + backend + migrate + db (mit YAML-anchors)
│   ├── Caddyfile                      # auto-HTTPS
│   ├── .env.example                   # alle env-Vars dokumentiert
│   └── db/init/001_extensions.sql     # CREATE EXTENSION pgcrypto, pg_trgm
├── docs/                              # NOCH LEER (folgt Phase F)
└── android/                           # NOCH LEER (folgt Phase D)

Roadmap / Fortschritt

Legende: erledigt · 🚧 in Arbeit · offen

  • Repo-Struktur + .gitignore + README.md + git init (Branch main)
  • Phase A Backend-Fundament: Config, slog, pgxpool, /healthz+/readyz, Server-Main.
  • Phase A Migrationen: vollständiges Init-Schema (up+down) + migrate-Binary (iofs).
  • Phase A Docker: Dockerfile (Go 1.26 → distroless nonroot), docker-compose (caddy/backend/migrate/db), Caddyfile, .env.example. Verifiziert: Image baut, beide Binaries laufen im Container (Smoke-Test OK).
  • Phase B Auth (Password): Argon2id im PHC-Format + UserStore (Create/GetByEmail/GetByID) + SessionStore (opaque Tokens, SHA-256-Hash, Create/Lookup/Revoke) + Handler Register/Login/Logout + RequireAuth-Middleware. Verifiziert: go test ./internal/auth/... grün, E2E-Smoke-Test gegen echtes PostgreSQL via Docker (Register/Login/Logout/Duplicate/Short-PW/Wrong-PW alle korrekt).
  • 🚧 Phase B OIDC: go-oidc-Verifikation (Google + Generic) + POST /auth/oidc.
  • Phase C Sync-Kern: op_log-Append (idempotent), HLC, Projektion op→items/lists (LWW+Tombstones).
  • Phase C Endpoints: /api/lists, /api/lists/{id}/ops (push+pull).
  • Phase C Suggestions: item_names-Trigger + /api/suggestions.
  • Phase D Android-Fundament: Gradle (Kotlin DSL, Version Catalog, Hilt/KSP, Compose BOM), Theme, Nav, Room.
  • Phase D Repository + Retrofit-API + DTOs.
  • Phase D Login-Screen (eigene User + Google Credential Manager + Generic OIDC PKCE).
  • Phase E SyncEngine (OutboxDrain + CursorPull via WorkManager), HLC client-side.
  • Phase E Listen-Übersicht + Detail + AddItemBar (Autocomplete) + Settings.
  • Phase F Polish (Fehlerbehandlung, Offline-Indikator, Empty States, Tests).
  • Phase F README + docs (ARCHITECTURE/SYNC/API).

Wo genau weitermachen?

Nächster Schritt = Phase B Teil 2 (OIDC) Teil 1 (Password-Auth) ist fertig :

Teil 1 (erledigt):

  • internal/auth/password.go Argon2id im PHC-Format (HashPassword/VerifyPassword).
  • internal/auth/user.go UserStore (CreateUser/GetByEmail/GetByID).
  • internal/auth/session.go SessionStore (crypto/rand + base64url + SHA-256, Create/Lookup/Revoke).
  • internal/httpapi/auth.go Register/Login/Logout + RequireAuth-Middleware.
  • In api.go sind /auth/register, /auth/login, /auth/logout aktiv verdrahtet.

Teil 2 (offen OIDC):

  1. internal/auth/oidc.go Verifikation eines id_token via github.com/coreos/go-oidc/v3 (JWKS-Signatur, iss/aud/exp prüfen). Provider-Auswahl anhand issuer aus Config.
  2. POST /auth/oidc-Handler in internal/httpapi/auth.go: empfängt {provider, id_token}, verifiziert, findet/legt User an (oidc_issuer+oidc_subject UNIQUE), stellt Session aus (gleicher issueSession-Pfad wie Login).
  3. In api.go mux.HandleFunc("POST /auth/oidc", a.auth.OIDC) einkommentieren.
  4. Config-Validierung: wenn OIDC_GOOGLE_ENABLED=true, muss OIDC_GOOGLE_CLIENT_ID gesetzt sein.

Wichtige technische Notizen / Fallstricke (bereits gelöst)

  • golang-migrate iofs-Pfad: Der Import ist github.com/golang-migrate/migrate/v4/source/iofs (MIT /source/). Die Pfade ohne /source/ gibt es nicht mehr → Build-Fehler.
  • Go-Version: pgx/v5 v5.10.0 braucht Go ≥ 1.25, deshalb Go 1.26 (lokal + Docker-Image golang:1.26-alpine). go.mod hat go 1.26.
  • Docker-Build auf Windows/Docker Desktop: --network host im Build-Container funktioniert NICHT (WSL2-NAT). Für Tests: echtes docker network create + Container-Namen nutzen.
  • Compose braucht zwingend POSTGRES_PASSWORD (.env oder Env), da db.environment mit ${POSTGRES_PASSWORD:?...}-Assertion vor dem Build validiert wird.
  • Healthcheck im backend-Service wurde entfernt, weil distroless/static kein wget/curl enthält. Später: eigenes Binary, das /healthz per Go-HTTP prüft, oder healthcheck über Caddy/extern.
  • SQL-Queries werden direkt mit pgx geschrieben (kein sqlc im MVP), da sqlc lokal nicht installiert ist und ein extra Code-Gen-Schritt nötig wäre. Bei Bedarf später problemlos nachrüstbar.

Build- & Test-Befehle

# Backend lokal bauen
cd backend && go build ./... && go vet ./...

# Backend-Image bauen
cd backend && docker build -t mitbringsl-backend:test .

# Komplettes Stack starten (braucht deploy/.env)
cd deploy && cp .env.example .env && docker compose up -d --build

# Migrationen manuell gegen bestehende DB anwenden
docker run --rm --network <net> \
  -e DATABASE_URL="postgres://app:PW@<db-host>:5432/appdb?sslmode=disable" \
  mitbringsl-backend:test /app/migrate up

Git-Status

  • Repo initialisiert, Branch main. Remote ist noch NICHT konfiguriert.
  • Es wurde noch nicht committet (Stand beim Schreiben dieser Datei).