fix: full IPs, top attacked form banner, Bearer token auth on /submit

server.js:
- Remove maskIP() — store full IPs as submitted (sanitizeIP trims/truncates only)
- Add requireToken() middleware with constant-time comparison (timingSafeEqual)
  using 128-byte padded buffers to prevent length-based timing leaks
- API_TOKEN env var — if unset the endpoint stays open (dev mode); set it in prod
- /api/v1/submit now requires Authorization: Bearer <token>

docker-compose.yml / .env.example:
- Expose API_TOKEN env var with clear comment

index.html:
- Add red-bordered 'MOST ATTACKED FORM (30D)' banner between stats and content grid
  showing form name, hit count, and % of all 30d blocks
- Widen live feed IP column 90px → 130px to fit full IPv4 addresses
- Remove 'ALL DATA IS ANONYMISED' from footer (IPs are full now)

honeypot-fields.php:
- SmartHoneypotAPIClient: add api_token to defaults + send Authorization header
- save_api_settings: persist api_token field
- Settings tab: add password input for API token with description
This commit is contained in:
2026-03-09 19:26:23 +01:00
parent 6740180981
commit bd5a67b57f
5 changed files with 105 additions and 19 deletions

View File

@@ -162,6 +162,7 @@ class SmartHoneypotAPIClient {
return [
'enabled' => false,
'api_url' => '',
'api_token' => '',
'last_sync' => 0,
'sent_total' => 0,
];
@@ -199,12 +200,17 @@ class SmartHoneypotAPIClient {
$batch = array_splice($queue, 0, self::BATCH_SIZE);
$site_hash = hash('sha256', home_url());
$headers = ['Content-Type' => 'application/json'];
if (!empty($s['api_token'])) {
$headers['Authorization'] = 'Bearer ' . $s['api_token'];
}
$response = wp_remote_post(
trailingslashit(esc_url_raw($s['api_url'])) . 'api/v1/submit',
[
'timeout' => 15,
'blocking' => true,
'headers' => ['Content-Type' => 'application/json'],
'headers' => $headers,
'body' => wp_json_encode([
'site_hash' => $site_hash,
'blocks' => $batch,
@@ -308,7 +314,8 @@ class SmartHoneypotAdmin {
$current = SmartHoneypotAPIClient::settings();
$new = [
'enabled' => !empty($_POST['hp_api_enabled']),
'api_url' => esc_url_raw(trim($_POST['hp_api_url'] ?? '')),
'api_url' => esc_url_raw(trim($_POST['hp_api_url'] ?? '')),
'api_token' => sanitize_text_field($_POST['hp_api_token'] ?? ''),
'last_sync' => $current['last_sync'],
'sent_total' => $current['sent_total'],
];
@@ -508,6 +515,17 @@ class SmartHoneypotAdmin {
<p class="description">Base URL of your Honeypot API Docker container.</p>
</td>
</tr>
<tr>
<th>API Token</th>
<td>
<input type="password" name="hp_api_token" value="<?= esc_attr($s['api_token']) ?>"
class="regular-text" placeholder="Bearer token (matches API_TOKEN in docker-compose)">
<p class="description">
Must match the <code>API_TOKEN</code> set in your Docker container's environment.
Leave empty only if the API is running without a token (not recommended).
</p>
</td>
</tr>
</table>
<?php submit_button('Save Settings'); ?>