-- 0002_auth.sql - 用户认证 + 防撞库 (MySQL) CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'user', is_active TINYINT(1) NOT NULL DEFAULT 1, last_login_at DATETIME DEFAULT NULL, last_login_ip VARCHAR(45) DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT chk_role CHECK (role IN ('user','admin')) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE INDEX idx_users_active ON users(is_active); CREATE TABLE IF NOT EXISTS login_attempts ( id INT AUTO_INCREMENT PRIMARY KEY, attempted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ip_address VARCHAR(45) NOT NULL, username VARCHAR(50) NOT NULL, success TINYINT(1) NOT NULL, user_agent VARCHAR(500) DEFAULT NULL, failure_reason VARCHAR(100) DEFAULT NULL, CONSTRAINT chk_success CHECK (success IN (0, 1)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE INDEX idx_attempts_ip_time ON login_attempts(ip_address, attempted_at); CREATE INDEX idx_attempts_user_time ON login_attempts(username, attempted_at); CREATE INDEX idx_attempts_time ON login_attempts(attempted_at); CREATE TABLE IF NOT EXISTS auth_locks ( lock_key VARCHAR(100) PRIMARY KEY, lock_type VARCHAR(10) NOT NULL, target VARCHAR(50) NOT NULL, locked_until DATETIME NOT NULL, reason VARCHAR(255) DEFAULT NULL, attempts INT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT chk_lock_type CHECK (lock_type IN ('ip','user')) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE INDEX idx_locks_until ON auth_locks(locked_until); INSERT IGNORE INTO settings (`key`, value, is_secret, description) VALUES ('session_lifetime_days', '30', 0, '登录 session 有效期(天)'), ('session_cookie_secure', 'auto', 0, 'Cookie secure 标志:true/false/auto'), ('login_max_failures_ip', '5', 0, '每 IP 允许的最大连续失败次数'), ('login_max_failures_user', '5', 0, '每用户名允许的最大连续失败次数'), ('login_lock_minutes_ip', '15', 0, 'IP 级别锁定时长(分钟)'), ('login_lock_minutes_user', '30', 0, '用户名级别锁定时长(分钟)'), ('login_global_max_failures', '10', 0, '触发全局 IP 封锁的失败次数'), ('login_global_lock_hours', '1', 0, '全局 IP 封锁时长(小时)'), ('login_attempts_retention_days', '30', 0, 'login_attempts 保留天数'), ('csrf_token_lifetime_hours', '12', 0, 'CSRF token 有效期(小时)'), ('bcrypt_cost', '12', 0, 'bcrypt cost factor');