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 ./...).
21 lines
965 B
Go
21 lines
965 B
Go
package api
|
|
|
|
import "net/http"
|
|
|
|
// Passwordless auth handlers (spec §B). Staff (Owner/Operator) authenticate via
|
|
// email-OTP / passkey + in-game approve on op.console; players via bind code or
|
|
// email-OTP on console. There is NO password login path. This file holds only the
|
|
// logout handler — the login doors live in handlers_auth_email.go (email OTP),
|
|
// handlers_onboard.go (bind code), and the deferred passkey-login slice.
|
|
|
|
// handleLogout revokes the presented session and clears the cookie (spec §B). It
|
|
// is mounted Public and idempotent: it reads the cookie directly, so it works even
|
|
// when the session has already expired and never errors on a missing one.
|
|
func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) {
|
|
if c, err := r.Cookie(sessionCookieName); err == nil && c.Value != "" {
|
|
_ = a.Repo.RevokeSession(r.Context(), hashCookie(c.Value))
|
|
}
|
|
clearSessionCookie(w)
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|