43 lines
1.6 KiB
PHP
43 lines
1.6 KiB
PHP
|
|
<?php
|
||
|
|
namespace App\Controllers;
|
||
|
|
|
||
|
|
/** Base for public (browser-callable) endpoints: origin allowlist + rate limit. */
|
||
|
|
class PublicController extends JsonController
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Cross-origin browser requests send Origin; if present and not allowlisted -> false.
|
||
|
|
* Same-origin GET / non-browser callers omit Origin -> allowed.
|
||
|
|
*/
|
||
|
|
protected function checkOrigin(): bool
|
||
|
|
{
|
||
|
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||
|
|
if ($origin === '') {
|
||
|
|
return true; // same-origin GET or server-side caller
|
||
|
|
}
|
||
|
|
$allowed = array_filter(array_map('trim', explode(',', defined('ALLOWED_ORIGINS') ? ALLOWED_ORIGINS : '')));
|
||
|
|
if (empty($allowed)) {
|
||
|
|
return true; // not configured (dev)
|
||
|
|
}
|
||
|
|
$originHost = parse_url($origin, PHP_URL_HOST);
|
||
|
|
foreach ($allowed as $a) {
|
||
|
|
$host = parse_url($a, PHP_URL_HOST) ?: $a;
|
||
|
|
if ($originHost && strcasecmp($originHost, $host) === 0) {
|
||
|
|
header('Access-Control-Allow-Origin: ' . $origin);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Guard helper: enforce origin + rate limit, or emit the error envelope and stop. */
|
||
|
|
protected function guardPublic(string $key, int $max = 60, int $window = 60): void
|
||
|
|
{
|
||
|
|
if (!$this->checkOrigin()) {
|
||
|
|
$this->json(null, 403, ['code' => 'forbidden_origin', 'message' => 'Origin not allowed']);
|
||
|
|
}
|
||
|
|
if ($this->throttle($key, $max, $window)) {
|
||
|
|
$this->json(null, 429, ['code' => 'rate_limited', 'message' => 'Too many requests']);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|