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
Rewrites the last two production-critical CFM endpoints for the biz.payfrit.com
Lucee removal project. Both endpoints follow the existing helpers.php patterns
with queryTimed/queryOne and are added to PUBLIC_ROUTES.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Moved directory on both dev and biz servers
- Updated nginx configs on both servers
- Added appRoot() helper, uploadsRoot() uses it
- No more hardcoded /opt/payfrit-api paths in codebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Moved uploads from Lucee webroot to /opt/payfrit-api/uploads/
- Updated nginx on both dev and biz to alias /uploads/ to new path
- Replaced luceeWebroot() with uploadsRoot() helper
- Temp files now use /opt/payfrit-api/temp/
- No more /opt/lucee or /var/www/biz.payfrit.com references
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added luceeWebroot() helper to avoid repeating the path. The previous
fix incorrectly used /var/www/biz.payfrit.com for production, but both
dev and biz use the same Lucee webroot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sendSMS() to helpers.php using Twilio REST API with cURL,
credentials loaded from config/twilio.json. Wire into sendOTP,
loginOTP, and sendLoginOTP endpoints, replacing TODO stubs.
SMS is auto-skipped on dev environments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete port of all 163 API endpoints from Lucee/CFML to PHP 8.3.
Shared helpers in api/helpers.php (DB, auth, request/response, security).
PDO prepared statements throughout. Same JSON response shapes as CFML.