- 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>
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
|
|
"git.wntrmute.dev/kyle/mcias/internal/db"
|
|
)
|
|
|
|
func (t *tool) runSchema(args []string) {
|
|
if len(args) == 0 {
|
|
fatalf("schema requires a subcommand: verify, migrate, force")
|
|
}
|
|
switch args[0] {
|
|
case "verify":
|
|
t.schemaVerify()
|
|
case "migrate":
|
|
t.schemaMigrate()
|
|
case "force":
|
|
t.schemaForce(args[1:])
|
|
default:
|
|
fatalf("unknown schema subcommand %q", args[0])
|
|
}
|
|
}
|
|
|
|
// schemaVerify reports the current schema version and exits 1 if migrations
|
|
// are pending, 0 if the database is up-to-date.
|
|
func (t *tool) schemaVerify() {
|
|
version, err := db.SchemaVersion(t.db)
|
|
if err != nil {
|
|
fatalf("get schema version: %v", err)
|
|
}
|
|
latest := db.LatestSchemaVersion
|
|
fmt.Printf("schema version: %d (latest: %d)\n", version, latest)
|
|
if version < latest {
|
|
fmt.Printf("%d migration(s) pending\n", latest-version)
|
|
// Exit 1 to signal that migrations are needed (useful in scripts).
|
|
// We call os.Exit directly rather than fatalf to avoid printing "mciasdb: ".
|
|
fmt.Println("run 'mciasdb schema migrate' to apply pending migrations")
|
|
exitCode1()
|
|
}
|
|
fmt.Println("schema is up-to-date")
|
|
}
|
|
|
|
// schemaForce marks the database as being at a specific migration version
|
|
// without running any SQL. Use this to clear a dirty migration state after
|
|
// you have verified that the schema already reflects the target version.
|
|
//
|
|
// Example: mciasdb schema force --version 6
|
|
func (t *tool) schemaForce(args []string) {
|
|
fs := flag.NewFlagSet("schema force", flag.ExitOnError)
|
|
version := fs.Int("version", 0, "schema version to force (required)")
|
|
_ = fs.Parse(args)
|
|
|
|
if *version <= 0 {
|
|
fatalf("--version must be a positive integer")
|
|
}
|
|
|
|
if err := db.ForceSchemaVersion(t.db, *version); err != nil {
|
|
fatalf("force schema version: %v", err)
|
|
}
|
|
fmt.Printf("schema version forced to %d; run 'schema migrate' to apply any remaining migrations\n", *version)
|
|
}
|
|
|
|
// schemaMigrate applies any pending migrations and reports each one.
|
|
func (t *tool) schemaMigrate() {
|
|
before, err := db.SchemaVersion(t.db)
|
|
if err != nil {
|
|
fatalf("get schema version: %v", err)
|
|
}
|
|
|
|
if err := db.Migrate(t.db); err != nil {
|
|
fatalf("migrate: %v", err)
|
|
}
|
|
|
|
after, err := db.SchemaVersion(t.db)
|
|
if err != nil {
|
|
fatalf("get schema version after migrate: %v", err)
|
|
}
|
|
|
|
if before == after {
|
|
fmt.Println("no migrations needed; schema is already up-to-date")
|
|
return
|
|
}
|
|
fmt.Printf("migrated schema from version %d to %d\n", before, after)
|
|
}
|