Phase 4 — P4-03/04: RBAC + user management #14

Merged
steve merged 30 commits from p4-03-04-rbac-user-mgmt into main 2026-05-05 11:01:44 +01:00
Showing only changes of commit d85e82110f - Show all commits
+14 -2
View File
@@ -296,8 +296,20 @@ Sizes: **S** = under a day, **M** = 13 days, **L** = 37 days.
- [ ] **P4-01** (M) Update delivery via OS package managers — host an apt repo (Linux) and Chocolatey package (Windows) on gitea releases. `restic-manager-agent update` is a thin wrapper over `apt-get install --only-upgrade restic-manager-agent` / `choco upgrade`. Trades flexibility for a much smaller security surface than bespoke signed binaries (see spec.md §4.2)
- [ ] **P4-02** (M) Agent version reporting on dashboard: surface "agent N versions behind server"; "update all" admin action calls the package-manager wrapper on each host
- [ ] **P4-03** (M) RBAC enforcement at API layer (admin / operator / viewer)
- [ ] **P4-04** (S) User management UI (create/edit/disable, role assignment, password reset)
- [x] **P4-03** (M) RBAC enforcement at API layer (admin / operator / viewer)
- [x] **P4-04** (S) User management UI (create/edit/disable, role assignment, password reset)
> **As shipped (2026-05-05):** Three-role hierarchy (admin > operator > viewer) enforced via chi route-group middleware (`requireRole`). Admin is the fail-closed default; agent endpoints stay on the bearer-token chain. Sessions re-validate `disabled_at` on every authenticated request — admin-driven changes (disable, force-logout) land immediately.
>
> **Setup-token flow** replaces temp passwords. Admin clicks `+ Add user`, picks username + email + role, server returns a one-time setup link valid for 1 hour (sha256-hashed at rest, raw shown to admin once). User clicks the link → sets a password (≥12 chars) → drops a session → lands on `/`. `/settings/users/{id}/regenerate-setup` issues a new link, replacing the old via INSERT OR REPLACE. Expired tokens are swept on the alert engine's 60s tick.
>
> **Disable-only lifecycle** — soft delete via `disabled_at`. Last-admin guard rejects "disable last admin" and "demote last admin to non-admin" (both server-side and UI-hinted). Re-enable on disabled-username collision: admin trying to add a name that matches a disabled user is redirected to that user's edit page rather than 409'd.
>
> **Self-service password change** at `/settings/account` available to any role. Skips current-password check when `must_change_password` is set so admin-initiated resets work without surfacing a credential the user doesn't know.
>
> **Schema:** migration 0017 adds `email`, `disabled_at`, `must_change_password` plus a UNIQUE INDEX on LOWER(username) (lowercase normalisation in Go on every CreateUser); 0018 adds `user_setup_tokens`. Both column-level ALTERs per CLAUDE.md preference. Email is metadata only in v1 (no SMTP-the-link); the SMTP channel infrastructure from P3-06 makes that a one-page follow-up.
>
> **Sweep verified (smoke env):** admin adds operator → setup link generated → curl-as-new-user fetches /setup (200, page shows username) → POSTs password → 303 to / + Set-Cookie → operator authenticated → 200 on /, 200 on /settings/account, **403 on /settings/users** (admin-only) → admin disables user → operator's next request is **401** + session row count drops to 0 → audit log shows `user.created` + `user.setup_completed` for the cycle. All 26 implementation tasks landed; full `go test ./...` green.
- [ ] **P4-05** (L) OIDC login (generic provider config, group → role mapping)
- [ ] **P4-06** (M) Repo size trend graphs (sparkline on host card, full chart on repo page)
- [ ] **P4-07** (S) Per-host tags + dashboard filtering by tag