diff --git a/system/pages/account/2fa.php b/system/pages/account/2fa.php index 3d060a17..fca2bcdb 100644 --- a/system/pages/account/2fa.php +++ b/system/pages/account/2fa.php @@ -20,61 +20,102 @@ csrfProtect(); /** * @var OTS_Account $account_logged */ -$step = isset($_REQUEST['step']) ?? ''; +$step = $_REQUEST['step'] ?? ''; +$code = $_REQUEST['email-code'] ?? ''; if ((!setting('core.mail_enabled')) && ACTION == 'email-code') { $twig->display('error_box.html.twig', ['errors' => ['Account two-factor e-mail authentication disabled.']]); return; } -$twoFactorAuth = new TwoFactorAuth($account_logged); +if (!isset($account_logged) || !$account_logged->isLoaded()) { + $current_session = getSession('account'); + if($current_session) { + $account_logged->load($current_session); + } +} + +$twoFactorAuth = TwoFactorAuth::getInstance($account_logged); if (ACTION == 'email-code') { - if ($step === 'verify') { - $code = $_POST['email-code'] ?? ''; - if ($twoFactorAuth->getAuthGateway()->verifyCode($code)) { - $twoFactorAuth->getAuthGateway()->deleteOldCodes(); - - //session(['2fa_skip' => true]); - header('Location: account/manage'); - exit; - } - } - else if ($step == 'resend') { - $twoFactorAuth->getAuthGateway()->resendEmailCode(); - - $twig->display('account.2fa.email_code.html.twig'); - } - else if ($step == 'confirm-activate') { - $account2faCode = $account_logged->getCustomField('2fa_email_code'); - $account2faCodeTimeout = $account_logged->getCustomField('2fa_email_code_timeout'); - - if (!empty($account2faCodeTimeout) && time() - (int)$account2faCodeTimeout < (24 * 60 * 60)) { - $postCode = $_POST['email-code'] ?? ''; - if (!empty($account2faCode)) { - if (!empty($postCode)) { - if ($postCode == $account2faCode) { - $twig->display('account.2fa.email-code.success.html.twig'); - } - } - else { - - } - } - else { - $errors[] = 'Your account dont have 2fa E-Mail code sent.'; - - } + if ($step == 'resend') { + if ($twoFactorAuth->hasRecentEmailCode(15 * 60)) { + $errors = ['Sorry, one email per 15 minutes']; } else { - $errors[] = 'E-Mail Code expired.'; + $twoFactorAuth->resendEmailCode(); } + + if (!empty($errors)) { + $twig->display('error_box.html.twig', ['errors' => $errors]); + } + + $twig->display('account.2fa.email-code.login.html.twig'); + } + else if ($step == 'activate') { + if (!$twoFactorAuth->hasRecentEmailCode(15 * 60)) { + $twoFactorAuth->resendEmailCode(); + } + + if (isset($_POST['save'])) { + if (!empty($code)) { + $twoFactorAuth->setAuthGateway(TwoFactorAuth::TYPE_EMAIL); + if ($twoFactorAuth->getAuthGateway()->verifyCode($code)) { + $serverName = configLua('serverName'); + + $twoFactorAuth->enable(TwoFactorAuth::TYPE_EMAIL); + $twoFactorAuth->deleteOldCodes(); + + $twig->display('success.html.twig', [ + 'title' => 'Email Code Authentication Activated', + 'description' => sprintf('You have successfully activated email code authentication for your account. This means an email code will be sent to the email address assigned to your account whenever you try to log in to the %s client or the %s website. In order to log in, you will need to enter the most recent email code you have received.', $serverName, $serverName) + ]); + + return; + } + else { + $errors[] = 'Invalid email code!'; + } + } + } + + if (!empty($errors)) { + $twig->display('error_box.html.twig', ['errors' => $errors]); + } + + $twig->display('account.2fa.email_code.html.twig', ['wrongCode' => count($errors) > 0]); + } + else if ($step == 'deactivate') { + if (!$twoFactorAuth->hasRecentEmailCode(15 * 60)) { + $twoFactorAuth->resendEmailCode(); + } + + if (isset($_POST['save'])) { + if (!empty($code)) { + if ($twoFactorAuth->getAuthGateway()->verifyCode($code)) { + + $twoFactorAuth->disable(); + $twoFactorAuth->deleteOldCodes(); + + $twig->display('success.html.twig', + [ + 'title' => 'Email Code Authentication Deactivated', + 'description' => 'You have successfully deactivated the Email Code Authentication for your account.' + ] + ); + + return; + } + else { + $errors[] = 'Invalid email code!'; + } + } + } + + if (!empty($errors)) { + $twig->display('error_box.html.twig', ['errors' => $errors]); + } + + $twig->display('account.2fa.email-code.deactivate.html.twig', ['wrongCode' => count($errors) > 0]); } } - -if (!empty($errors)) { - $twig->display('error_box.html.twig', ['errors' => $errors]); - $twig->display('account.back_button.html.twig', [ - 'new_line' => true - ]); -} diff --git a/system/pages/account/login.php b/system/pages/account/login.php index b4367c32..544656a2 100644 --- a/system/pages/account/login.php +++ b/system/pages/account/login.php @@ -58,8 +58,8 @@ if(!empty($login_account) && !empty($login_password)) setSession('remember_me', true); } - $twoFactorAuth = new TwoFactorAuth($account_logged); - if (!$twoFactorAuth->process()) { + $twoFactorAuth = TwoFactorAuth::getInstance($account_logged); + if (!$twoFactorAuth->process($_POST['email-code'] ?? '')) { return; } diff --git a/system/pages/account/manage.php b/system/pages/account/manage.php index 3776a732..9b4603f7 100644 --- a/system/pages/account/manage.php +++ b/system/pages/account/manage.php @@ -8,6 +8,9 @@ * @copyright 2019 MyAAC * @link https://my-aac.org */ + +use MyAAC\TwoFactorAuth\TwoFactorAuth; + defined('MYAAC') or die('Direct access not allowed!'); $title = 'Account Management'; @@ -111,6 +114,8 @@ $twig->display('account.management.html.twig', array( 'account_registered' => $account_registered, 'account_rlname' => $account_rlname, 'account_location' => $account_location, + 'twoFactorViews' => TwoFactorAuth::getInstance($account_logged)->getAccountManageViews(), + 'actions' => $actions, - 'players' => $account_players + 'players' => $account_players, )); diff --git a/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php b/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php index 0e2a4da7..90e7f311 100644 --- a/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php +++ b/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php @@ -12,33 +12,5 @@ class EmailAuthGateway extends BaseAuthGateway implements AuthGatewayInterface { return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('code', $code)->where('created_at', '>', time() - TwoFactorAuth::EMAIL_CODE_VALID_UNTIL)->first() !== null; } - - public function hasRecentEmailCode(): bool { - return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('created_at', '>', time() - TwoFactorAuth::EMAIL_CODE_VALID_UNTIL)->first() !== null; - } - - public function deleteOldCodes(): void { - AccountEMailCode::where('account_id', '=', $this->account->getId())->delete(); - } - - public function resendEmailCode(): void - { - global $twig; - - $newCode = generateRandomString(6, true, false, true); - AccountEMailCode::create([ - 'account_id' => $this->account->getId(), - 'code' => $newCode, - 'created_at' => time(), - ]); - - $mailBody = $twig->render('mail.account.2fa.email-code.html.twig', [ - 'code' => $newCode, - ]); - - if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Requested Authentication Email Code', $mailBody)) { - error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log'); - } - } } diff --git a/system/src/TwoFactorAuth/TwoFactorAuth.php b/system/src/TwoFactorAuth/TwoFactorAuth.php index 8eafbcc5..f4de835a 100644 --- a/system/src/TwoFactorAuth/TwoFactorAuth.php +++ b/system/src/TwoFactorAuth/TwoFactorAuth.php @@ -12,43 +12,126 @@ class TwoFactorAuth const TYPE_NONE = 0; const TYPE_EMAIL = 1; const TYPE_APP = 2; + // maybe later + //const TYPE_SMS = 3; const EMAIL_CODE_VALID_UNTIL = 24 * 60 * 60; + private static TwoFactorAuth $instance; + private \OTS_Account $account; private int $authType; private EmailAuthGateway|AppAuthGateway $authGateway; - public function __construct(\OTS_Account $account) { - $this->account = $account; + public function __construct(\OTS_Account|int $account) { + if (is_int($account)) { + $this->account = new \OTS_Account(); + $this->account->load($account); + } + else { + $this->account = $account; + } $this->authType = (int)$this->account->getCustomField('2fa_type'); - if ($this->authType === self::TYPE_EMAIL) { - $this->authGateway = new EmailAuthGateway($account); - } - else if ($this->authType === self::TYPE_APP) { - $this->authGateway = new AppAuthGateway($account); - } + $this->setAuthGateway($this->authType); } - public function process() + public static function getInstance($account = null): self + { + if (!isset(self::$instance)) { + self::$instance = new self($account); + } + + return self::$instance; + } + + public function process($code): bool { global $twig; + if (!$this->isActive()) { + return true; + } + + if (!empty($code)) { + if ($this->getAuthGateway()->verifyCode($code)) { + if ($this->authType === self::TYPE_EMAIL) { + $this->deleteOldCodes(); + } + + header('Location: account/manage'); + return true; + } + else { + if (setting('core.mail_enabled')) { + $mailBody = $twig->render('mail.account.2fa.email-code.wrong-attempt.html.twig'); + + if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Failed Two-Factor Authentication Attempt', $mailBody)) { + error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log'); + } + } + + define('HIDE_LOGIN_BOX', true); + + $errors[] = 'Invalid email code!'; + $twig->display('error_box.html.twig', ['errors' => $errors]); + + $twig->display('account.2fa.email-code.login.html.twig', ['wrongCode' => true]); + + return false; + } + } + if ($this->authType == TwoFactorAuth::TYPE_EMAIL) { - if (!$this->authGateway->hasRecentEmailCode()) { - $this->authGateway->resendEmailCode(); - success('Resent email.'); + if (!$this->hasRecentEmailCode(15 * 60)) { + $this->resendEmailCode(); + //success('Resent email.'); } define('HIDE_LOGIN_BOX', true); $twig->display('account.2fa.email-code.login.html.twig'); + return false; } return true; } + public function setAuthGateway(int $authType): void + { + if ($authType === self::TYPE_EMAIL) { + $this->authGateway = new EmailAuthGateway($this->account); + } + else if ($authType === self::TYPE_APP) { + $this->authGateway = new AppAuthGateway($this->account); + } + } + + public function getAccountManageViews(): array + { + $twoFactorView = 'account.2fa.protected.html.twig'; + if ($this->authType == TwoFactorAuth::TYPE_EMAIL) { + $twoFactorView2 = 'account.2fa.email.activated.html.twig'; + } + elseif ($this->authType == TwoFactorAuth::TYPE_APP) { + $twoFactorView2 = 'account.2fa.app.activated.html.twig'; + } + else { + $twoFactorView = 'account.2fa.connect.html.twig'; + $twoFactorView2 = 'account.2fa.email.activate.html.twig'; + } + + return [$twoFactorView, $twoFactorView2]; + } + + public function enable(int $type): void { + $this->account->setCustomField('2fa_type', $type); + } + + public function disable(): void { + $this->account->setCustomField('2fa_type', self::TYPE_NONE); + } + public function isActive(): bool { return $this->authType != self::TYPE_NONE; } @@ -60,4 +143,32 @@ class TwoFactorAuth public function getAuthGateway(): AppAuthGateway|EmailAuthGateway { return $this->authGateway; } + + public function hasRecentEmailCode($since = TwoFactorAuth::EMAIL_CODE_VALID_UNTIL): bool { + return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('created_at', '>', time() - $since)->first() !== null; + } + + public function deleteOldCodes(): void { + AccountEMailCode::where('account_id', '=', $this->account->getId())->delete(); + } + + public function resendEmailCode(): void + { + global $twig; + + $newCode = generateRandomString(6, true, false, true); + AccountEMailCode::create([ + 'account_id' => $this->account->getId(), + 'code' => $newCode, + 'created_at' => time(), + ]); + + $mailBody = $twig->render('mail.account.2fa.email-code.html.twig', [ + 'code' => $newCode, + ]); + + if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Requested Authentication Email Code', $mailBody)) { + error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log'); + } + } } diff --git a/system/templates/account.2fa.connect.html.twig b/system/templates/account.2fa.connect.html.twig new file mode 100644 index 00000000..bfe70dd8 --- /dev/null +++ b/system/templates/account.2fa.connect.html.twig @@ -0,0 +1,36 @@ +
Connect your {{ config.lua.serverName }} account to an authenticator app!
+
+
+
+ |
+
+ As a first step to connect an authenticator app to your account, click on "Request"! An email with a confirmation key will be sent to the email address assigned to your account. + |
+
+
+
+
|
+ |
+
+
+
|
+ |
+
+
+
|
+
+ + | ++ + | +
+ + | ++ + | +
You have successfully activated email code authentication for your account. This means an email - code will be sent to the email address assigned to your account whenever you try to log in to the - {{ config.lua.serverName }} client or the {{ config.lua.serverName }} website. In order to log in, you will need to enter the most recent email code you have received. - | -
Activate email code authentication for your account!
+
+
+
+ |
+
+ As a first step to activate email code authentication for your account, click on "Request"! An email code will be sent to the email address assigned to your account. You will be asked to enter this email code on the next page within 24 hours. + |
+
+
+
+
+ Two-Factor Email Code Authentication Activated!
+ To deactivate email code authentication, click on the "Deactivate" button. +You will have to confirm the deactivation by entering an email code which will be sent + to the email address assigned to your account. + |
+