Player accounts backend: schema, registration, self-service, SSO provision
- Widen users.role enum to include 'player'; make password_hash nullable; add email/email_verified/status/last_login_ip; pin username _ci collation. - POST /auth/register (honeypot + registerLimiter + botScore, reserved-name blocklist, duplicate->409, auto-login). player_registration setting gates it. - SSO auto-provision in finishLogin (setting-gated); return/portal-aware SSO redirects for the player portal; status refusal on login + requireAuth. - New /player self-service group (account, change username/password, TOTP, identities), reusing account.controller; accountChangeLimiter. - Admin: 'player' role + status/email on user create/update, role/status audit, player_registration enum validation, derived public registration flags. - usernamePolicy module (reserved, sanitize, derive, dedup) + unit tests; extend SSO callback tests. 133 server tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019rao86n5cXpwAyjdBFEshV
This commit is contained in:
@@ -4,16 +4,34 @@
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(32) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(72) NOT NULL,
|
||||
role ENUM('admin','editor','moderator') NOT NULL DEFAULT 'admin',
|
||||
-- COLLATE is pinned to a case-insensitive (_ci) collation so uniqueness and
|
||||
-- findByUsername lookups both fold case identically ('Foo' == 'foo'). This is
|
||||
-- the atomic backstop for the username-uniqueness race (see the register /
|
||||
-- change-username duplicate-key handling).
|
||||
username VARCHAR(32) NOT NULL COLLATE utf8mb4_general_ci UNIQUE,
|
||||
-- Nullable: SSO-provisioned players have no password until they choose to set
|
||||
-- one. A NULL hash means password login is impossible for that account
|
||||
-- (validatePassword returns false).
|
||||
password_hash VARCHAR(72) NULL,
|
||||
role ENUM('admin','editor','moderator','player') NOT NULL DEFAULT 'admin',
|
||||
-- Optional contact email (players). Not unique — SSO emails may repeat. Used
|
||||
-- only for display + a future self-serve reset. email_verified is wired now so
|
||||
-- an eventual SMTP verification flow needs no schema change.
|
||||
email VARCHAR(255) NULL,
|
||||
email_verified TINYINT(1) NOT NULL DEFAULT 0,
|
||||
-- Account lifecycle, independent of role: staff can disable/ban a player
|
||||
-- without changing their role. active = normal; disabled = admin-locked;
|
||||
-- banned = moderation ban; pending = reserved for future email-verify gating.
|
||||
-- Enforced in requireAuth + login (non-active is rejected).
|
||||
status ENUM('active','pending','disabled','banned') NOT NULL DEFAULT 'active',
|
||||
totp_secret VARCHAR(64) NULL, -- base32 TOTP secret (opt-in 2FA)
|
||||
totp_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||
-- Any session token issued before this instant is rejected (see requireAuth).
|
||||
-- Bumped on password change / "log out everywhere". NULL = no cutoff yet.
|
||||
tokens_valid_after DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at DATETIME NULL
|
||||
last_login_at DATETIME NULL,
|
||||
last_login_ip VARCHAR(45) NULL -- IPv6-capable; set on each login
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
@@ -458,7 +476,22 @@ ALTER TABLE users ADD COLUMN IF NOT EXISTS tokens_valid_after DATETIME NULL;
|
||||
-- Moderation dashboard (Phase 6): add the 'moderator' role to databases created
|
||||
-- before it. MODIFY has no IF NOT EXISTS form, but re-declaring the same ENUM is
|
||||
-- an idempotent no-op, so it is safe to run on every boot.
|
||||
ALTER TABLE users MODIFY COLUMN role ENUM('admin','editor','moderator') NOT NULL DEFAULT 'admin';
|
||||
-- Player accounts: widen the enum again to include 'player' (self-service public
|
||||
-- accounts). Same idempotent-MODIFY pattern.
|
||||
ALTER TABLE users MODIFY COLUMN role ENUM('admin','editor','moderator','player') NOT NULL DEFAULT 'admin';
|
||||
-- Player accounts: make password_hash nullable (SSO-only players), pin the
|
||||
-- username collation (case-insensitive uniqueness backstop), and add the player
|
||||
-- columns to databases created before this. MODIFY is an idempotent no-op when
|
||||
-- the column already matches; ADD COLUMN IF NOT EXISTS is safe to re-run.
|
||||
ALTER TABLE users MODIFY COLUMN password_hash VARCHAR(72) NULL;
|
||||
ALTER TABLE users MODIFY COLUMN username VARCHAR(32) NOT NULL COLLATE utf8mb4_general_ci;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS email VARCHAR(255) NULL;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified TINYINT(1) NOT NULL DEFAULT 0;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS status ENUM('active','pending','disabled','banned') NOT NULL DEFAULT 'active';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_ip VARCHAR(45) NULL;
|
||||
-- Player self-registration mode: disabled | password | sso | both. Default off,
|
||||
-- so the system behaves exactly as today until an admin opts in.
|
||||
INSERT IGNORE INTO settings (`key`, value) VALUES ('player_registration', 'disabled');
|
||||
|
||||
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS excerpt VARCHAR(400) NULL;
|
||||
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS category_id INT NULL;
|
||||
|
||||
Reference in New Issue
Block a user