Merge: trusted-IP throttle exemption + session hardening
This commit is contained in:
commit
41f1b941fa
3 changed files with 43 additions and 0 deletions
|
|
@ -24,6 +24,12 @@ class JsonController extends \Controller
|
||||||
protected function throttle(string $key, int $max, int $window): bool
|
protected function throttle(string $key, int $max, int $window): bool
|
||||||
{
|
{
|
||||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||||
|
// Trusted server-local / whitelisted callers are never throttled: the static-site build
|
||||||
|
// fetches the read API from the box itself thousands of times per build. The throttle is
|
||||||
|
// for public abuse (real client IPs), not our own build. See Functions::isTrustedIp.
|
||||||
|
if (\Functions::isTrustedIp($ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$count = (int) \Db::getValue(
|
$count = (int) \Db::getValue(
|
||||||
"SELECT COUNT(*) FROM `api_requests`
|
"SELECT COUNT(*) FROM `api_requests`
|
||||||
WHERE `requesting_ip` = ? AND `request` = ?
|
WHERE `requesting_ip` = ? AND `request` = ?
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,31 @@ class Functions {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True for trusted, server-local / whitelisted callers that should bypass public rate
|
||||||
|
* limits (e.g. the SSG build hammering the read API from the box itself).
|
||||||
|
* - loopback (127.0.0.1, ::1)
|
||||||
|
* - the server's own address (same-host request)
|
||||||
|
* - anything in the optional TRUSTED_IPS config list (comma-separated IPs or CIDRs —
|
||||||
|
* e.g. the server's public IP, an internal build host, or a private range like 10.0.0.0/8)
|
||||||
|
* Deliberately does NOT blanket-trust all private ranges: behind a private-IP reverse proxy
|
||||||
|
* that would exempt every visitor. Add specific ranges to TRUSTED_IPS if your setup wants them.
|
||||||
|
*/
|
||||||
|
public static function isTrustedIp($ip) {
|
||||||
|
if ($ip === '127.0.0.1' || $ip === '::1') return true;
|
||||||
|
$serverAddr = $_SERVER['SERVER_ADDR'] ?? '';
|
||||||
|
if ($serverAddr !== '' && $ip === $serverAddr) return true;
|
||||||
|
if (defined('TRUSTED_IPS') && TRUSTED_IPS) {
|
||||||
|
foreach (array_filter(array_map('trim', explode(',', TRUSTED_IPS))) as $entry) {
|
||||||
|
if ($ip === $entry) return true;
|
||||||
|
if (strpos($entry, '/') !== false
|
||||||
|
&& filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
|
||||||
|
&& self::ip_in_range($ip, $entry)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static function ip_in_range($ip, $range) {
|
public static function ip_in_range($ip, $range) {
|
||||||
if (strpos($range, '/') == false)
|
if (strpos($range, '/') == false)
|
||||||
$range .= '/32';
|
$range .= '/32';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
|
// Harden the session cookie before it is created (login state rides on it).
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
$https = (($_SERVER['HTTPS'] ?? '') !== '' && $_SERVER['HTTPS'] !== 'off')
|
||||||
|
|| ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';
|
||||||
|
session_set_cookie_params([
|
||||||
|
'lifetime' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'httponly' => true,
|
||||||
|
'secure' => $https,
|
||||||
|
'samesite' => 'Lax',
|
||||||
|
]);
|
||||||
|
}
|
||||||
@session_start();
|
@session_start();
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
require dirname(__DIR__) . '/config.php';
|
require dirname(__DIR__) . '/config.php';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue