UI: password change enforcement + migration recovery
- Web UI admin password reset now enforces admin role server-side (was cookie-auth + CSRF only; any logged-in user could previously reset any account's password) - Added self-service password change UI at GET/PUT /profile: current_password + new_password + confirm_password; server-side equality check; lockout + Argon2id verification; revokes all other sessions on success - password_change_form.html fragment and profile.html page - Nav bar actor name now links to /profile - policy: ActionChangePassword + default rule -7 allowing human accounts to change their own password - openapi.yaml: built-in rules count updated to -7 Migration recovery: - mciasdb schema force --version N: new subcommand to clear dirty migration state without running SQL (break-glass) - schema subcommands bypass auto-migration on open so the tool stays usable when the database is dirty - Migrate(): shim no longer overrides schema_migrations when it already has an entry; duplicate-column error on the latest migration is force-cleaned and treated as success (handles columns added outside the runner) Security: - Admin role is now validated in handleAdminResetPassword before any DB access; non-admin receives 403 - handleSelfChangePassword follows identical lockout + constant-time Argon2id path as the REST self-service handler; current password required to prevent token-theft account takeover Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
60
PROGRESS.md
60
PROGRESS.md
@@ -4,6 +4,59 @@ Source of truth for current development state.
|
||||
---
|
||||
All phases complete. **v1.0.0 tagged.** All packages pass `go test ./...`; `golangci-lint run ./...` clean.
|
||||
|
||||
### 2026-03-12 — Checkpoint: password change UI enforcement + migration recovery
|
||||
|
||||
**internal/ui/handlers_accounts.go**
|
||||
- `handleAdminResetPassword`: added server-side admin role check at the top of
|
||||
the handler; any authenticated non-admin calling this route now receives 403.
|
||||
Previously only cookie validity + CSRF were checked.
|
||||
|
||||
**internal/ui/handlers_auth.go**
|
||||
- Added `handleProfilePage`: renders the new `/profile` page for any
|
||||
authenticated user.
|
||||
- Added `handleSelfChangePassword`: self-service password change for non-admin
|
||||
users; validates current password (Argon2id, lockout-checked), enforces
|
||||
server-side confirmation equality check, hashes new password, revokes all
|
||||
other sessions, audits as `{"via":"ui_self_service"}`.
|
||||
|
||||
**internal/ui/ui.go**
|
||||
- Added `ProfileData` view model.
|
||||
- Registered `GET /profile` and `PUT /profile/password` routes (cookie auth +
|
||||
CSRF; no admin role required).
|
||||
- Added `password_change_form.html` to shared template list; added `profile`
|
||||
page template.
|
||||
- Nav bar actor-name span changed to a link pointing to `/profile`.
|
||||
|
||||
**web/templates/fragments/password_change_form.html** (new)
|
||||
- HTMX form with `current_password`, `new_password`, `confirm_password` fields.
|
||||
- Client-side JS confirmation guard; server-side equality check in handler.
|
||||
|
||||
**web/templates/profile.html** (new)
|
||||
- Profile page hosting the self-service password change form.
|
||||
|
||||
**internal/db/migrate.go**
|
||||
- Compatibility shim now only calls `m.Force(legacyVersion)` when
|
||||
`schema_migrations` is completely empty (`ErrNilVersion`); leaves existing
|
||||
version entries (including dirty ones) alone to prevent re-running already-
|
||||
attempted migrations.
|
||||
- Added duplicate-column-name recovery: when `m.Up()` fails with "duplicate
|
||||
column name" and the dirty version equals `LatestSchemaVersion`, the migrator
|
||||
is force-cleaned and returns nil (handles databases where columns were added
|
||||
outside the runner before migration 006 existed).
|
||||
- Added `ForceSchemaVersion(database *DB, version int) error`: break-glass
|
||||
exported function; forces golang-migrate version without running SQL.
|
||||
|
||||
**cmd/mciasdb/schema.go**
|
||||
- Added `schema force --version N` subcommand backed by `db.ForceSchemaVersion`.
|
||||
|
||||
**cmd/mciasdb/main.go**
|
||||
- `schema` commands now open the database via `openDBRaw` (no auto-migration)
|
||||
so the tool stays usable when the database is in a dirty migration state.
|
||||
- `openDB` refactored to call `openDBRaw` then `db.Migrate`.
|
||||
- Updated usage text.
|
||||
|
||||
All tests pass; `golangci-lint run ./...` clean.
|
||||
|
||||
### 2026-03-12 — Password change: self-service and admin reset
|
||||
|
||||
Added the ability for users to change their own password and for admins to
|
||||
@@ -394,9 +447,10 @@ All tests pass (`go test ./...`); `golangci-lint run ./...` reports 0 issues.
|
||||
- `engine.go` — `Evaluate(input, operatorRules) (Effect, *Rule)`: pure function;
|
||||
merges operator rules with default rules, sorts by priority, deny-wins,
|
||||
then first allow, then default-deny
|
||||
- `defaults.go` — 6 compiled-in rules (IDs -1 to -6, Priority 0): admin
|
||||
wildcard, self-service logout/renew, self-service TOTP, system account own
|
||||
pgcreds, system account own service token, public login/validate endpoints
|
||||
- `defaults.go` — 7 compiled-in rules (IDs -1 to -7, Priority 0): admin
|
||||
wildcard, self-service logout/renew, self-service TOTP, self-service password
|
||||
change (human only), system account own pgcreds, system account own service
|
||||
token, public login/validate endpoints
|
||||
- `engine_wrapper.go` — `Engine` struct with `sync.RWMutex`; `SetRules()`
|
||||
decodes DB records; `PolicyRecord` type avoids import cycle
|
||||
- `engine_test.go` — 11 tests: DefaultDeny, AdminWildcard, SelfService*,
|
||||
|
||||
Reference in New Issue
Block a user