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. */ /** * Gate an admin-only endpoint. Emits 401 and stops unless the current * session is an authenticated admin (set by AdminAuth on login). Every * structured-content and media endpoint calls this first. */ protected function requireAdmin(): void { if (empty($_SESSION['admin']['ok'])) { $this->json(null, 401, ['code' => 'unauthorized', 'message' => 'Admin login required']); } } 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']); } } }