diff --git a/application/libraries/FusionCaptcha.php b/application/libraries/FusionCaptcha.php index f066d6a05..02d1d0264 100644 --- a/application/libraries/FusionCaptcha.php +++ b/application/libraries/FusionCaptcha.php @@ -56,6 +56,18 @@ public function generate_challenge(): array $ip = $this->get_user_ip(); $data = $this->read_ip_data($ip); + // Challenges are bound to the user-agent hash when bind_ua is enabled. + // If a different browser behind the same IP asks for a challenge, do not + // reuse the previous browser's challenge because redemption would fail + // with ua_mismatch. + if ($this->settings['bind_ua']) { + $currentUa = $this->user_agent_hash(); + if (($data['ua'] ?? '') !== $currentUa) { + $data['ua'] = $currentUa; + $data['challenge'] = null; + } + } + // Rate limit check (stored inside the same IP file) if (!$this->rate_limit_in_data($data, 'challenge', $this->settings['max_challenges_per_minute'])) { return ['success' => false, 'error' => 'rate_limited']; @@ -305,8 +317,7 @@ public function garbage_collect(): void return; } - $prefix = 'captcha/captcha_'; // prefix used in cache_save - $files = glob('writable/cache/data/' . $prefix . '*'); + $files = glob($this->cache_path() . '/captcha_*.cache'); if (!is_array($files)) { return; @@ -375,7 +386,7 @@ protected function get_user_ip(): string return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'; } - protected function cache_save(string $key, $value, int $ttl): void + protected function cache_path(): string { $cachePath = WRITEPATH . 'cache/data/captcha'; if (!is_dir($cachePath)) { @@ -384,12 +395,47 @@ protected function cache_save(string $key, $value, int $ttl): void } @chmod($cachePath, 0775); - $this->CI->cache->save('captcha/captcha_' . $key, $value, $ttl); + return $cachePath; + } + + protected function cache_file(string $key): string + { + $safeKey = preg_replace('/[^A-Za-z0-9_\-]/', '_', $key); + return $this->cache_path() . '/captcha_' . $safeKey . '.cache'; + } + + protected function cache_save(string $key, $value, int $ttl): void + { + // FusionCaptcha must keep challenge/token state even when FusionCMS' + // global data cache is disabled. + $payload = [ + 'expiration' => time() + $ttl, + 'content' => $value, + ]; + + $file = $this->cache_file($key); + @file_put_contents($file, json_encode($payload), LOCK_EX); + @chmod($file, 0640); } protected function cache_get(string $key) { - return $this->CI->cache->get('captcha/captcha_' . $key); + $file = $this->cache_file($key); + if (!is_file($file)) { + return false; + } + + $payload = json_decode((string) @file_get_contents($file), true); + if (!is_array($payload) || !array_key_exists('expiration', $payload)) { + return false; + } + + if ((int) $payload['expiration'] <= time()) { + @unlink($file); + return false; + } + + return $payload['content'] ?? false; } protected function base64url_encode(string $data): string diff --git a/application/modules/admin/language/english/admin.php b/application/modules/admin/language/english/admin.php index 5ae663f18..b6278605f 100644 --- a/application/modules/admin/language/english/admin.php +++ b/application/modules/admin/language/english/admin.php @@ -462,6 +462,7 @@ 'dark' => 'Dark', 'light' => 'Light', 'image_captcha' => 'Image captcha', + 'fusion_captcha' => 'Fusion captcha', 'google_recaptcha_v2' => 'Google reCAPTCHA v2', 'google_recaptcha_v3' => 'Google reCAPTCHA v3', 'total_players_online' => 'Total players online', diff --git a/application/modules/admin/language/france/admin.php b/application/modules/admin/language/france/admin.php index 0287d7a59..2c66a2f13 100644 --- a/application/modules/admin/language/france/admin.php +++ b/application/modules/admin/language/france/admin.php @@ -458,6 +458,7 @@ 'dark' => 'Sombre', 'light' => 'Clair', 'image_captcha' => 'Captcha d\'image', + 'fusion_captcha' => 'Captcha Fusion', 'google_recaptcha_v2' => 'Google reCAPTCHA v2', 'google_recaptcha_v3' => 'Google reCAPTCHA v3', 'total_players_online' => 'Total des joueurs en ligne', diff --git a/application/modules/admin/language/persian/admin.php b/application/modules/admin/language/persian/admin.php index e00d09c5b..d157a1133 100644 --- a/application/modules/admin/language/persian/admin.php +++ b/application/modules/admin/language/persian/admin.php @@ -458,6 +458,7 @@ 'dark' => 'تیره', 'light' => 'روشن', 'image_captcha' => 'کپچای تصویری', + 'fusion_captcha' => 'کپچای Fusion', 'google_recaptcha_v2' => 'گوگل ریکپچا v2', 'google_recaptcha_v3' => 'گوگل ریکپچا v3', 'total_players_online' => 'مجموع بازیکنان آنلاین', diff --git a/application/modules/admin/language/portuguese-brazilian/admin.php b/application/modules/admin/language/portuguese-brazilian/admin.php index 66c0cefad..d4b63d5c5 100644 --- a/application/modules/admin/language/portuguese-brazilian/admin.php +++ b/application/modules/admin/language/portuguese-brazilian/admin.php @@ -458,6 +458,7 @@ 'dark' => 'Escuro', 'light' => 'Claro', 'image_captcha' => 'Captcha de imagem', + 'fusion_captcha' => 'Captcha Fusion', 'google_recaptcha_v2' => 'Google reCAPTCHA v2', 'google_recaptcha_v3' => 'Google reCAPTCHA v3', 'total_players_online' => 'Total de jogadores online', diff --git a/application/modules/admin/language/spanish/admin.php b/application/modules/admin/language/spanish/admin.php index d6d1e2b69..4ca1105b9 100644 --- a/application/modules/admin/language/spanish/admin.php +++ b/application/modules/admin/language/spanish/admin.php @@ -458,6 +458,7 @@ 'dark' => 'Oscuro', 'light' => 'Claro', 'image_captcha' => 'Captcha de imagen', + 'fusion_captcha' => 'Captcha Fusion', 'google_recaptcha_v2' => 'Google reCAPTCHA v2', 'google_recaptcha_v3' => 'Google reCAPTCHA v3', 'total_players_online' => 'Total de jugadores en línea',