feat(auth)!: go fully passwordless and fix cross-check review findings
Remove password authentication everywhere; the only session doors are passkey (WebAuthn), email OTP, in-game bind codes, QR scan-login, and op-login vouching. Remediates the 33-finding cross-check review across backend, CLI, panel, plugins, and docs. Backend/CLI: - Drop password routes and fields from account/user/onboard/auth handlers; align tests (new account subtests, naming reserves "console", op-login/onboard/qr-login test updates). - Add migrations 0016_op_login.sql and 0017_drop_password.sql. - Thread panel/admin hostnames from hostcfg through api.go, setup_panel.go, tui_root.go and tui_preflight.go instead of hardcoding; bootstrap.sh writes panel-hostname/admin-hostname into felis.toml. - Reword breakglass and TUI copy for passwordless flows. Panel: - Delete the ChangePassword page and all password UI; align login/auth/api/types with the passwordless contract; add the migration and op-login approval flows. - i18n: convert ImageBuildPage durations/status badges and ServerLuckPerms strings to translation keys; drop 72 orphan keys per locale; unify the title as "Felis - Console". Plugins (all six rebuilt): - Velocity waiting router returns 503 at_capacity during wake; MOTD/control-channel copy and config comments. - Paper zh menu title; Limbo bind-code TTL 600s with panel_url preference; unified /link lines in fabric/forge/neoforge; shared link-client javadoc contract fixes. Docs: openapi.yaml, sequence-diagrams.md, deploy/limbo/README.md and plugins/README.md aligned with the implementation. BREAKING CHANGE: migration 0017 irreversibly drops users.password_hash and users.must_change_password; password login cannot be restored after migrating.
This commit is contained in:
97 files changed
+1923
-1444
No files matched your search
@@ -0,0 +1,28 @@
|
||||
-- op.console staff sign-in (spec §B op-login): a staff account signs in at
|
||||
-- op.console with an email-OTP (minted under purpose 'op_login', stored in
|
||||
-- player_email_otp) PLUS an in-game admin vouching for the attempt via
|
||||
-- /felis web op approve <request_id>. A row here is the vouch half of that
|
||||
-- pair: it exists from the moment the OTP checks out until the approved
|
||||
-- request is exchanged for a session (consumed_at) or expires.
|
||||
--
|
||||
-- Lifecycle (derived, no state column): pending while approved_at IS NULL,
|
||||
-- approved once ApproveOpLogin stamps approved_at/approved_by, dead once
|
||||
-- consumed_at is set or expires_at passes. Both the approve and the consume
|
||||
-- UPDATE re-check the full liveness predicate, so a double approval or a
|
||||
-- replayed finish is a no-op.
|
||||
CREATE TABLE op_login_requests (
|
||||
id text PRIMARY KEY, -- opaque handle shown to the staff member and typed in-game
|
||||
user_id text NOT NULL REFERENCES users(id), -- the staff account signing in
|
||||
email text NOT NULL, -- snapshot for the audit trail (users.email may change later)
|
||||
expires_at timestamptz NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
consumed_at timestamptz, -- set exactly once by the finish path
|
||||
approved_at timestamptz, -- set by the in-game admin's approval
|
||||
approved_by text REFERENCES users(id) -- the approving admin's web account
|
||||
);
|
||||
|
||||
-- ListPendingOpLogins serves the in-game admin's approval prompt: live rows
|
||||
-- only (pending, unconsumed, unexpired), oldest first.
|
||||
CREATE INDEX idx_op_login_requests_pending
|
||||
ON op_login_requests (created_at)
|
||||
WHERE consumed_at IS NULL AND approved_at IS NULL;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Global passwordless: retire the 0003 password columns.
|
||||
-- The product no longer has a password anywhere — web sessions are minted only
|
||||
-- by the passwordless doors (passkey, email-OTP, bind code, op-login vouch) and
|
||||
-- `felis breakGlass` hands the Owner a one-time setup URL instead of a
|
||||
-- credential. No code path reads or writes these columns any more, so keeping
|
||||
-- them would preserve stale bcrypt material for an auth model that cannot use
|
||||
-- it. Dropping the hashes is deliberate and irreversible: it guarantees no
|
||||
-- legacy password can ever authenticate again.
|
||||
ALTER TABLE users
|
||||
DROP COLUMN password_hash,
|
||||
DROP COLUMN must_change_password;
|
||||
Reference in new issue
Block a user