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
+13
-10
@@ -52,13 +52,13 @@ func connectMethodLabel(m connectMethod) string {
|
||||
type preflightDoneMsg struct{}
|
||||
|
||||
type ownerResultMsg struct {
|
||||
username string
|
||||
displayPassword string
|
||||
mode string
|
||||
accountable string
|
||||
auditWarning string
|
||||
isOperator bool // true when an Operator was added rather than the Owner provisioned
|
||||
err error
|
||||
username string
|
||||
setupTokenURL string
|
||||
mode string
|
||||
accountable string
|
||||
auditWarning string
|
||||
isOperator bool // true when an Operator was added rather than the Owner provisioned
|
||||
err error
|
||||
}
|
||||
|
||||
// connectResultMsg is emitted by every connection method (the chooser for
|
||||
@@ -207,6 +207,9 @@ func (m *rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m.showStatus()
|
||||
}
|
||||
m.stage = stageOwner
|
||||
if m.mode == consoleModeSetup {
|
||||
return m.adopt(newMCBindModel(m.ctx, m.store, m.adminHost, m.osUser))
|
||||
}
|
||||
return m.adopt(newOwnerModel(m.ctx, m.store, m.osUser, false))
|
||||
|
||||
case menuChoiceMsg:
|
||||
@@ -228,7 +231,7 @@ func (m *rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.result.provisioned = true
|
||||
m.result.isOperator = msg.isOperator
|
||||
m.result.username = msg.username
|
||||
m.result.displayPassword = msg.displayPassword
|
||||
m.result.setupTokenURL = msg.setupTokenURL
|
||||
m.result.mode = msg.mode
|
||||
m.result.accountable = msg.accountable
|
||||
m.result.auditWarning = msg.auditWarning
|
||||
@@ -363,7 +366,7 @@ func (m *rootModel) reviewBody(stage int) string {
|
||||
if m.result.username != "" {
|
||||
b.WriteString(tuiLabel.Render("username ") + m.result.username + "\n")
|
||||
}
|
||||
b.WriteString(tuiHint.Render("Created and recorded. The one-time password was shown on the Owner step."))
|
||||
b.WriteString(tuiHint.Render("Created and recorded. The one-time setup URL was shown on the Owner step."))
|
||||
case stageConnect:
|
||||
b.WriteString(tuiOK.Render("✓ Connection") + "\n")
|
||||
b.WriteString(tuiLabel.Render("method ") + connectMethodLabel(m.result.connectMethod) + "\n")
|
||||
@@ -488,7 +491,7 @@ func (m *rootModel) showSummary() (tea.Model, tea.Cmd) {
|
||||
return m.adopt(&summaryModel{
|
||||
panelURL: m.result.panelURL,
|
||||
ownerUsername: m.result.username,
|
||||
ownerPassword: m.result.displayPassword,
|
||||
setupTokenURL: m.result.setupTokenURL,
|
||||
accessLabel: connectMethodLabel(m.result.connectMethod),
|
||||
storageLabel: m.result.storageDetail,
|
||||
routedHosts: routed,
|
||||
|
||||
Reference in new issue
Block a user