Add secure administrator operations console

Provide TOTP-authenticated, role-controlled user and credit workflows with paginated audit data and SQL-backed statistics so operations can manage growth safely.
This commit is contained in:
Rocky
2026-08-17 15:20:34 +08:00
parent 676bfd2451
commit 1a9c518f96
76 changed files with 14602 additions and 3 deletions
+9
View File
@@ -30,6 +30,15 @@ app:
antiAbuse:
identityHmacKeyBase64: "$IDENTITY_HMAC_KEY"
tombstoneRetentionDays: "$IDENTITY_TOMBSTONE_RETENTION_DAYS:365"
admin:
enabled: "$ADMIN_ENABLED:false"
bootstrapEnabled: "$ADMIN_BOOTSTRAP_ENABLED:false"
bootstrapOperatorId: "$ADMIN_BOOTSTRAP_OPERATOR_ID:"
bootstrapUsername: "$ADMIN_BOOTSTRAP_USERNAME:"
bootstrapPasswordHash: "$ADMIN_BOOTSTRAP_PASSWORD_HASH:"
bootstrapTotpSecretBase32: "$ADMIN_BOOTSTRAP_TOTP_SECRET_BASE32:"
sessionHours: "$ADMIN_SESSION_HOURS:8"
maximumManualGrant: "$ADMIN_MAXIMUM_MANUAL_GRANT:100000"
apple:
teamId: "$APPLE_TEAM_ID:"
keyId: "$APPLE_KEY_ID:"
@@ -0,0 +1,107 @@
CREATE TABLE admin_operators (
id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
username VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
password_hash VARCHAR(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
encrypted_totp_secret TEXT NOT NULL,
role VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
failed_login_count INT NOT NULL DEFAULT 0,
locked_until DATETIME(6) NULL,
last_totp_counter BIGINT NULL,
last_login_at DATETIME(6) NULL,
disabled_at DATETIME(6) NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uk_admin_operators_username (username),
INDEX idx_admin_operators_status_created (disabled_at, created_at),
CONSTRAINT chk_admin_operators_role
CHECK (role IN ('SUPER_ADMIN', 'SUPPORT', 'ANALYST')),
CONSTRAINT chk_admin_operators_failed_logins
CHECK (failed_login_count >= 0),
CONSTRAINT chk_admin_operators_totp_counter
CHECK (last_totp_counter IS NULL OR last_totp_counter >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE admin_sessions (
id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
operator_id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
token_hash CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
csrf_token_hash CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
expires_at DATETIME(6) NOT NULL,
revoked_at DATETIME(6) NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_admin_sessions_token_hash (token_hash),
INDEX idx_admin_sessions_operator_active (operator_id, revoked_at, expires_at),
INDEX idx_admin_sessions_expiry (expires_at),
CONSTRAINT fk_admin_sessions_operator
FOREIGN KEY (operator_id) REFERENCES admin_operators (id) ON DELETE RESTRICT,
CONSTRAINT chk_admin_sessions_expiry
CHECK (expires_at > created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE admin_audit_log (
id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
actor_operator_id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NULL,
action VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
outcome VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
target_type VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NULL,
target_id VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL,
request_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NULL,
occurred_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
INDEX idx_admin_audit_occurred (occurred_at, id),
INDEX idx_admin_audit_action_occurred (action, occurred_at),
INDEX idx_admin_audit_actor_occurred (actor_operator_id, occurred_at),
INDEX idx_admin_audit_target (target_type, target_id, occurred_at),
CONSTRAINT fk_admin_audit_actor
FOREIGN KEY (actor_operator_id) REFERENCES admin_operators (id) ON DELETE RESTRICT,
CONSTRAINT chk_admin_audit_target_pair CHECK (
(target_type IS NULL AND target_id IS NULL)
OR (target_type IS NOT NULL AND target_id IS NOT NULL)
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE admin_credit_grants (
id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
operator_id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
account_id VARCHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
amount BIGINT NOT NULL,
reason VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
idempotency_key VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
ledger_entry_id CHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
audit_log_id CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
UNIQUE KEY uk_admin_credit_grants_idempotency (idempotency_key),
UNIQUE KEY uk_admin_credit_grants_ledger (ledger_entry_id),
UNIQUE KEY uk_admin_credit_grants_audit (audit_log_id),
INDEX idx_admin_credit_grants_account_created (account_id, created_at),
INDEX idx_admin_credit_grants_operator_created (operator_id, created_at),
INDEX idx_admin_credit_grants_created (created_at),
CONSTRAINT fk_admin_credit_grants_operator
FOREIGN KEY (operator_id) REFERENCES admin_operators (id) ON DELETE RESTRICT,
CONSTRAINT fk_admin_credit_grants_ledger
FOREIGN KEY (ledger_entry_id) REFERENCES credit_ledger (id) ON DELETE RESTRICT,
CONSTRAINT fk_admin_credit_grants_audit
FOREIGN KEY (audit_log_id) REFERENCES admin_audit_log (id) ON DELETE RESTRICT,
CONSTRAINT chk_admin_credit_grants_amount CHECK (amount > 0),
CONSTRAINT chk_admin_credit_grants_reason
CHECK (CHAR_LENGTH(TRIM(reason)) BETWEEN 1 AND 500),
CONSTRAINT chk_admin_credit_grants_idempotency
CHECK (CHAR_LENGTH(idempotency_key) BETWEEN 8 AND 128)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- account_id intentionally has no foreign key: account deletion must preserve
-- the immutable, pseudonymized grant and ledger records.
-- Dashboard queries aggregate by creation time and state. These indexes avoid
-- full scans without changing existing business data or ledger semantics.
CREATE INDEX idx_accounts_created ON accounts (created_at);
CREATE INDEX idx_credit_ledger_type_created ON credit_ledger (entry_type, created_at);
CREATE INDEX idx_credit_usage_created ON credit_usage_records (created_at);
CREATE INDEX idx_referral_bindings_status_bound
ON referral_bindings (reward_status, bound_at);
-- admin_audit_log and admin_credit_grants are append-only. The production
-- runtime database role must receive SELECT/INSERT only on these tables.