Add CSRF, CAPTCHA, and input validation improvements

Introduces CSRF protection to all sensitive controller actions, integrates configurable CAPTCHA (reCAPTCHA v2/v3, Turnstile) for authentication and registration flows, and centralizes input validation via a new InputValidator helper. Adds new helpers and services for CSRF and CAPTCHA, updates settings and migration for CAPTCHA configuration, and enhances logging and error handling in TLD registry import processes. Also improves validation for user, domain, group, and profile inputs throughout the application.
This commit is contained in:
Hosteroid
2025-10-10 00:04:12 +03:00
parent 98f37c2482
commit a29becc944
41 changed files with 1689 additions and 77 deletions

View File

@@ -26,6 +26,7 @@ class SettingsController extends Controller
$settings = $this->settingModel->getAllAsKeyValue();
$appSettings = $this->settingModel->getAppSettings();
$emailSettings = $this->settingModel->getEmailSettings();
$captchaSettings = $this->settingModel->getCaptchaSettings();
// Predefined notification day options
$notificationPresets = [
@@ -68,6 +69,7 @@ class SettingsController extends Controller
'settings' => $settings,
'appSettings' => $appSettings,
'emailSettings' => $emailSettings,
'captchaSettings' => $captchaSettings,
'notificationPresets' => $notificationPresets,
'checkIntervalPresets' => $checkIntervalPresets,
'title' => 'Settings'
@@ -81,6 +83,9 @@ class SettingsController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#monitoring');
try {
// Update notification days
$notificationPreset = $_POST['notification_preset'] ?? 'standard';
@@ -144,6 +149,9 @@ class SettingsController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/settings');
// Update last check run time to show the test worked
$this->settingModel->updateLastCheckRun();
@@ -158,6 +166,9 @@ class SettingsController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#maintenance');
try {
// Clear notification logs older than 30 days
$stmt = $this->settingModel->db->prepare(
@@ -182,6 +193,9 @@ class SettingsController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#app');
try {
$appSettings = [
'app_name' => trim($_POST['app_name'] ?? 'Domain Monitor'),
@@ -237,6 +251,9 @@ class SettingsController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#email');
try {
$emailSettings = [
'mail_host' => trim($_POST['mail_host'] ?? ''),
@@ -278,6 +295,79 @@ class SettingsController extends Controller
}
}
public function updateCaptcha()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/settings');
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#security');
try {
$captchaProvider = trim($_POST['captcha_provider'] ?? 'disabled');
$captchaSiteKey = trim($_POST['captcha_site_key'] ?? '');
$captchaSecretKey = trim($_POST['captcha_secret_key'] ?? '');
$recaptchaV3Threshold = trim($_POST['recaptcha_v3_score_threshold'] ?? '0.5');
// Validate provider
$validProviders = ['disabled', 'recaptcha_v2', 'recaptcha_v3', 'turnstile'];
if (!in_array($captchaProvider, $validProviders)) {
$_SESSION['error'] = 'Invalid CAPTCHA provider selected';
$this->redirect('/settings#security');
return;
}
// If CAPTCHA is enabled, validate keys
if ($captchaProvider !== 'disabled') {
if (empty($captchaSiteKey)) {
$_SESSION['error'] = 'Site key is required when CAPTCHA is enabled';
$this->redirect('/settings#security');
return;
}
if (empty($captchaSecretKey)) {
$_SESSION['error'] = 'Secret key is required when CAPTCHA is enabled';
$this->redirect('/settings#security');
return;
}
}
// Validate v3 score threshold
if ($captchaProvider === 'recaptcha_v3') {
$threshold = floatval($recaptchaV3Threshold);
if ($threshold < 0.0 || $threshold > 1.0) {
$_SESSION['error'] = 'reCAPTCHA v3 score threshold must be between 0.0 and 1.0';
$this->redirect('/settings#security');
return;
}
}
// Prepare settings array
$captchaSettings = [
'captcha_provider' => $captchaProvider,
'captcha_site_key' => $captchaSiteKey,
'recaptcha_v3_score_threshold' => $recaptchaV3Threshold
];
// Only update secret key if provided (to allow updating other settings without re-entering secret)
if (!empty($captchaSecretKey)) {
$captchaSettings['captcha_secret_key'] = $captchaSecretKey;
}
// Update CAPTCHA settings
$this->settingModel->updateCaptchaSettings($captchaSettings);
$_SESSION['success'] = 'CAPTCHA settings updated successfully';
$this->redirect('/settings#security');
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update CAPTCHA settings: ' . $e->getMessage();
$this->redirect('/settings#security');
}
}
public function testEmail()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
@@ -285,6 +375,9 @@ class SettingsController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/settings#email');
$testEmail = trim($_POST['test_email'] ?? '');
if (empty($testEmail) || !filter_var($testEmail, FILTER_VALIDATE_EMAIL)) {