-- ============================================================================= -- 洗车记录系统 - Migration 0002: 用户认证 + 防撞库 -- ============================================================================= -- ----------------------------------------------------------------------------- -- 1. users - 登录账号 -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE COLLATE NOCASE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user','admin')), is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)), last_login_at TEXT, last_login_ip TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active); -- ----------------------------------------------------------------------------- -- 2. login_attempts - 登录尝试记录 -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS login_attempts ( id INTEGER PRIMARY KEY AUTOINCREMENT, attempted_at TEXT NOT NULL DEFAULT (datetime('now')), ip_address TEXT NOT NULL, username TEXT NOT NULL, success INTEGER NOT NULL CHECK (success IN (0, 1)), user_agent TEXT, failure_reason TEXT ); CREATE INDEX IF NOT EXISTS idx_attempts_ip_time ON login_attempts(ip_address, attempted_at); CREATE INDEX IF NOT EXISTS idx_attempts_user_time ON login_attempts(username, attempted_at); CREATE INDEX IF NOT EXISTS idx_attempts_time ON login_attempts(attempted_at); -- ----------------------------------------------------------------------------- -- 3. auth_locks - 锁状态 -- ----------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS auth_locks ( lock_key TEXT PRIMARY KEY, lock_type TEXT NOT NULL CHECK (lock_type IN ('ip','user')), target TEXT NOT NULL, locked_until TEXT NOT NULL, reason TEXT, attempts INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_locks_until ON auth_locks(locked_until); -- ----------------------------------------------------------------------------- -- 4. auth 设置 seed -- ----------------------------------------------------------------------------- INSERT OR 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');