diff --git a/api/admin/scheduledTasks/runDue.php b/api/admin/scheduledTasks/runDue.php index 7e183d1..5a249f0 100644 --- a/api/admin/scheduledTasks/runDue.php +++ b/api/admin/scheduledTasks/runDue.php @@ -1,7 +1,7 @@ false, 'message' => $errMsg]; } +// ============================================ +// CRON AUTH +// ============================================ + +/** + * Require a valid X-Cron-Secret header for cron/scheduled task endpoints. + * The secret is read from the PAYFRIT_CRON_SECRET environment variable. + * Aborts with 403 if missing or mismatched. + */ +function requireCronSecret(): void { + $expected = trim(getenv('PAYFRIT_CRON_SECRET') ?: ''); + if ($expected === '') { + error_log('[cron_auth] PAYFRIT_CRON_SECRET env var is not set. Blocking request.'); + http_response_code(403); + jsonResponse(['OK' => false, 'ERROR' => 'cron_secret_not_configured'], 403); + } + + $provided = headerValue('X-Cron-Secret'); + if ($provided === '' || !hash_equals($expected, $provided)) { + http_response_code(403); + jsonResponse(['OK' => false, 'ERROR' => 'invalid_cron_secret'], 403); + } +} + // ============================================ // AUTH MIDDLEWARE // ============================================ @@ -525,18 +549,14 @@ function runAuth(): void { $businessId = (int) $bizHeader; } - // Check if public route + // Check if public route (exact match only) $isPublic = false; foreach (PUBLIC_ROUTES as $route) { - if (str_contains($path, strtolower($route))) { + if ($path === strtolower($route)) { $isPublic = true; break; } } - // Also allow /api/admin/ paths - if (str_contains($path, '/api/admin/')) { - $isPublic = true; - } if (!$isPublic) { if ($userId <= 0) { diff --git a/cron/expireStaleChats.php b/cron/expireStaleChats.php index 46d8371..4a24420 100644 --- a/cron/expireStaleChats.php +++ b/cron/expireStaleChats.php @@ -1,6 +1,6 @@