119 lines
4.2 KiB
PHP
119 lines
4.2 KiB
PHP
<?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');
|
||
}
|
||
}
|