Auth2FA/app/Presentation/Sign/SignPresenter.php

160 lines
4.8 KiB
PHP

<?php
namespace App\Presentation\Sign;
use App\Model\FormData\LoginFormData;
use App\Core\MujAutentifikator;
use App\Core\TwoFactorService;
use Nette;
use Nette\Application\UI\Form;
use Nette\Application\Attributes\Persistent;
use Nette\Http\Session;
use Nette\Http\SessionSection;
use App\Model\Login\UserIdentity;
final class SignPresenter extends Nette\Application\UI\Presenter
{
/**
* Stores the previous page hash to redirect back after successful login.
*/
#[Persistent]
public string $backlink = '';
// Dočasné úložiště pro identitu před ověřením 2FA
private SessionSection $twoFactorSession;
public function __construct(
private MujAutentifikator $autentifikator,
private TwoFactorService $twoFactorService,
private Session $session
) {
$this->twoFactorSession = $session->getSection('2fa_auth');
// $secret = $this->twoFactorService->generateSecret();
// bdump($secret);
}
protected function createComponentSignInForm(): Form
{
$form = new Form();
$form->addText('username', 'Uživatelské jméno:')
->setRequired('Prosím vyplňte své uživatelské jméno.')
->setValue(@$_COOKIE['user']);
$form->addPassword('password', 'Heslo:');
// setRequired nepoužívat!! kvůli soft logoutu!!!
//->setRequired('Prosím vyplňte své heslo.');
$form->addSubmit('send', 'Přihlásit');
//$form->addProtection(); //ochrana pomocí TOKENu
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form;
}
private function signInFormSucceeded(Form $form, LoginFormData $data): void //moje vlastní třída LoginFormData
{
try {
// 1. Ověříme heslo přes autentifikátor (manuálně bez volání $this->getUser()->login())
$identity = $this->autentifikator->authenticate($data->username, $data->password);
// 2. Zjistíme, zda má uživatel aktivní 2FA (např. má v DB uložený secret)
$twoFactor = $identity->twoFactor ?? false;
if ($twoFactor) {
// Uživatel MÁ 2FA -> uložíme do session a jdeme na druhý krok
$this->twoFactorSession->identity = $identity;
$this->twoFactorSession->setExpiration('5 minutes');
$this->redirect('twoFactor');
} else {
// Uživatel NEMÁ 2FA -> přihlásíme ho hned
$this->getUser()->login($identity);
$this->restoreRequest($this->backlink);
$this->redirect(':Home:');
}
} catch (Nette\Security\AuthenticationException $e) {
$form->addError('Neplatné jméno nebo heslo.');
}
// try {
// $this->getUser()->setAuthenticator($this->autentifikator); // musíme ji registrovat!
// // validace přihlášení je v třídě MujAutentifikator->authenticate(...)
// $this->getUser()->login($data->username, $data->password);
// // kam potom?
// $this->restoreRequest($this->backlink); // vrátit se na požadovanou stránku
// $this->redirect(':Home:'); // jinak přejdi sem
// } catch (Nette\Database\ConnectionException) {
// error_log("Obvody - neplatne prihlaseni.", 0); //log je ve var/log/apache2/error.log (fail2ban)
// $form->addError('Neplatné přihlášení.');
// } catch (Nette\Security\AuthenticationException $e) {
// error_log("Obvody - neplatne prihlaseni.", 0); //log je ve var/log/apache2/error.log (fail2ban)
// $form->addError($e->getMessage());
// }
}
protected function createComponentTwoFactorForm(): Form
{
$form = new Form();
$form->addText('code', 'Ověřovací kód (OTP):')
->setRequired()
->addRule($form::Pattern, 'Kód musí mít 6 číslic', '\d{6}')
->setHtmlAttribute('placeholder', '000 000')
->setHtmlAttribute('autocomplete', 'one-time-code');
$form->addSubmit('verify', 'Ověřit a přihlásit');
$form->onSuccess[] = $this->twoFactorFormSucceeded(...);
return $form;
}
private function twoFactorFormSucceeded(Form $form, \stdClass $data): void
{
/**
* Uživatelská identita = přihlášený user.
* @var UserIdentity
*/
$identity = $this->twoFactorSession->identity;
if (!$identity) {
$this->redirect('in');
}
bdump($identity);
// Předpokládáme, že secret je uložen v identitě (z DB)
$secret = $identity->totpSecret ?? null;
if ($this->twoFactorService->verifyCode($secret, $data->code)) {
// KÓD JE SPRÁVNÝ -> Přihlásíme uživatele do Nette nativně
$this->getUser()->login($identity);
// Vyčistíme dočasnou session
$this->twoFactorSession->remove();
$this->restoreRequest($this->backlink);
$this->redirect(':Home:');
} else {
$form->addError('Neplatný ověřovací kód.');
}
}
public function renderTwoFactor(): void
{
if (!$this->twoFactorSession->identity) {
$this->redirect('in');
}
}
public function renderIn(): void
{
}
/**
* Normalní logout.
* @return void
*/
public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('Odhlášení bylo úspěšné.');
$this->redirect('Sign:in');
}
}