['password' => '', ...доп. поля], ...]; * * Путь к файлу — Config::get('auth', 'users_file'). * Состояние авторизации хранится в сессии (логин текущего пользователя), * ключ сессии — Config::get('auth', 'session_key') (по умолчанию 'auth_user'). */ class FileAuthDriver implements AuthDriver { /** @var string Имя cookie «запомнить меня» */ protected const REMEMBER_COOKIE = 'remember_me'; /** @var int Время жизни cookie «запомнить меня», сек (90 дней) */ protected const REMEMBER_LIFETIME = 90 * 24 * 3600; /** @var string|null Путь к файлу пользователей (если не передан — берётся из конфига) */ protected ?string $_users_file; /** @var string Ключ сессии, под которым хранится логин авторизованного пользователя */ protected string $_session_key; /** * @param string|null $users_file Путь к файлу пользователей (для тестов/переопределения) * @param string|null $session_key Ключ сессии (для тестов/переопределения) */ public function __construct(?string $users_file = null, ?string $session_key = null) { $this->_users_file = $users_file ?? Config::get('auth', 'users_file') ?? null; $this->_session_key = $session_key ?? Config::get('auth', 'session_key') ?? 'auth_user'; } /** * Загружает список пользователей из файла (с полем 'password'). * * @return array */ protected function users(): array { if ($this->_users_file === null || !file_exists($this->_users_file)) { return []; } return (array)(include $this->_users_file); } /** * Логин текущего авторизованного пользователя: из сессии, либо (если сессии * нет) восстанавливается из cookie «запомнить меня» — тогда же выставляется * обратно и в сессию, чтобы не проверять cookie на каждом обращении. * * @return string|null */ protected function currentLogin(): ?string { $login = Session::instance()->get($this->_session_key); if ($login !== null) { return $login; } return $this->restoreFromRememberCookie(); } /** * @inheritDoc */ public function login(string $login, string $password): bool { if (!$this->verifyCredentials($login, $password)) { return false; } Session::instance()->set($this->_session_key, $login); return true; } /** * @inheritDoc */ public function verifyCredentials(string $login, string $password): bool { $user = $this->users()[$login] ?? null; return $user !== null && password_verify($password, $user['password'] ?? ''); } /** * @inheritDoc */ public function forceLogin(string $login, bool $remember = false): void { Session::instance()->set($this->_session_key, $login); if ($remember) { $this->setRememberCookie($login); } } /** * @inheritDoc */ public function logout(): void { Session::instance()->delete($this->_session_key); Cookie::delete(self::REMEMBER_COOKIE); } /** * Ставит cookie «запомнить меня»: логин + HMAC-подпись на хэше пароля * пользователя (не отдельный секрет — при смене пароля хэш меняется, * старые cookie сами становятся недействительными). Без серверного * хранилища токенов — не нужно для файлового драйвера. * * @param string $login * @return void */ protected function setRememberCookie(string $login): void { $hash = $this->users()[$login]['password'] ?? null; if ($hash === null) { return; } Cookie::set(self::REMEMBER_COOKIE, $login . '|' . hash_hmac('sha256', $login, $hash), self::REMEMBER_LIFETIME); } /** * Проверяет cookie «запомнить меня» и, если подпись верна, восстанавливает * сессию для этого логина. * * @return string|null Логин, если cookie валиден */ protected function restoreFromRememberCookie(): ?string { $cookie = Cookie::get(self::REMEMBER_COOKIE); if ($cookie === null || !str_contains($cookie, '|')) { return null; } [$login, $signature] = explode('|', $cookie, 2); $hash = $this->users()[$login]['password'] ?? null; if ($hash === null || !hash_equals(hash_hmac('sha256', $login, $hash), $signature)) { return null; } Session::instance()->set($this->_session_key, $login); return $login; } /** * @inheritDoc */ public function loggedIn(): bool { return $this->getUser() !== null; } /** * @inheritDoc */ public function getUser(): ?array { $login = $this->currentLogin(); if ($login === null) { return null; } $user = $this->users()[$login] ?? null; if ($user === null) { return null; } unset($user['password']); return ['login' => $login] + $user; } /** * @inheritDoc */ public function checkPassword(string $password): bool { $login = $this->currentLogin(); if ($login === null) { return false; } $hash = $this->users()[$login]['password'] ?? null; return $hash !== null && password_verify($password, $hash); } }