local sqlite3 = require("lsqlite3") local crypto = require("crypto") -- ============================================================================ -- 配置(写在脚本里) -- ============================================================================ local CONFIG = { db_path = "./card_key.db", max_attempts = 5, attempt_window = 600, ip_block_duration = 3600, device_block_duration = 86400, card_key_prefix = "CK-", card_key_length = 20, } -- ============================================================================ -- 工具函数 -- ============================================================================ local function get_current_time() return os.date("%Y-%m-%d %H:%M:%S") end local function db_connect() local db = sqlite3.open(CONFIG.db_path) db:exec("PRAGMA journal_mode = WAL") return db end local function generate_card_key() local chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" local key = "" for i = 1, CONFIG.card_key_length do local idx = math.random(1, #chars) key = key .. string.sub(chars, idx, idx) end return CONFIG.card_key_prefix .. key end local function is_ip_blocked(ip_address) local db = db_connect() local stmt = db:prepare( "SELECT COUNT(*) as cnt FROM blocked_devices WHERE ip_address = ? AND block_time > datetime('now', '-1 hour')" ) stmt:bind_values(ip_address) local result = stmt:step() stmt:finalize() db:close() return result and result.cnt > 0 end local function is_device_blocked(device_id) local db = db_connect() local stmt = db:prepare( "SELECT COUNT(*) as cnt FROM blocked_devices WHERE device_id = ? AND block_time > datetime('now', '-24 hours')" ) stmt:bind_values(device_id) local result = stmt:step() stmt:finalize() db:close() return result and result.cnt > 0 end local function get_recent_attempts(card_key, device_id, ip_address) local db = db_connect() local stmt = db:prepare( "SELECT COUNT(*) as cnt FROM login_attempts WHERE (card_key = ? OR device_id = ? OR ip_address = ?) AND success = 0 AND attempt_time > datetime('now', '-10 minutes')" ) stmt:bind_values(card_key, device_id, ip_address) local result = stmt:step() local count = result and result.cnt or 0 stmt:finalize() db:close() return count end local function block_ip(ip_address, reason) local db = db_connect() local stmt = db:prepare( "INSERT OR IGNORE INTO blocked_devices (ip_address, reason) VALUES (?, ?)" ) stmt:bind_values(ip_address, reason) stmt:step() stmt:finalize() db:close() end local function block_device(device_id, reason) local db = db_connect() local stmt = db:prepare( "INSERT OR IGNORE INTO blocked_devices (device_id, reason) VALUES (?, ?)" ) stmt:bind_values(device_id, reason) stmt:step() stmt:finalize() db:close() end -- ============================================================================ -- 核心验证逻辑 -- ============================================================================ local CardKeySystem = {} function CardKeySystem.verify(card_key, device_id, ip_address) local result = { success = false, message = "", expire_time = nil } if not card_key or card_key == "" then register_error(elgg_echo("card_key:error:no_card_key")) return result end if is_ip_blocked(ip_address) then register_error(elgg_echo("card_key:error:too_many_attempts")) block_device(device_id, "IP已被封禁") return result end if is_device_blocked(device_id) then register_error(elgg_echo("card_key:error:device_blocked")) return result end local recent_attempts = get_recent_attempts(card_key, device_id, ip_address) if recent_attempts >= CONFIG.max_attempts then register_error(elgg_echo("card_key:error:too_many_attempts")) block_ip(ip_address, "尝试次数过多") block_device(device_id, "尝试次数过多") return result end local db = db_connect() local stmt = db:prepare("SELECT * FROM card_keys WHERE card_key = ?") stmt:bind_values(card_key) local card = stmt:step() stmt:finalize() if not card then local log_stmt = db:prepare( "INSERT INTO login_attempts (card_key, device_id, ip_address, success, error_msg) VALUES (?, ?, ?, 0, ?)" ) log_stmt:bind_values(card_key, device_id, ip_address, "卡密不存在") log_stmt:step() log_stmt:finalize() db:close() register_error(elgg_echo("card_key:error:not_exist")) return result end if card.status == 0 then register_error(elgg_echo("card_key:error:disabled")) db:close() return result end if card.expire_time < get_current_time() then register_error(elgg_echo("card_key:error:expired")) db:close() return result end if card.bind_device_id and card.bind_device_id ~= device_id then register_error(elgg_echo("card_key:error:already_in_use")) local log_stmt = db:prepare( "INSERT INTO login_attempts (card_key, device_id, ip_address, success, error_msg) VALUES (?, ?, ?, 0, ?)" ) log_stmt:bind_values(card_key, device_id, ip_address, "设备不匹配") log_stmt:step() log_stmt:finalize() db:close() return result end -- 验证成功 local update_stmt = db:prepare( "UPDATE card_keys SET bind_device_id = ?, bind_time = datetime('now'), last_login_time = datetime('now'), login_count = login_count + 1 WHERE card_key = ?" ) update_stmt:bind_values(device_id, card_key) update_stmt:step() update_stmt:finalize() local log_stmt = db:prepare( "INSERT INTO login_attempts (card_key, device_id, ip_address, success) VALUES (?, ?, ?, 1)" ) log_stmt:bind_values(card_key, device_id, ip_address) log_stmt:step() log_stmt:finalize() db:close() result.success = true result.expire_time = card.expire_time system_message(string.format(elgg_echo("card_key:success"), card.expire_time)) return result end function CardKeySystem.create_card_key(expire_time) local card_key = generate_card_key() local db = db_connect() local stmt = db:prepare( "INSERT INTO card_keys (card_key, expire_time) VALUES (?, ?)" ) stmt:bind_values(card_key, expire_time) stmt:step() stmt:finalize() db:close() return card_key end function CardKeySystem.unbind_device(card_key) local db = db_connect() local stmt = db:prepare( "UPDATE card_keys SET bind_device_id = NULL, bind_time = NULL WHERE card_key = ?" ) stmt:bind_values(card_key) stmt:step() stmt:finalize() db:close() return true end function CardKeySystem.disable_card_key(card_key) local db = db_connect() local stmt = db:prepare("UPDATE card_keys SET status = 0 WHERE card_key = ?") stmt:bind_values(card_key) stmt:step() stmt:finalize() db:close() return true end function CardKeySystem.get_card_info(card_key) local db = db_connect() local stmt = db:prepare("SELECT * FROM card_keys WHERE card_key = ?") stmt:bind_values(card_key) local card = stmt:step() stmt:finalize() db:close() return card end return CardKeySystem