feat(api): local-password authentication backend
Add username+password login for Owner/Operator staff accounts on op.console, the primary web login when Zero Trust is not in front of the API. Three handlers form the whole surface: login mints a server-side session cookie, logout revokes it idempotently, and change-password re-verifies the current password before rotating the hash and clearing must_change_password. - Session cookies are HttpOnly+Secure+SameSite=Lax, host-only, stored server-side as a SHA-256 hash with a 12h TTL. - Login is anti-enumeration: every failure runs a uniform bcrypt compare against a dummy hash and returns the same vague error. - Credential-bearing writes require Content-Type: application/json, returning 415 otherwise, to close the cross-site form-POST forgery vector as a belt to the SameSite cookie. - Local auth fails closed: login is rejected unless local_auth_enabled is set, so a Zero-Trust-only deployment never accepts a local password. - Extend the users table with a nullable password_hash and must_change_password; staff are role=admin rows with a hash, players are role=user rows with hash NULL. - /me now reports must_change_password so the panel can force a first-login change. Covered by Go unit tests (handlers, content-type guard, anti-enumeration, forced-change lockdown) and the OpenAPI route-parity gate.
This commit is contained in:
17 files changed
+1370
-17
No files matched your search
+11
-3
@@ -19,10 +19,18 @@ type Principal struct {
|
||||
Email string
|
||||
// Role is "admin" or "user" (mirrors users.role).
|
||||
Role string
|
||||
// ViaAdminAccess is true only when the request arrived through the admin.*
|
||||
// Zero-Trust hostname (Cloudflare Access). Admin-tier operations require it
|
||||
// in addition to Role=="admin" (spec §14: ZT is graded by operation).
|
||||
// ViaAdminAccess is true only when the request arrived through an admin-graded
|
||||
// path: the admin.* Zero-Trust hostname (Cloudflare Access, the remote face) OR
|
||||
// a local-password session presented on the op.console host (SessionAuth, the
|
||||
// break-glass-enabled face). Admin-tier operations require it in addition to
|
||||
// Role=="admin" (spec §14: ZT is graded by operation). A role=admin session
|
||||
// arriving on the player console (console.*) never sets it.
|
||||
ViaAdminAccess bool
|
||||
// MustChangePassword is set only on the local-password (SessionAuth) path when
|
||||
// the staff account still owes a first-login change. The JWT path leaves it
|
||||
// false. The lockdown middleware fences such a principal to the change-password
|
||||
// and logout surface until it is cleared.
|
||||
MustChangePassword bool
|
||||
}
|
||||
|
||||
// IsAdmin reports whether the principal may perform admin-tier operations.
|
||||
|
||||
Reference in new issue
Block a user