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
79 lines
2.5 KiB
PHP
79 lines
2.5 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../../helpers.php';
|
|
require_once __DIR__ . '/_cronUtils.php';
|
|
requireCronSecret();
|
|
|
|
/**
|
|
* Process all due scheduled tasks.
|
|
* Called by cron every minute. Creates Tasks entries for any due ScheduledTaskDefinitions.
|
|
*/
|
|
|
|
try {
|
|
$dueTasks = queryTimed("
|
|
SELECT
|
|
ID AS ScheduledTaskID,
|
|
BusinessID,
|
|
TaskCategoryID AS CategoryID,
|
|
Title,
|
|
Details,
|
|
CronExpression,
|
|
COALESCE(ScheduleType, 'cron') AS ScheduleType,
|
|
IntervalMinutes
|
|
FROM ScheduledTaskDefinitions
|
|
WHERE IsActive = 1
|
|
AND NextRunOn <= NOW()
|
|
");
|
|
|
|
$createdTasks = [];
|
|
|
|
foreach ($dueTasks as $task) {
|
|
queryTimed("
|
|
INSERT INTO Tasks (BusinessID, CategoryID, TaskTypeID, Title, Details, CreatedOn, ClaimedByUserID)
|
|
VALUES (?, ?, 0, ?, ?, NOW(), 0)
|
|
", [
|
|
$task['BusinessID'],
|
|
$task['CategoryID'],
|
|
$task['Title'],
|
|
$task['Details'],
|
|
]);
|
|
|
|
$newTaskID = (int) lastInsertId();
|
|
|
|
// Calculate next run based on schedule type
|
|
$st = $task['ScheduleType'];
|
|
$interval = (int) ($task['IntervalMinutes'] ?? 0);
|
|
|
|
if ($st === 'interval_after_completion' && $interval > 0) {
|
|
$nextRunStr = null; // Paused until task completion
|
|
} elseif ($st === 'interval' && $interval > 0) {
|
|
$nextRun = new DateTime("+{$interval} minutes", new DateTimeZone('UTC'));
|
|
$nextRunStr = $nextRun->format('Y-m-d H:i:s');
|
|
} else {
|
|
$nextRun = calculateNextRun($task['CronExpression']);
|
|
$nextRunStr = $nextRun->format('Y-m-d H:i:s');
|
|
}
|
|
|
|
queryTimed("
|
|
UPDATE ScheduledTaskDefinitions SET LastRunOn = NOW(), NextRunOn = ?
|
|
WHERE ID = ?
|
|
", [$nextRunStr, $task['ScheduledTaskID']]);
|
|
|
|
$createdTasks[] = [
|
|
'ScheduledTaskID' => (int) $task['ScheduledTaskID'],
|
|
'TaskID' => $newTaskID,
|
|
'BusinessID' => (int) $task['BusinessID'],
|
|
'Title' => $task['Title'],
|
|
];
|
|
}
|
|
|
|
jsonResponse([
|
|
'OK' => true,
|
|
'MESSAGE' => 'Processed ' . count($createdTasks) . ' scheduled task(s)',
|
|
'CREATED_TASKS' => $createdTasks,
|
|
'CHECKED_COUNT' => count($dueTasks),
|
|
'RAN_AT' => gmdate('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]);
|
|
}
|