70 lines
1.9 KiB
PHP
70 lines
1.9 KiB
PHP
<?php
|
||
/**
|
||
* @package Bicycle
|
||
* @author Egor Isaev
|
||
* @description CSRF.php
|
||
* @copyright (c) 23/06/2026
|
||
*/
|
||
|
||
namespace System\Classes;
|
||
|
||
/**
|
||
* Защита форм от CSRF. Токен хранится в сессии, встраивается в форму
|
||
* скрытым полем и проверяется при обработке запроса.
|
||
*
|
||
* <form method="post">
|
||
* <?= CSRF::field() ?>
|
||
* ...
|
||
* </form>
|
||
*
|
||
* if (!CSRF::validate($request->post('csrf_token'))) {
|
||
* throw HTTPException::factory(403);
|
||
* }
|
||
*/
|
||
class CSRF
|
||
{
|
||
/** @var string Имя поля/ключа сессии для токена */
|
||
public static string $key = 'csrf_token';
|
||
|
||
/**
|
||
* Возвращает токен текущей сессии, создавая его при первом обращении.
|
||
*
|
||
* @return string
|
||
*/
|
||
public static function token(): string
|
||
{
|
||
$session = Session::instance();
|
||
$token = $session->get(self::$key);
|
||
|
||
if (!$token) {
|
||
$token = bin2hex(random_bytes(32));
|
||
$session->set(self::$key, $token);
|
||
}
|
||
|
||
return $token;
|
||
}
|
||
|
||
/**
|
||
* Проверяет присланный токен против токена из сессии.
|
||
*
|
||
* @param string|null $value Значение из Request::post(CSRF::$key)
|
||
* @return bool
|
||
*/
|
||
public static function validate(?string $value): bool
|
||
{
|
||
$token = Session::instance()->get(self::$key);
|
||
|
||
return is_string($value) && is_string($token) && hash_equals($token, $value);
|
||
}
|
||
|
||
/**
|
||
* Возвращает готовое скрытое поле с токеном для вставки в форму.
|
||
*
|
||
* @return string
|
||
*/
|
||
public static function field(): string
|
||
{
|
||
return '<input type="hidden" name="' . self::$key . '" value="' . self::token() . '">';
|
||
}
|
||
}
|