77 lines
2.7 KiB
PHP
77 lines
2.7 KiB
PHP
<?php
|
|
/*
|
|
* SvxPortal — web front-end for SvxReflector
|
|
* Copyright (C) 2026 SA7AUX (Björn)
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License, or (at your option) any
|
|
* later version. See the LICENSE file for details.
|
|
*/
|
|
declare(strict_types=1);
|
|
// http.php — shared PHP helper for every web (dmr-web, svx-web).
|
|
// Canonical copy: callmgr2/www-mumien/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);
|
|
}
|