Files
dw-sheet-data-checker/templates/editor/setting-table-security.php

598 lines
33 KiB
PHP

<table class="table checker-setting" data-toggle="table" id="checker-security" style="display:none;">
<tbody>
<tr class="has-link" style="display: none;">
<th>Rate Limiting</th>
<td>
<p class="text-muted small mb-3">Limit the number of searches per IP address to prevent abuse and bot attacks</p>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="yes" id="security-rate-limit-enabled" name="checker[security][rate_limit][enabled]" <?= isset(
$checker["security"]["rate_limit"]["enabled"],
) && $checker["security"]["rate_limit"]["enabled"] == "yes"
? "checked"
: "" ?>>
<label class="form-check-label fw-bold" for="security-rate-limit-enabled">
Enable Rate Limiting
</label>
</div>
<div class="rate-limit-settings" style="<?= isset(
$checker["security"]["rate_limit"]["enabled"],
) && $checker["security"]["rate_limit"]["enabled"] == "yes"
? ""
: "display:none;" ?>">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Max Attempts</label>
<input type="number" name="checker[security][rate_limit][max_attempts]" value="<?= $checker[
"security"
]["rate_limit"]["max_attempts"] ??
5 ?>" class="form-control" min="1" max="100">
<small class="text-muted">Maximum searches allowed per time window (1-100)</small>
</div>
<div class="col-md-6">
<label class="form-label">Time Window (minutes)</label>
<input type="number" name="checker[security][rate_limit][time_window]" value="<?= $checker[
"security"
]["rate_limit"]["time_window"] ??
15 ?>" class="form-control" min="1" max="1440">
<small class="text-muted">Reset attempts after this duration (1-1440 minutes)</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Block Duration (minutes)</label>
<input type="number" name="checker[security][rate_limit][block_duration]" value="<?= $checker[
"security"
]["rate_limit"]["block_duration"] ??
60 ?>" class="form-control" min="1" max="10080">
<small class="text-muted">How long to block after exceeding limit (1-10080 minutes)</small>
</div>
<div class="col-md-6">
<label class="form-label">Error Message</label>
<input type="text" name="checker[security][rate_limit][error_message]" value="<?= $checker[
"security"
]["rate_limit"]["error_message"] ??
"Too many attempts. Please try again later." ?>" class="form-control">
<small class="text-muted">Message shown when blocked</small>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="yes" id="security-rate-limit-whitelist" name="checker[security][rate_limit][whitelist_enabled]" <?= isset(
$checker["security"]["rate_limit"][
"whitelist_enabled"
],
) &&
$checker["security"]["rate_limit"][
"whitelist_enabled"
] == "yes"
? "checked"
: "" ?>>
<label class="form-check-label" for="security-rate-limit-whitelist">
Enable IP Whitelist
</label>
</div>
<div id="whitelist-ips" style="<?= isset(
$checker["security"]["rate_limit"][
"whitelist_enabled"
],
) &&
$checker["security"]["rate_limit"][
"whitelist_enabled"
] == "yes"
? ""
: "display:none;" ?>" class="mt-3">
<label class="form-label">Whitelisted IPs (one per line)</label>
<textarea name="checker[security][rate_limit][whitelist_ips]" class="form-control" rows="3"><?= $checker[
"security"
]["rate_limit"]["whitelist_ips"] ??
"" ?></textarea>
<small class="text-muted">IPs that bypass rate limiting (supports CIDR notation like 192.168.1.0/24)</small>
</div>
</div>
</div>
</div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th>Google reCAPTCHA v3</th>
<td>
<div class="alert alert-info small mb-3">
<strong>How to get keys:</strong><br>
1. Go to <a href="https://www.google.com/recaptcha/admin/create" target="_blank" rel="noopener">reCAPTCHA Admin Console</a><br>
2. Create a new site with <strong>Score based (v3)</strong> type<br>
3. Add your domain (e.g., dwindi.com)<br>
4. Copy the <strong>Site Key</strong> and <strong>Secret Key</strong> shown after creation<br>
<em class="text-muted">Note: If using Google Cloud Console, click "Use Legacy Key" under Integration tab to get the secret key</em>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="yes" id="security-recaptcha-enabled" name="checker[security][recaptcha][enabled]" <?= isset(
$checker["security"]["recaptcha"]["enabled"],
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
? "checked"
: "" ?>>
<label class="form-check-label fw-bold" for="security-recaptcha-enabled">
Enable reCAPTCHA v3
</label>
</div>
<div class="recaptcha-settings" style="<?= isset(
$checker["security"]["recaptcha"]["enabled"],
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
? ""
: "display:none;" ?>">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Site Key <span class="text-danger">*</span></label>
<input type="text" name="checker[security][recaptcha][site_key]" value="<?= esc_attr($checker["security"]["recaptcha"]["site_key"] ?? "") ?>" class="form-control" placeholder="6Lc...">
<small class="text-muted">Public key for frontend - shown after creating reCAPTCHA site</small>
</div>
<div class="col-md-6">
<label class="form-label">Secret Key (Server) <span class="text-danger">*</span></label>
<input type="text" name="checker[security][recaptcha][secret_key]" value="<?= esc_attr($checker["security"]["recaptcha"]["secret_key"] ?? "") ?>" class="form-control" placeholder="Secret key from Google admin console">
<small class="text-muted">Server-side secret from Google reCAPTCHA admin console (required for verification)</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Minimum Score</label>
<input type="number" name="checker[security][recaptcha][min_score]" value="<?= $checker[
"security"
]["recaptcha"]["min_score"] ??
0.5 ?>" class="form-control" min="0" max="1" step="0.1" <?= isset(
$checker["security"]["recaptcha"]["enabled"],
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
? "required"
: "" ?>>
<small class="text-muted">0.0 (likely bot) to 1.0 (likely human). Recommended: 0.5</small>
</div>
<div class="col-md-6">
<label class="form-label">Action Name</label>
<input type="text" name="checker[security][recaptcha][action]" value="<?= $checker[
"security"
]["recaptcha"]["action"] ??
"checker_validate" ?>" class="form-control" <?= isset(
$checker["security"]["recaptcha"]["enabled"],
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
? "required"
: "" ?>>
<small class="text-muted">Action name for reCAPTCHA tracking (letters only)</small>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="yes" id="security-recaptcha-hide-badge" name="checker[security][recaptcha][hide_badge]" <?= isset(
$checker["security"]["recaptcha"][
"hide_badge"
],
) &&
$checker["security"]["recaptcha"][
"hide_badge"
] == "yes"
? "checked"
: "" ?>>
<label class="form-check-label" for="security-recaptcha-hide-badge">
Hide reCAPTCHA Badge
</label>
</div>
<small class="text-muted">Hides the "protected by reCAPTCHA" badge. You must add attribution elsewhere on the page.</small>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">Custom Error Message</label>
<input type="text" name="checker[security][recaptcha][error_message]" value="<?= esc_attr($checker["security"]["recaptcha"]["error_message"] ?? "") ?>" class="form-control" placeholder="<?= esc_attr__('Leave empty for default message', 'sheet-data-checker-pro') ?>">
<small class="text-muted"><?= esc_html__('Custom message shown when reCAPTCHA verification fails (leave empty for default)', 'sheet-data-checker-pro') ?></small>
</div>
</div>
</div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th>Cloudflare Turnstile</th>
<td>
<div class="alert alert-info small mb-3">
<strong>How to get keys:</strong><br>
1. Go to <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank" rel="noopener">Cloudflare Turnstile Dashboard</a><br>
2. Click "Add Widget" and enter your site name<br>
3. Add your domain (e.g., dwindi.com)<br>
4. Choose Widget Mode: <strong>Managed</strong> (recommended) or Non-interactive<br>
5. Copy the <strong>Site Key</strong> and <strong>Secret Key</strong> shown after creation
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="yes" id="security-turnstile-enabled" name="checker[security][turnstile][enabled]" <?= isset(
$checker["security"]["turnstile"]["enabled"],
) && $checker["security"]["turnstile"]["enabled"] == "yes"
? "checked"
: "" ?>>
<label class="form-check-label fw-bold" for="security-turnstile-enabled">
Enable Cloudflare Turnstile
</label>
</div>
<div class="turnstile-settings" style="<?= isset(
$checker["security"]["turnstile"]["enabled"],
) && $checker["security"]["turnstile"]["enabled"] == "yes"
? ""
: "display:none;" ?>">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Site Key <span class="text-danger">*</span></label>
<input type="text" name="checker[security][turnstile][site_key]" value="<?= esc_attr($checker["security"]["turnstile"]["site_key"] ?? "") ?>" class="form-control" placeholder="0x4AAA...">
<small class="text-muted">Public key for frontend (starts with 0x4AAA...)</small>
</div>
<div class="col-md-6">
<label class="form-label">Secret Key <span class="text-danger">*</span></label>
<input type="text" name="checker[security][turnstile][secret_key]" value="<?= esc_attr($checker["security"]["turnstile"]["secret_key"] ?? "") ?>" class="form-control" placeholder="0x4AAA...">
<small class="text-muted">Private key for backend verification (starts with 0x4AAA...)</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Theme</label>
<select name="checker[security][turnstile][theme]" class="form-select">
<option value="light" <?= isset(
$checker["security"]["turnstile"]["theme"],
) &&
$checker["security"]["turnstile"]["theme"] ==
"light"
? "selected"
: "" ?>>Light</option>
<option value="dark" <?= isset(
$checker["security"]["turnstile"]["theme"],
) &&
$checker["security"]["turnstile"]["theme"] ==
"dark"
? "selected"
: "" ?>>Dark</option>
<option value="auto" <?= isset(
$checker["security"]["turnstile"]["theme"],
) &&
$checker["security"]["turnstile"]["theme"] ==
"auto"
? "selected"
: "" ?>>Auto</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Size</label>
<select name="checker[security][turnstile][size]" class="form-select">
<option value="normal" <?= isset(
$checker["security"]["turnstile"]["size"],
) &&
$checker["security"]["turnstile"]["size"] ==
"normal"
? "selected"
: "" ?>>Normal</option>
<option value="compact" <?= isset(
$checker["security"]["turnstile"]["size"],
) &&
$checker["security"]["turnstile"]["size"] ==
"compact"
? "selected"
: "" ?>>Compact</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">Custom Error Message</label>
<input type="text" name="checker[security][turnstile][error_message]" value="<?= esc_attr($checker["security"]["turnstile"]["error_message"] ?? "") ?>" class="form-control" placeholder="<?= esc_attr__('Leave empty for default message', 'sheet-data-checker-pro') ?>">
<small class="text-muted"><?= esc_html__('Custom message shown when Turnstile verification fails (leave empty for default)', 'sheet-data-checker-pro') ?></small>
</div>
</div>
</div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th>Honeypot Protection</th>
<td>
<p class="text-muted small mb-3"><?= esc_html__('Invisible spam protection that catches automated bots without affecting real users', 'sheet-data-checker-pro') ?></p>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="yes" id="security-honeypot-enabled" name="checker[security][honeypot][enabled]" <?= isset($checker["security"]["honeypot"]["enabled"]) && $checker["security"]["honeypot"]["enabled"] == "yes" ? "checked" : "" ?>>
<label class="form-check-label fw-bold" for="security-honeypot-enabled">
<?= esc_html__('Enable Honeypot Field', 'sheet-data-checker-pro') ?>
</label>
</div>
<div class="honeypot-settings" style="<?= isset($checker["security"]["honeypot"]["enabled"]) && $checker["security"]["honeypot"]["enabled"] == "yes" ? "" : "display:none;" ?>">
<div class="row mb-3">
<div class="col-12">
<label class="form-label"><?= esc_html__('Custom Error Message', 'sheet-data-checker-pro') ?></label>
<input type="text" name="checker[security][honeypot][error_message]" value="<?= esc_attr($checker["security"]["honeypot"]["error_message"] ?? "") ?>" class="form-control" placeholder="<?= esc_attr__('Leave empty for default message', 'sheet-data-checker-pro') ?>">
<small class="text-muted"><?= esc_html__('Message shown when honeypot is triggered (leave empty for default)', 'sheet-data-checker-pro') ?></small>
</div>
</div>
<div class="alert alert-info small">
<i class="bi bi-info-circle"></i> <?= esc_html__('Honeypot adds an invisible field that bots will fill out, allowing easy detection without user interaction.', 'sheet-data-checker-pro') ?>
</div>
</div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th>IP Detection Method</th>
<td>
<p class="text-muted small mb-3">Configure how to detect visitor IP addresses</p>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">IP Detection Priority</label>
<div class="form-check">
<input class="form-check-input" type="radio" id="ip-auto" name="checker[security][ip_detection]" value="auto" <?= !isset(
$checker["security"]["ip_detection"],
) || $checker["security"]["ip_detection"] == "auto"
? "checked"
: "" ?>>
<label class="form-check-label" for="ip-auto">
Automatic (Recommended)
</label>
<small class="d-block text-muted">Automatically detect IP through Cloudflare, proxies, and standard headers</small>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="ip-remote-addr" name="checker[security][ip_detection]" value="remote_addr" <?= isset(
$checker["security"]["ip_detection"],
) &&
$checker["security"]["ip_detection"] ==
"remote_addr"
? "checked"
: "" ?>>
<label class="form-check-label" for="ip-remote-addr">
REMOTE_ADDR Only
</label>
<small class="d-block text-muted">Only use REMOTE_ADDR (less accurate but more predictable)</small>
</div>
</div>
</div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th>Nonce Verification</th>
<td>
<p class="text-muted small mb-3">WordPress security token to prevent CSRF attacks</p>
<div class="row mb-3">
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="yes" id="security-nonce-enabled" name="checker[security][nonce_enabled]" value="yes" checked disabled>
<label class="form-check-label" for="security-nonce-enabled">
Enable Nonce Verification (Always Active)
</label>
</div>
<small class="text-muted">
<i class="fas fa-info-circle"></i>
Nonce verification is always enabled for security. This protects against Cross-Site Request Forgery (CSRF) attacks.
</small>
</div>
</div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th>Security Status</th>
<td>
<div id="security-status" class="alert alert-warning mb-3">
<i class="fas fa-exclamation-triangle"></i>
<strong>Security Check:</strong> Please configure at least one protection method (Rate Limiting, reCAPTCHA, or Turnstile).
</div>
<button type="button" class="btn btn-sm btn-outline-primary" id="test-security-btn">
<i class="fas fa-shield-alt"></i> Test Security Settings
</button>
<div id="security-test-results" style="display:none;" class="mt-3"></div>
</td>
</tr>
<tr class="has-link" style="display: none;">
<th colspan="2">
<div class="alert alert-info mb-0">
<strong>Security Recommendations:</strong>
<ul class="mb-0 mt-2">
<li>Enable at least one protection method (Rate Limiting, reCAPTCHA, or Turnstile)</li>
<li>Only enable ONE CAPTCHA solution at a time (reCAPTCHA or Turnstile)</li>
<li>For high-traffic sites, use Rate Limiting with reCAPTCHA v3</li>
<li>Regularly review rate limiting logs for suspicious activity</li>
</ul>
</div>
</th>
</tr>
</tbody>
</table>
<script>
jQuery(document).ready(function($){
// Toggle rate limit settings
$('#security-rate-limit-enabled').on('change', function(){
if($(this).is(':checked')){
$('.rate-limit-settings').slideDown();
}else{
$('.rate-limit-settings').slideUp();
}
});
// Toggle IP whitelist
$('#security-rate-limit-whitelist').on('change', function(){
if($(this).is(':checked')){
$('#whitelist-ips').slideDown();
}else{
$('#whitelist-ips').slideUp();
}
});
// Toggle reCAPTCHA settings
$('#security-recaptcha-enabled').on('change', function(){
if($(this).is(':checked')){
$('.recaptcha-settings').slideDown();
// Disable Turnstile if reCAPTCHA is enabled
if($('#security-turnstile-enabled').is(':checked')){
if(confirm('reCAPTCHA will be enabled. Do you want to disable Turnstile?')){
$('#security-turnstile-enabled').prop('checked', false).trigger('change');
} else {
$(this).prop('checked', false);
alert('Only one CAPTCHA solution can be active at a time.');
return false;
}
}
}else{
$('.recaptcha-settings').slideUp();
}
updateSecurityStatus();
});
// Toggle Turnstile settings
$('#security-turnstile-enabled').on('change', function(){
if($(this).is(':checked')){
$('.turnstile-settings').slideDown();
// Disable reCAPTCHA if Turnstile is enabled
if($('#security-recaptcha-enabled').is(':checked')){
if(confirm('Turnstile will be enabled. Do you want to disable reCAPTCHA?')){
$('#security-recaptcha-enabled').prop('checked', false).trigger('change');
} else {
$(this).prop('checked', false);
alert('Only one CAPTCHA solution can be active at a time.');
return false;
}
}
}else{
$('.turnstile-settings').slideUp();
}
updateSecurityStatus();
});
// Honeypot toggle
$('#security-honeypot-enabled').on('change', function(){
if($(this).is(':checked')){
$('.honeypot-settings').slideDown();
}else{
$('.honeypot-settings').slideUp();
}
updateSecurityStatus();
});
// Update security status when any setting changes
$('input[type="checkbox"]').on('change', function(){
updateSecurityStatus();
});
function updateSecurityStatus() {
var rateLimitEnabled = $('#security-rate-limit-enabled').is(':checked');
var recaptchaEnabled = $('#security-recaptcha-enabled').is(':checked');
var turnstileEnabled = $('#security-turnstile-enabled').is(':checked');
var honeypotEnabled = $('#security-honeypot-enabled').is(':checked');
var statusEl = $('#security-status');
var protectionCount = 0;
var protections = [];
if(rateLimitEnabled) { protectionCount++; protections.push('Rate Limiting'); }
if(recaptchaEnabled) { protectionCount++; protections.push('reCAPTCHA'); }
if(turnstileEnabled) { protectionCount++; protections.push('Turnstile'); }
if(honeypotEnabled) { protectionCount++; protections.push('Honeypot'); }
if(protectionCount > 0) {
statusEl.removeClass('alert-warning alert-danger').addClass('alert-success');
statusEl.html('<i class="fas fa-check-circle"></i> <strong>Security Status:</strong> ' + protectionCount + ' protection(s) active: ' + protections.join(', '));
} else {
statusEl.removeClass('alert-success alert-danger').addClass('alert-warning');
statusEl.html('<i class="fas fa-exclamation-triangle"></i> <strong>Security Status:</strong> No protection enabled. Please configure at least one protection method.');
}
// Check for CAPTCHA conflict
if(recaptchaEnabled && turnstileEnabled) {
statusEl.removeClass('alert-success alert-warning').addClass('alert-danger');
statusEl.html('<i class="fas fa-exclamation-circle"></i> <strong>Security Warning:</strong> Both reCAPTCHA and Turnstile are enabled. Please disable one of them.');
}
}
// Test security settings
$('#test-security-btn').on('click', function(){
var btn = $(this);
var resultsEl = $('#security-test-results');
btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Testing...');
resultsEl.removeClass('alert-success alert-danger alert-warning').addClass('alert-info')
.html('<i class="fas fa-info-circle"></i> Running security tests...').show();
// Simulate security test
setTimeout(function(){
var issues = [];
// Check rate limiting
if(!$('#security-rate-limit-enabled').is(':checked')) {
issues.push('Rate limiting is not enabled');
}
// Check CAPTCHA
if(!$('#security-recaptcha-enabled').is(':checked') && !$('#security-turnstile-enabled').is(':checked')) {
issues.push('No CAPTCHA solution is enabled');
}
// Check for CAPTCHA conflict
if($('#security-recaptcha-enabled').is(':checked') && $('#security-turnstile-enabled').is(':checked')) {
issues.push('Both reCAPTCHA and Turnstile are enabled (conflict)');
}
// Check reCAPTCHA keys
if($('#security-recaptcha-enabled').is(':checked')) {
var siteKey = $('input[name="checker[security][recaptcha][site_key]"]').val();
var secretKey = $('input[name="checker[security][recaptcha][secret_key]"]').val();
if(!siteKey || !siteKey.startsWith('6Lc')) {
issues.push('reCAPTCHA site key is missing or invalid');
}
if(!secretKey || !secretKey.startsWith('6Lc')) {
issues.push('reCAPTCHA secret key is missing or invalid');
}
}
// Check Turnstile keys
if($('#security-turnstile-enabled').is(':checked')) {
var siteKey = $('input[name="checker[security][turnstile][site_key]"]').val();
var secretKey = $('input[name="checker[security][turnstile][secret_key]"]').val();
if(!siteKey || !siteKey.startsWith('0x4AAA')) {
issues.push('Turnstile site key is missing or invalid');
}
if(!secretKey || !secretKey.startsWith('0x4AAA')) {
issues.push('Turnstile secret key is missing or invalid');
}
}
// Display results
btn.prop('disabled', false).html('<i class="fas fa-shield-alt"></i> Test Security Settings');
if(issues.length === 0) {
resultsEl.removeClass('alert-info alert-danger alert-warning').addClass('alert-success')
.html('<i class="fas fa-check-circle"></i> <strong>All security checks passed!</strong> Your configuration is secure.');
} else {
var issuesHtml = '<ul class="mb-0">';
issues.forEach(function(issue) {
issuesHtml += '<li>' + issue + '</li>';
});
issuesHtml += '</ul>';
resultsEl.removeClass('alert-info alert-success alert-warning').addClass('alert-danger')
.html('<i class="fas fa-exclamation-circle"></i> <strong>Security issues found:</strong> ' + issuesHtml);
}
}, 1500);
});
// Initial security status update
updateSecurityStatus();
});
</script>