hub v0.54.0: change operator login password from the Configuration UI

Adds a "Login password" card on /configuration. The password was previously
settable only via the hub-config ConfigMap (auth.password_hash) + redeploy.

- store: hub_settings key operator_password_hash + Get/SetOperatorPasswordHash
- server: passwordHash field -> configPasswordHash (seed); new
  effectivePasswordHash() (DB override wins, else seed) is now the single
  source for the CSRF gate, RequireAuth, and handleLogin
- POST /configuration/password (handleChangePassword): requires current
  password, 8-72 byte new + confirm, bcrypt cost 10, persists DB override;
  existing sessions kept valid; ConfigMap stays the break-glass reset path
- UI: current/new/confirm form + inline mismatch pre-check + 6 flashes
- tests + red-proofs: override precedence, happy-path via handleLogin,
  wrong-current rejection, mismatch/too-short/no-op, template render
- docs: CHANGELOG, README (auth+config), REUSE, REPORT

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbMm4T7Ayzs1unB9pN6Uqd
@
This commit is contained in:
2026-07-13 22:46:49 +02:00
parent ed271fadb2
commit a1d045079f
8 changed files with 440 additions and 32 deletions
+16
View File
@@ -1347,6 +1347,22 @@ const (
settingArtifactMinAgent = "artifact_min_agent"
)
// settingOperatorPasswordHash is the hub_settings key for the operator login password bcrypt hash,
// set via the Configuration UI (v0.54.0). When present it OVERRIDES the config/env seed
// (auth.password_hash in hub.yaml) — the same DB-override-wins precedence as the controller-version
// floor. The ConfigMap value stays the break-glass fallback: clear this row (or edit the manifest +
// redeploy) to reset a lost password.
const settingOperatorPasswordHash = "operator_password_hash"
// GetOperatorPasswordHash returns the UI-set operator password bcrypt hash, or "" when none has been
// set (the config/env seed is then authoritative).
func (s *Store) GetOperatorPasswordHash() string { return s.getSetting(settingOperatorPasswordHash) }
// SetOperatorPasswordHash persists a new operator password bcrypt hash set via the Configuration UI.
func (s *Store) SetOperatorPasswordHash(hash string) error {
return s.setSetting(settingOperatorPasswordHash, hash)
}
// getSetting reads a single hub_settings value ("" if the row is absent).
func (s *Store) getSetting(key string) string {
var v string