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
+21
-6
@@ -24,6 +24,15 @@ const hostBootstrapKubeconfigPath = "/etc/rancher/k3s/k3s.yaml"
|
||||
|
||||
var errHostBootstrapCancelled = errors.New("host bootstrap cancelled")
|
||||
|
||||
// channelName maps the --dev flag to the release channel deploy/bootstrap.sh
|
||||
// understands. Release is the default so a bare `felis setup` is production.
|
||||
func channelName(dev bool) string {
|
||||
if dev {
|
||||
return "dev"
|
||||
}
|
||||
return "release"
|
||||
}
|
||||
|
||||
// cmdSetup is the normal first-run operator console. It is intentionally separate
|
||||
// from breakGlass: setup creates the initial Owner and optional web edge; breakGlass
|
||||
// is reserved for emergency local recovery/reset.
|
||||
@@ -31,12 +40,20 @@ func cmdSetup(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("setup", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
cfgPath := fs.String("config", defaultSetupConfigPath, "path to felis.toml")
|
||||
dev := fs.Bool("dev", false, "install the dev channel (felis:dev, main HEAD) instead of the default release channel (felis:release, newest tag)")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
// The channel governs which image tag/source ref the host bootstrap builds.
|
||||
// runBootstrap forwards the whole environment, so exporting it here is enough
|
||||
// to reach deploy/bootstrap.sh without threading a parameter through the TUI.
|
||||
if err := os.Setenv("FELIS_CHANNEL", channelName(*dev)); err != nil {
|
||||
fmt.Fprintf(stderr, "felis setup: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
configFlagSet := false
|
||||
fs.Visit(func(f *flag.Flag) {
|
||||
if f.Name == "config" {
|
||||
@@ -112,18 +129,16 @@ func cmdSetup(args []string, stdout, stderr io.Writer) int {
|
||||
}
|
||||
|
||||
if res.provisioned {
|
||||
fmt.Fprintf(stdout, "\nfelis setup: Owner account %q provisioned; local-password login is ENABLED.\n", res.username)
|
||||
fmt.Fprintf(stdout, "\nfelis setup: Owner account %q provisioned (passwordless).\n", res.username)
|
||||
fmt.Fprintf(stdout, "Recorded as %q (mode: %s, os user: %s).\n", res.accountable, res.mode, res.osUser)
|
||||
if res.displayPassword != "" {
|
||||
fmt.Fprintf(stdout, "One-time password (you MUST change it on first login):\n\n %s\n\n", res.displayPassword)
|
||||
} else {
|
||||
fmt.Fprintln(stdout, "Log in with the password you just entered (you MUST change it on first login).")
|
||||
if res.setupTokenURL != "" {
|
||||
fmt.Fprintf(stdout, "Open this URL to complete passwordless login setup (verify email / enroll passkey):\n\n %s\n\n", res.setupTokenURL)
|
||||
}
|
||||
if res.auditWarning != "" {
|
||||
fmt.Fprintf(stdout, "WARNING: the accountability audit row was NOT written: %s\n", res.auditWarning)
|
||||
}
|
||||
if panelURL != "" {
|
||||
fmt.Fprintf(stdout, "Log in at %s with that username and password.\n", panelURL)
|
||||
fmt.Fprintf(stdout, "Admin console: %s\n", panelURL)
|
||||
fmt.Fprintln(stdout, "The local HTTPS certificate is self-signed; your browser may ask for confirmation on first visit.")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user