fix: harden auth middleware — exact route matching, remove admin bypass, add cron secret
1. Switch str_contains() to exact match ($path === $route) in PUBLIC_ROUTES check to prevent substring-based route bypass attacks. 2. Remove blanket /api/admin/ bypass that was letting all admin endpoints through without authentication. 3. Add requireCronSecret() — cron/scheduled task endpoints now require a valid X-Cron-Secret header matching the PAYFRIT_CRON_SECRET env var. Uses hash_equals() for timing-safe comparison. Applied to: - cron/expireStaleChats.php - cron/expireTabs.php - api/admin/scheduledTasks/runDue.php
This commit is contained in:
parent
dde811d876
commit
601245d969
4 changed files with 29 additions and 9 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../../helpers.php';
|
||||
require_once __DIR__ . '/_cronUtils.php';
|
||||
// No runAuth() — this is a cron/public endpoint
|
||||
requireCronSecret();
|
||||
|
||||
/**
|
||||
* Process all due scheduled tasks.
|
||||
|
|
|
|||
|
|
@ -300,6 +300,30 @@ function sendSMS(string $to, string $body): array {
|
|||
return ['success' => 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) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../api/helpers.php';
|
||||
// No runAuth() — cron/public endpoint
|
||||
requireCronSecret();
|
||||
|
||||
/**
|
||||
* Expire stale chats (older than 20 minutes with no recent activity).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../api/helpers.php';
|
||||
require_once __DIR__ . '/../api/config/stripe.php';
|
||||
// No runAuth() — cron/public endpoint
|
||||
requireCronSecret();
|
||||
|
||||
/**
|
||||
* Scheduled task to handle tab expiry and cleanup.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue