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.
11 KiB
11 KiB
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), Extensionspgcrypto+pg_trgm. - Sync: Local-First, keine Datenverluste – Append-only
op_log(SOURCE OF TRUTH),items/listssind Projektionen; Konfliktlösung per LWW-Register(hlc_ts, client_id)- Tombstones (
deleted_at). HLC = Hybrid Logical Clock.
- Push:
POST /api/lists/{id}/ops(idempotent viaUNIQUE(client_id, client_seq)). - Pull:
GET /api/lists/{id}/ops?since={seq}.
- Tombstones (
- 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_tokenans Backend (POST /auth/oidc). Backend verifiziert viagithub.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). EndpointGET /api/suggestions?q=. - MVP-Scope: Single-Owner-Listen (
list_membersexistiert, 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(Branchmain) - ✅ 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) + HandlerRegister/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.gosind/auth/register,/auth/login,/auth/logoutaktiv verdrahtet.
Teil 2 (offen – OIDC):
internal/auth/oidc.go– Verifikation einesid_tokenviagithub.com/coreos/go-oidc/v3(JWKS-Signatur, iss/aud/exp prüfen). Provider-Auswahl anhandissueraus Config.POST /auth/oidc-Handler ininternal/httpapi/auth.go: empfängt{provider, id_token}, verifiziert, findet/legt User an (oidc_issuer+oidc_subject UNIQUE), stellt Session aus (gleicherissueSession-Pfad wie Login).- In
api.gomux.HandleFunc("POST /auth/oidc", a.auth.OIDC)einkommentieren. - Config-Validierung: wenn
OIDC_GOOGLE_ENABLED=true, mussOIDC_GOOGLE_CLIENT_IDgesetzt sein.
Wichtige technische Notizen / Fallstricke (bereits gelöst)
- golang-migrate
iofs-Pfad: Der Import istgithub.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.0braucht Go ≥ 1.25, deshalb Go 1.26 (lokal + Docker-Imagegolang:1.26-alpine).go.modhatgo 1.26. - Docker-Build auf Windows/Docker Desktop:
--network hostim Build-Container funktioniert NICHT (WSL2-NAT). Für Tests: echtesdocker network create+ Container-Namen nutzen. - Compose braucht zwingend
POSTGRES_PASSWORD(.envoder Env), dadb.environmentmit${POSTGRES_PASSWORD:?...}-Assertion vor dem Build validiert wird. - Healthcheck im
backend-Service wurde entfernt, weil distroless/static keinwget/curlenthält. Später: eigenes Binary, das/healthzper Go-HTTP prüft, oderhealthcheckü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).