Files
wow-registrace/includes/functions.php
T
vojta b1d3e255e0 Respektovat X-Forwarded-For za reverzní proxí (Nginx Proxy Manager)
Bez TRUST_PROXY_HEADERS=true by rate limiting viděl u každého požadavku
IP proxy místo skutečného návštěvníka a limit by se sdílel napříč všemi
uživateli. Přidán i návod na zapojení do Nginx Proxy Manageru v README.
2026-08-05 12:05:04 +02:00

101 lines
2.9 KiB
PHP

<?php
declare(strict_types=1);
function client_ip(): string
{
// Za reverzní proxy (Nginx Proxy Manager, Cloudflare, ...) je
// REMOTE_ADDR vždy IP proxy, ne návštěvníka — rate limiting by tak
// omylem sdílel jeden limit pro úplně všechny. TRUST_PROXY_HEADERS=true
// v .env zapíná čtení X-Forwarded-For; zapínej ho JEN pokud je tenhle
// web dostupný z internetu výhradně přes tu proxy (Apache port 80 by
// neměl být veřejně dosažitelný přímo), jinak si klient IP jednoduše
// vymyslí a rate limiting úplně obejde.
if (env('TRUST_PROXY_HEADERS', 'false') === 'true') {
$forwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
if ($forwardedFor !== '') {
$first = trim(explode(',', $forwardedFor)[0]);
if (filter_var($first, FILTER_VALIDATE_IP)) {
return $first;
}
}
}
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0';
}
function csrf_token(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function csrf_verify(?string $token): bool
{
return is_string($token)
&& !empty($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
/**
* Honeypot: skryté pole, které lidský uživatel nikdy nevyplní, ale
* jednoduší boti ano.
*/
function honeypot_triggered(): bool
{
return !empty($_POST['website']);
}
/**
* @return string[] seznam chyb (prázdné pole = validní)
*/
function validate_username(string $username, array $rules): array
{
$errors = [];
$len = mb_strlen($username);
if ($len < $rules['username_min'] || $len > $rules['username_max']) {
$errors[] = sprintf(
'Uživatelské jméno musí mít %d až %d znaků.',
$rules['username_min'],
$rules['username_max']
);
}
if (!preg_match('/^[A-Za-z0-9]+$/', $username)) {
$errors[] = 'Uživatelské jméno smí obsahovat pouze písmena A-Z a číslice (bez diakritiky a speciálních znaků).';
}
return $errors;
}
function validate_password(string $password, array $rules): array
{
$errors = [];
$len = strlen($password);
if ($len < $rules['password_min'] || $len > $rules['password_max']) {
$errors[] = sprintf(
'Heslo musí mít %d až %d znaků.',
$rules['password_min'],
$rules['password_max']
);
}
if (!preg_match('/^[\x21-\x7E]+$/', $password)) {
$errors[] = 'Heslo smí obsahovat pouze běžné znaky ASCII (bez mezer a diakritiky) — herní klient WoW jiné znaky nepodporuje.';
}
return $errors;
}
function validate_email(string $email): array
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ['Zadej platnou e-mailovou adresu.'];
}
return [];
}