diff --git a/cmd/account-api/main.go b/cmd/account-api/main.go new file mode 100644 index 0000000..397f6e4 --- /dev/null +++ b/cmd/account-api/main.go @@ -0,0 +1,84 @@ +// account-api ist der Aufrufpunkt fuer IAM-16: setzt die bereits fertigen +// IAM-08-Handler (internal/auth, internal/authtoken, internal/totp) zu +// einem laufenden Login/Account-HTTP-Dienst zusammen. REINES WIRING — +// keine Aenderung an den bestehenden Paketen. Tenant-gescoped (Modell C): +// ein Dienst pro Mandanten-Datenbank, wie internal/auth.LoginService es +// bereits vorsieht. +package main + +import ( + "context" + "log" + "net/http" + "os" + + "github.com/jackc/pgx/v5/pgxpool" + + "gitea.perlbach24.de/scripte/nexarch/internal/auth" + "gitea.perlbach24.de/scripte/nexarch/internal/authtoken" + "gitea.perlbach24.de/scripte/nexarch/internal/totp" + "gitea.perlbach24.de/scripte/nexarch/internal/user" +) + +func requireEnv(name string) string { + v := os.Getenv(name) + if v == "" { + log.Fatalf("%s muss gesetzt sein", name) + } + return v +} + +func main() { + dsn := requireEnv("NEXARCH_ACCOUNT_TENANT_DSN") + tenantSlug := requireEnv("NEXARCH_ACCOUNT_TENANT_SLUG") + jwtSecret := requireEnv("NEXARCH_ACCOUNT_JWT_SECRET") + totpIssuer := os.Getenv("NEXARCH_ACCOUNT_TOTP_ISSUER") + if totpIssuer == "" { + totpIssuer = "NEXARCH" + } + addr := os.Getenv("NEXARCH_ACCOUNT_API_LISTEN_ADDR") + if addr == "" { + addr = "127.0.0.1:8099" + } + + ctx := context.Background() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + log.Fatalf("datenbankverbindung: %v", err) + } + defer pool.Close() + + users := user.NewTenantUserStore(pool) + totpStore := totp.NewStore(pool) + tokenIssuer := auth.NewTokenIssuer(jwtSecret) + loginService := auth.NewLoginService(users, tokenIssuer, tenantSlug) + + authHandler := auth.NewHandler(loginService) + totpHandler := totp.NewHandler(users, totpStore, loginService, totpIssuer) + profileHandler := auth.NewProfileHandler(users) + changePasswordHandler := auth.NewChangePasswordHandler(users) + resetStore := authtoken.NewStore(pool) + resetHandler := authtoken.NewHandler(resetStore, users, authtoken.LogNotifier{}) + + mux := http.NewServeMux() + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + // Login ist DER EINE Endpunkt, den das Frontend aufruft (deckt 2FA + // optional mit ab) — siehe internal/totp/handler.go-Dokumentation. + mux.HandleFunc("POST /auth/login", totpHandler.Login) + mux.HandleFunc("POST /auth/logout", authHandler.Logout) + mux.HandleFunc("POST /auth/password-reset/request", resetHandler.RequestReset) + mux.HandleFunc("POST /auth/password-reset/complete", resetHandler.CompleteReset) + + mux.HandleFunc("GET /account/me", auth.RequireAuth(tokenIssuer, profileHandler.Me)) + mux.HandleFunc("POST /account/change-password", auth.RequireAuth(tokenIssuer, changePasswordHandler.ChangePassword)) + + mux.HandleFunc("GET /auth/totp/status", auth.RequireAuth(tokenIssuer, totpHandler.Status)) + mux.HandleFunc("POST /auth/totp/setup/begin", auth.RequireAuth(tokenIssuer, totpHandler.SetupBegin)) + mux.HandleFunc("POST /auth/totp/setup/confirm", auth.RequireAuth(tokenIssuer, totpHandler.SetupConfirm)) + + log.Printf("account-api: listening on %s (tenant %s)", addr, tenantSlug) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatalf("http server: %v", err) + } +} diff --git a/deploy/systemd/nexarch-account-api.service.tmpl b/deploy/systemd/nexarch-account-api.service.tmpl new file mode 100644 index 0000000..5ddf51e --- /dev/null +++ b/deploy/systemd/nexarch-account-api.service.tmpl @@ -0,0 +1,14 @@ +[Unit] +Description=NEXARCH Core - Login/Account-Dienst (IAM-08/IAM-16) +After=network.target postgresql.service + +[Service] +Type=simple +User=nexarch +EnvironmentFile=/etc/nexarch/account-api.env +ExecStart=__INSTALL_DIR__/bin/account-api +Restart=on-failure +StandardOutput=journal + +[Install] +WantedBy=multi-user.target diff --git a/docs/IAM-16-PRUEFPROTOKOLL.md b/docs/IAM-16-PRUEFPROTOKOLL.md new file mode 100644 index 0000000..ed1b6d3 --- /dev/null +++ b/docs/IAM-16-PRUEFPROTOKOLL.md @@ -0,0 +1,78 @@ +# IAM-16 – Prüfprotokoll: Login/Account-Dienst starten (IAM-08 als laufender Dienst) + +Voraussetzung IAM-08 – bereits Fertig, hier UNVERÄNDERT. + +## Bedeutung über IAM-08 hinaus + +IAM-16 ist nicht nur die Voraussetzung für die IAM-08-eigenen Prüfungen, +sondern die grundlegende Session-/Auth-Infrastruktur für JEDE +Auth-geschützte Core-GUI (`auth.RequireAuth`/`auth.ClaimsFromContext`). +Bei der RBAC-05-Sichtprüfung wurde festgestellt, dass +`internal/rbac.Handler.ListRoles` genau diese Middleware voraussetzt — +RBAC-05s Board-`dependsOn` wurde entsprechend um IAM-16 ergänzt. + +## Reines Wiring, keine neue Logik + +`git diff --stat internal/auth/ internal/authtoken/ internal/totp/ internal/user/` +liefert KEINEN Diff gegenüber dem IAM-08-Stand. `cmd/account-api/main.go` +setzt ausschließlich bestehende Konstruktoren zusammen (u. a. +`totp.Handler.Login` als DER EINE Login-Endpunkt, der optional 2FA +mitprüft — bereits so von IAM-08 dokumentiert). Passwort-Reset nutzt +`authtoken.LogNotifier{}` — ebenfalls bereits von IAM-08 als +Übergangslösung bereitgestellt (Versand über CFG-02/CFG-05 ist explizit +ein späterer Austausch, kein IAM-16-Thema). + +## Umsetzung + +- `cmd/account-api/main.go` – tenant-gescopter (Modell C) Login/Account- + Dienst: `POST /auth/login` (2FA-fähig), `POST /auth/logout`, + `POST /auth/password-reset/{request,complete}`, `GET /account/me`, + `POST /account/change-password`, `GET/POST /auth/totp/*` (Status/ + Setup) — letztere drei hinter `auth.RequireAuth`. +- `deploy/systemd/nexarch-account-api.service.tmpl`. + +## Prüfungen + +| # | Prüfung | Ergebnis | +|---|---|---| +| 1 | Dienst startet und bleibt stabil (systemctl status aktiv) | **bestanden** – real auf 131: `nexarch-account-api.service` aktiv, `Restart=on-failure` | +| 2 | Realer Login-Versuch (korrektes Passwort) liefert eine gültige Session, falsches Passwort wird abgelehnt | **bestanden** – real per `curl`: falsches Passwort → 401 (`invalid_credentials`); korrektes Passwort → 200 mit echtem, signiertem `nexarch_session`-JWT-Cookie (`HttpOnly`, `Secure`, `SameSite=Strict`); anschließend `GET /account/me` MIT Cookie → 200 mit den echten Nutzerdaten, OHNE Cookie → 401 | +| 3 | Code-Review: keine Änderung an internal/auth/, internal/authtoken/, internal/totp/ selbst, nur main.go+systemd neu | **bestanden** – `git diff --stat` bestätigt: alle vier Pakete unverändert gegenüber IAM-08 | + +## Echte Verdrahtung auf 192.168.1.131 + +- `account-api` gebaut nach `/opt/nexarch-core/bin/`, + `/etc/nexarch/account-api.env` (0600), `nexarch-account-api.service` + installiert/aktiviert. +- Migrationen `0001_users`, `0002_users_password`, `0003_totp`, + `0003_password_tokens` real auf `tenant_acme` angewendet (fehlten + bisher dort) — `nexarch_core` hatte zusätzlich keine + CREATE-Berechtigung auf `tenant_acme`, Migrationen daher als + `postgres` ausgeführt (Betriebs-Erkenntnis, kein IAM-16-Defekt: DDL + läuft grundsätzlich privilegiert, DML über die Anwendungsrolle). +- Reale Grant-Lücke gefunden und behoben (gleiches Muster wie zuvor): + `nexarch_core` hatte keine Rechte auf `users`, `totp_credentials`, + `totp_recovery_codes`, `totp_policy`, `password_tokens` — `GRANT` + nachgezogen und über `information_schema.role_table_grants` + verifiziert. +- End-zu-Ende-Beweis: echter Testnutzer angelegt (`bcrypt`-Hash über + `auth.HashPassword`), Login-Fehlversuch und -Erfolg, geschützter + Endpunkt mit/ohne Cookie — alle Testdaten anschließend entfernt. + +## Build/Test-Ergebnis (192.168.1.131) + +``` +go build ./... -> clean +go vet ./... -> clean +golangci-lint run ./cmd/account-api/... -> 0 issues +``` + +Keine neuen Go-Tests nötig (kein neuer Fachcode außer main.go, die +eigentliche Logik ist bereits durch IAM-08s eigene Tests abgedeckt). + +## Gesamtergebnis + +**Bestanden.** IAM-08 ist jetzt ein real laufender, über systemd +verwalteter Dienst — Voraussetzung für RBAC-05 und jede weitere +Auth-geschützte Core-GUI. Board-`dependsOn` von RBAC-05 wurde vor +dieser Umsetzung entsprechend korrigiert.