Files
dw-sheet-data-checker/includes/class-Security.php
2025-11-16 01:01:53 +07:00

218 lines
7.5 KiB
PHP

<?php
class CHECKER_SECURITY {
/**
* Check rate limit for an IP address
*
* @param int $checker_id Checker post ID
* @param string $ip IP address to check
* @return array ['allowed' => bool, 'message' => string, 'remaining' => int]
*/
public static function check_rate_limit($checker_id, $ip) {
$checker = get_post_meta($checker_id, 'checker', true);
// Check if rate limiting is enabled
if (!isset($checker['security']['rate_limit']['enabled']) || $checker['security']['rate_limit']['enabled'] !== 'yes') {
return ['allowed' => true, 'remaining' => 999];
}
// Get settings
$max_attempts = isset($checker['security']['rate_limit']['max_attempts']) ? (int)$checker['security']['rate_limit']['max_attempts'] : 5;
$time_window = isset($checker['security']['rate_limit']['time_window']) ? (int)$checker['security']['rate_limit']['time_window'] : 15;
$block_duration = isset($checker['security']['rate_limit']['block_duration']) ? (int)$checker['security']['rate_limit']['block_duration'] : 60;
$error_message = isset($checker['security']['rate_limit']['error_message']) ? $checker['security']['rate_limit']['error_message'] : 'Too many attempts. Please try again later.';
// Create transient keys
$transient_key = 'checker_rate_' . $checker_id . '_' . md5($ip);
$block_key = 'checker_block_' . $checker_id . '_' . md5($ip);
// Check if IP is blocked
$blocked_until = get_transient($block_key);
if ($blocked_until !== false) {
$remaining_time = ceil(($blocked_until - time()) / 60);
return [
'allowed' => false,
'message' => $error_message . ' (' . $remaining_time . ' minutes remaining)',
'remaining' => 0
];
}
// Get current attempts
$attempts = get_transient($transient_key);
if ($attempts === false) {
$attempts = 0;
}
// Increment attempts
$attempts++;
// Check if exceeded limit
if ($attempts > $max_attempts) {
// Block the IP
set_transient($block_key, time() + ($block_duration * 60), $block_duration * 60);
// Reset attempts counter
delete_transient($transient_key);
return [
'allowed' => false,
'message' => $error_message,
'remaining' => 0
];
}
// Update attempts counter
set_transient($transient_key, $attempts, $time_window * 60);
return [
'allowed' => true,
'remaining' => $max_attempts - $attempts
];
}
/**
* Verify reCAPTCHA v3 token
*
* @param int $checker_id Checker post ID
* @param string $token reCAPTCHA token from frontend
* @return array ['success' => bool, 'score' => float, 'message' => string]
*/
public static function verify_recaptcha($checker_id, $token) {
$checker = get_post_meta($checker_id, 'checker', true);
// Check if reCAPTCHA is enabled
if (!isset($checker['security']['recaptcha']['enabled']) || $checker['security']['recaptcha']['enabled'] !== 'yes') {
return ['success' => true, 'score' => 1.0];
}
// Get settings
$secret_key = isset($checker['security']['recaptcha']['secret_key']) ? $checker['security']['recaptcha']['secret_key'] : '';
$min_score = isset($checker['security']['recaptcha']['min_score']) ? (float)$checker['security']['recaptcha']['min_score'] : 0.5;
if (empty($secret_key) || empty($token)) {
return [
'success' => false,
'score' => 0,
'message' => 'reCAPTCHA verification failed: Missing credentials'
];
}
// Verify with Google
$response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
'body' => [
'secret' => $secret_key,
'response' => $token
]
]);
if (is_wp_error($response)) {
return [
'success' => false,
'score' => 0,
'message' => 'reCAPTCHA verification failed: ' . $response->get_error_message()
];
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!isset($body['success']) || !$body['success']) {
return [
'success' => false,
'score' => 0,
'message' => 'reCAPTCHA verification failed'
];
}
$score = isset($body['score']) ? (float)$body['score'] : 0;
if ($score < $min_score) {
return [
'success' => false,
'score' => $score,
'message' => 'reCAPTCHA score too low. Please try again.'
];
}
return [
'success' => true,
'score' => $score
];
}
/**
* Verify Cloudflare Turnstile token
*
* @param int $checker_id Checker post ID
* @param string $token Turnstile token from frontend
* @return array ['success' => bool, 'message' => string]
*/
public static function verify_turnstile($checker_id, $token) {
$checker = get_post_meta($checker_id, 'checker', true);
// Check if Turnstile is enabled
if (!isset($checker['security']['turnstile']['enabled']) || $checker['security']['turnstile']['enabled'] !== 'yes') {
return ['success' => true];
}
// Get settings
$secret_key = isset($checker['security']['turnstile']['secret_key']) ? $checker['security']['turnstile']['secret_key'] : '';
if (empty($secret_key) || empty($token)) {
return [
'success' => false,
'message' => 'Turnstile verification failed: Missing credentials'
];
}
// Verify with Cloudflare
$response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'body' => [
'secret' => $secret_key,
'response' => $token
]
]);
if (is_wp_error($response)) {
return [
'success' => false,
'message' => 'Turnstile verification failed: ' . $response->get_error_message()
];
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!isset($body['success']) || !$body['success']) {
return [
'success' => false,
'message' => 'Turnstile verification failed'
];
}
return ['success' => true];
}
/**
* Get client IP address
*
* @return string IP address
*/
public static function get_client_ip() {
$ip = '';
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
// If multiple IPs, get the first one
if (strpos($ip, ',') !== false) {
$ip = trim(explode(',', $ip)[0]);
}
return $ip;
}
}