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); }