/auth/lib/http.php; other repos carry a COPY. // JSON request/response, CSRF check, Redis-backed rate limiting. // Loaded by auth/lib.php; needs cfg() from the page's own lib.php. function json_in(): array { $raw = file_get_contents('php://input'); if ($raw === false || $raw === '') return []; $j = json_decode($raw, true); return is_array($j) ? $j : []; } function json_out(array $obj, int $code = 200): void { http_response_code($code); header('Content-Type: application/json; charset=utf-8'); // V5-14F63: prevent browser heuristic caching — every endpoint returns // live data; stale-from-cache breaks SPA pages that re-fetch on nav. header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); echo json_encode($obj, JSON_UNESCAPED_SLASHES); exit; } function csrf_token(): string { start_session(); if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function csrf_check(): void { start_session(); $token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; $expected = $_SESSION['csrf_token'] ?? ''; session_write_close(); // release the lock immediately after reading if ($token === '' || $expected === '' || !hash_equals($expected, $token)) { json_out(['ok' => false, 'error' => 'invalid_csrf_token'], 403); } } // Rate-limit key namespace per site: a site may define rate_limit_key_prefix() // (svx-web uses its own rkey() namespace); the default is callmgr's. function rate_limit_key_prefix_(): string { return function_exists('rate_limit_key_prefix') ? rate_limit_key_prefix() : '/callmgr/ratelimit/'; } function rate_limit(string $key, int $max_attempts, int $window_seconds): void { $r = redis(); $rk = rate_limit_key_prefix_() . $key; $count = (int)$r->get($rk); if ($count >= $max_attempts) { $ttl = $r->ttl($rk); json_out(['ok' => false, 'error' => "Too many attempts. Try again in {$ttl} seconds."], 429); } $r->incr($rk); if ($count === 0) { $r->expire($rk, $window_seconds); } } function rate_limit_clear(string $key): void { redis()->del(rate_limit_key_prefix_() . $key); }