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.
This commit is contained in:
parent
2899eb205b
commit
7b1c18590e
10 changed files with 756 additions and 29 deletions
43
AGENTS.md
43
AGENTS.md
|
|
@ -90,11 +90,18 @@ mitbringsl/
|
|||
│ │ ├── 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 aktiv, Rest noch auskommentiert)
|
||||
│ │ ├── 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
|
||||
│ │ ├── 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
|
||||
|
|
@ -123,8 +130,12 @@ Legende: ✅ erledigt · 🚧 in Arbeit · ⬜ offen
|
|||
- ✅ **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:** Argon2id + eigene User (register/login) + Session-Middleware.
|
||||
- ⬜ **Phase B – OIDC:** go-oidc-Verifikation (Google + Generic) + `POST /auth/oidc`.
|
||||
- ✅ **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`.
|
||||
|
|
@ -138,13 +149,23 @@ Legende: ✅ erledigt · 🚧 in Arbeit · ⬜ offen
|
|||
- ⬜ **Phase F – README + docs** (ARCHITECTURE/SYNC/API).
|
||||
|
||||
### Wo genau weitermachen?
|
||||
**Nächster Schritt = Phase B (Auth):**
|
||||
1. `internal/auth/password.go` – Argon2id (PHC-Format) Hashen/Verifizieren.
|
||||
2. `internal/auth/session.go` – Token generieren (crypto/rand, base64url), SHA-256-Hash,
|
||||
in `sessions` einfügen, Middleware `requireAuth` (lädt `user_id` in Context).
|
||||
3. `internal/httpapi/auth.go` – Handler `Register`/`Login`/`Logout`.
|
||||
4. In `api.go` die `/auth/*`-Routen einkommentieren + verdrahten.
|
||||
5. Dann Phase B Teil 2: OIDC (`internal/auth/oidc.go` + `POST /auth/oidc`).
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue