feat(account): migrate a live account's owned servers to a new account (§B3 inherit)
Old account runs /felis migrate in-game to open a migration, proves control via a fresh web step-up (passkey forced when enrolled, else email-OTP), names the target and mints a one-time code. The target redeems it while authenticated AS that target: in one transaction the source's owned servers re-point to the target and the source is retired (sessions revoked, disabled, soft-deleted), which also spends the code so it cannot be replayed. Only server ownership moves; the mc_uuid link and web credentials stay with the source, so migrate is not a credential-theft primitive. - 0015 migration: account_migrations state machine (initiated -> confirmed -> code_issued -> redeemed), one live migration per source - Repo/PGRepo: Start/ForSource/Confirm/IssueCode/Redeem - 8 routes (1 internal /felis side, 7 web) with openapi parity - passkey step-up runs the same clone-signal (sign-count) check as the login door - code bound to the named target at issue and at redeem Quota is grandfathered at redeem: no per-target quota re-check when servers move.
This commit is contained in:
8 files changed
+1692
No files matched your search
@@ -0,0 +1,48 @@
|
||||
-- Account migration (spec §B3 inherit): a LIVE old account hands its owned servers
|
||||
-- to a new account and is then retired. This is scenario A (the source runs
|
||||
-- /felis migrate in-game), distinct from the eviction-inherit hook on
|
||||
-- player_data_holds (scenario B, a barred UUID's stashed data).
|
||||
--
|
||||
-- State machine (one live migration per source at a time):
|
||||
-- initiated -- /felis migrate in-game put the source account into migrate mode
|
||||
-- confirmed -- source proved control via a FRESH web step-up (passkey if any is
|
||||
-- enrolled, else email-OTP) — never mere session possession
|
||||
-- code_issued -- source named the target account by id and minted a one-time code
|
||||
-- redeemed -- target logged in, entered the code; owned servers re-pointed to the
|
||||
-- target and the source account disabled (terminal)
|
||||
--
|
||||
-- The transfer moves server ownership only. It does NOT move the mc_uuid link (the
|
||||
-- target keeps the in-game identity it logged in with) nor web credentials (email /
|
||||
-- passkeys stay with the source) — moving credentials would make migrate a
|
||||
-- credential-theft primitive.
|
||||
CREATE TABLE account_migrations (
|
||||
id text PRIMARY KEY,
|
||||
source_user_id text NOT NULL REFERENCES users(id),
|
||||
target_user_id text REFERENCES users(id), -- NULL until code_issued (named at issue time)
|
||||
state text NOT NULL, -- initiated|confirmed|code_issued|redeemed
|
||||
confirm_factor text, -- 'passkey'|'email_otp'; NULL until confirmed
|
||||
confirmed_at timestamptz, -- when the step-up proof landed
|
||||
code_hash text, -- sha-256 of the one-time code; NULL until code_issued
|
||||
code_expires_at timestamptz, -- one-time code TTL; NULL until code_issued
|
||||
redeemed_at timestamptz, -- when the target spent the code (terminal)
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- At most one live (non-terminal) migration per source. Re-running /felis migrate
|
||||
-- supersedes any earlier unfinished attempt (StartMigration deletes it first), so
|
||||
-- this guards against two concurrent live migrations racing the same source.
|
||||
CREATE UNIQUE INDEX idx_account_migrations_live_source
|
||||
ON account_migrations (source_user_id)
|
||||
WHERE state <> 'redeemed';
|
||||
|
||||
-- Redeem resolves a submitted code to its pending migration. Scoped to code_issued
|
||||
-- so spent/superseded rows never match.
|
||||
CREATE INDEX idx_account_migrations_code
|
||||
ON account_migrations (code_hash)
|
||||
WHERE code_hash IS NOT NULL AND state = 'code_issued';
|
||||
|
||||
-- Reuse the shared updated_at trigger installed in 0010_user_management.sql.
|
||||
CREATE TRIGGER trg_account_migrations_updated_at
|
||||
BEFORE UPDATE ON account_migrations
|
||||
FOR EACH ROW EXECUTE FUNCTION felis_set_updated_at();
|
||||
Reference in new issue
Block a user