feat(auth): migrate console login to passwordless
Replace console password auth with a passwordless surface — the pre-session
login doors plus an identifier-first discovery endpoint — and remove the
password paths.
- Login doors (Public, pre-session): email-OTP, passkey assertion, op.console
login with in-game approval, and setup-token redeem.
- /api/v1/auth/options: identifier-first discovery reporting which console
methods an email can use. The single sanctioned existence oracle; methods
are computed with no role branch, so staff and player accounts in the same
credential state return byte-identical bodies (staffness invisible by
construction).
- Remove password auth: drop StaffUser.PasswordHash and the /auth/login,
/auth/change-password and /users/{id}/reset-password endpoints (and test).
- Data layer: UserByEmail, verified-email uniqueness, setup-token store
(migration 0012).
- Reconcile docs/openapi.yaml with the served surface; the method/path/face/
tier parity gate (TestOpenAPIMatchesServedRoutes) passes.
- felis TUI: in-game MC bind, owner/break-glass OP provisioning, version.
- Velocity /felis command suite.
Consolidates the accumulated backend migration work; the frontend (panel/)
is left untouched. Full Go tree green on WSL (go build ./... && go test ./...).
This commit is contained in:
46 files changed
+5554
-1651
No files matched your search
@@ -122,23 +122,6 @@ func (a *API) ownerOnly(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// lockdownDuringPasswordChange fences a staff principal that still owes a
|
||||
// first-login password change to the change-password surface (spec §B). It is the
|
||||
// default-deny half of the lockdown: buildFace wraps every authenticated route
|
||||
// with it except the AllowDuringPasswordChange opt-outs, so a half-onboarded
|
||||
// account can do nothing but change its password, log out, or read /me. It is
|
||||
// nil-principal safe (the internal face sets no Principal), so it passes such
|
||||
// requests straight through and only ever acts on the external face.
|
||||
func (a *API) lockdownDuringPasswordChange(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if p := principalFromContext(r.Context()); p != nil && p.MustChangePassword {
|
||||
writeError(w, r, errPasswordChangeRequired)
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// newRequestID returns a short random hex id. crypto/rand never fails on the
|
||||
// platforms we target; on the impossible error path we fall back to a constant
|
||||
// so a request still gets a (non-unique) id rather than crashing.
|
||||
|
||||
Reference in new issue
Block a user