feat: rate limiting + internal health monitor + secure VAPID note
Rate limiting: - Shop POST endpoints get per-IP throttles with distinct prefixes so login, register, password-email, and password-reset have separate buckets: login/register/pw-reset = 5/min, pw-email = 3/min - OcrInvoiceService gates per-tenant via RateLimiter (30/hour) so a runaway uploader can't burn Claude Vision spend Health monitor (poor-man's monitoring): - HealthCheckCommand probes DB (SELECT 1), cache write/read, public storage write/read, and most-recent backup age. On any failure, pushes a Telegram alert via HEALTH_ALERT_BOT_TOKEN/HEALTH_ALERT_CHAT_ID. Dedups identical failures within a 30-min window via cache. - Scheduled every 10 min. Pair with external uptime monitoring (UptimeRobot, Better Stack hitting /up) for total-outage coverage. - .env.example documents the two new env vars. VAPID secret hygiene: - credentials.md no longer stores the VAPID_PRIVATE_KEY; the source of truth is the Coolify env on the autocrm app. Doc points to where to read it (UI or API). Mitigates accidental git leak. Tests (4 new): - shop login throttles after 5 attempts (6th = 429); register throttle is independent of login (separate prefix); health command runs clean; dedup cache path exercised Full suite: 138 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,15 @@ class OcrInvoiceService
|
||||
return ['ok' => false, 'error' => 'Tenant nerezolvat.'];
|
||||
}
|
||||
|
||||
// Per-tenant rate limit — caps Claude Vision spend even if a user
|
||||
// accidentally (or maliciously) submits many invoices.
|
||||
$key = 'ocr-invoice:' . $company->id;
|
||||
if (\Illuminate\Support\Facades\RateLimiter::tooManyAttempts($key, 30)) {
|
||||
$retry = \Illuminate\Support\Facades\RateLimiter::availableIn($key);
|
||||
return ['ok' => false, 'error' => "Prea multe importuri OCR. Reîncearcă în {$retry} sec."];
|
||||
}
|
||||
\Illuminate\Support\Facades\RateLimiter::hit($key, 3600); // 30 / hour
|
||||
|
||||
$key = data_get($company->settings, 'ai.claude_key');
|
||||
if (! $key) {
|
||||
return ['ok' => false, 'error' => '⚠️ Lipsește cheia Claude în Setări → AI.'];
|
||||
|
||||
Reference in New Issue
Block a user