Bicycle/App/Controller/LoginController.php
2026-08-11 22:35:26 +03:00

119 lines
4.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @package Bicycle
* @author Egor Isaev
* @description LoginController.php
* @copyright (c) 12/08/2026
*/
namespace App\Controller;
use Services\Auth;
use System\Classes\Controller;
use System\Classes\HTTP;
use System\Classes\HTTP\Request as HTTPRequest;
use System\Classes\MyException;
use System\Classes\Request;
use System\Classes\Session;
use System\Classes\Validation;
/**
* Вход/выход, в два шага (логин/пароль → код с почты, пока заглушка '1234' —
* реальной отправки письма нет). /login — форма и обработка, /login/logout — выход.
*/
class LoginController extends Controller
{
/** @var string Ключ сессии — логин, прошедший первый шаг, ждёт код */
protected const PENDING_KEY = 'pending_2fa_login';
/** @var string Ключ сессии — «запомнить меня», выбрано на первом шаге, применяется на втором */
protected const REMEMBER_KEY = 'pending_2fa_remember';
/** @var string Заглушка кода из письма — реальная отправка не реализована */
protected const STUB_CODE = '1234';
/**
* @return string
* @throws MyException
*/
public function indexAction(): string
{
if (Auth::instance()->loggedIn()) {
HTTP::redirect('/');
}
$session = Session::instance();
$request = Request::$current;
if ($request->method() === HTTPRequest::POST) {
return $session->get(self::PENDING_KEY) !== null
? $this->handleCodeStep($session)
: $this->handleCredentialsStep($session);
}
return $this->renderContent('login', [
'error' => null,
'step' => $session->get(self::PENDING_KEY) !== null ? 'code' : 'credentials',
]);
}
/**
* Первый шаг — логин/пароль. При успехе не авторизует сразу, а переводит
* на шаг с кодом (см. Auth::verifyCredentials() — проверка без сессии).
*
* @param Session $session
* @return string
* @throws MyException
*/
protected function handleCredentialsStep(Session $session): string
{
$request = Request::$current;
$validation = Validation::factory($request->post())
->label('login', 'Логин')
->label('password', 'Пароль')
->rule('login', 'required')
->rule('password', 'required');
if ($validation->check() && Auth::instance()->verifyCredentials($request->post('login'), $request->post('password'))) {
$session->set(self::PENDING_KEY, $request->post('login'));
$session->set(self::REMEMBER_KEY, $request->post('remember') !== null);
return $this->renderContent('login', ['error' => null, 'step' => 'code']);
}
return $this->renderContent('login', ['error' => 'Неверный логин или пароль', 'step' => 'credentials']);
}
/**
* Второй шаг — код с почты (заглушка STUB_CODE). При успехе — Auth::forceLogin()
* (второй фактор пройден, пароль повторно не проверяется), с учётом «запомнить меня».
*
* @param Session $session
* @return string
* @throws MyException
*/
protected function handleCodeStep(Session $session): string
{
$request = Request::$current;
$login = $session->get(self::PENDING_KEY);
if ($request->post('code') === self::STUB_CODE) {
Auth::instance()->forceLogin($login, (bool)$session->get(self::REMEMBER_KEY, false));
$session->delete(self::PENDING_KEY)->delete(self::REMEMBER_KEY);
HTTP::redirect('/');
}
return $this->renderContent('login', ['error' => 'Неверный код', 'step' => 'code']);
}
/**
* @return never
*/
public function logoutAction(): never
{
Auth::instance()->logout();
HTTP::redirect('/login');
}
}