- 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>
96 lines
3.9 KiB
Go
96 lines
3.9 KiB
Go
package policy
|
|
|
|
// defaultRules are the compiled-in authorization rules. They cannot be
|
|
// modified or deleted via the API. They reproduce the previous binary
|
|
// admin/non-admin behavior exactly when no operator rules exist, so wiring
|
|
// the policy engine alongside RequireRole("admin") produces identical results.
|
|
//
|
|
// All defaults use Priority 0 so they are evaluated before any operator rule
|
|
// (which defaults to Priority 100). Within priority 0, deny-wins still applies,
|
|
// but the defaults contain no Deny rules — they only grant the minimum required
|
|
// for self-service and admin operations.
|
|
//
|
|
// Security rationale for each rule is documented inline.
|
|
var defaultRules = []Rule{
|
|
{
|
|
// Admin wildcard: an account bearing the "admin" role is permitted to
|
|
// perform any action on any resource. This mirrors the previous
|
|
// RequireRole("admin") check and is the root of all administrative trust.
|
|
ID: -1,
|
|
Description: "Admin wildcard: admin role allows all actions",
|
|
Priority: 0,
|
|
Roles: []string{"admin"},
|
|
Effect: Allow,
|
|
},
|
|
{
|
|
// Self-service logout and token renewal: any authenticated principal may
|
|
// revoke or renew their own token. No resource scoping is needed because
|
|
// the handler independently verifies that the JTI belongs to the caller.
|
|
ID: -2,
|
|
Description: "Self-service: any principal may logout or renew their own token",
|
|
Priority: 0,
|
|
Actions: []Action{ActionLogout, ActionRenewToken},
|
|
Effect: Allow,
|
|
},
|
|
{
|
|
// Self-service TOTP enrollment: any authenticated human account may
|
|
// initiate and confirm their own TOTP enrollment. The handler verifies
|
|
// the subject matches before writing.
|
|
ID: -3,
|
|
Description: "Self-service: any principal may enroll their own TOTP",
|
|
Priority: 0,
|
|
Actions: []Action{ActionEnrollTOTP},
|
|
Effect: Allow,
|
|
},
|
|
{
|
|
// Self-service password change: any authenticated human account may
|
|
// change their own password. The handler derives the target exclusively
|
|
// from the JWT subject (claims.Subject) and requires the current
|
|
// password, so a non-admin caller can only affect their own account.
|
|
ID: -7,
|
|
Description: "Self-service: any human account may change their own password",
|
|
Priority: 0,
|
|
AccountTypes: []string{"human"},
|
|
Actions: []Action{ActionChangePassword},
|
|
Effect: Allow,
|
|
},
|
|
{
|
|
// System accounts reading their own pgcreds: a service that has already
|
|
// authenticated (e.g. via its bearer service token) may retrieve its own
|
|
// Postgres credentials without admin privilege. OwnerMatchesSubject
|
|
// ensures the service can only reach its own row — not another service's.
|
|
ID: -4,
|
|
Description: "System accounts may read their own pg_credentials",
|
|
Priority: 0,
|
|
AccountTypes: []string{"system"},
|
|
Actions: []Action{ActionReadPGCreds},
|
|
ResourceType: ResourcePGCreds,
|
|
OwnerMatchesSubject: true,
|
|
Effect: Allow,
|
|
},
|
|
{
|
|
// System accounts issuing or renewing their own service token: a system
|
|
// account may rotate its own bearer token. OwnerMatchesSubject ensures
|
|
// it cannot issue tokens for other accounts.
|
|
ID: -5,
|
|
Description: "System accounts may issue or renew their own service token",
|
|
Priority: 0,
|
|
AccountTypes: []string{"system"},
|
|
Actions: []Action{ActionIssueToken, ActionRenewToken},
|
|
ResourceType: ResourceToken,
|
|
OwnerMatchesSubject: true,
|
|
Effect: Allow,
|
|
},
|
|
{
|
|
// Public endpoints: token validation and login do not require
|
|
// authentication. The middleware exempts them from RequireAuth entirely;
|
|
// this rule exists so that if a policy check is accidentally applied to
|
|
// these paths, it does not block them.
|
|
ID: -6,
|
|
Description: "Public: token validation and login are always permitted",
|
|
Priority: 0,
|
|
Actions: []Action{ActionValidateToken, ActionLogin},
|
|
Effect: Allow,
|
|
},
|
|
}
|