From 867e3e2c384f8afb775cc1b1ea8ee65c236372ef Mon Sep 17 00:00:00 2001 From: slawkens Date: Wed, 21 Jan 2026 20:12:41 +0100 Subject: [PATCH] [WIP] 2fa - Optimize code, views --- system/pages/account/2fa/app/disable.php | 10 +-- system/pages/account/2fa/app/enable.php | 85 +++++++++++-------- system/pages/account/2fa/email/disable.php | 4 +- system/src/TwoFactorAuth/TwoFactorAuth.php | 53 ++++++++++-- .../account/2fa/app/enable.html.twig | 21 +++-- .../account/2fa/app/enable.warning.html.twig | 46 +++++++--- .../account/2fa/app/enabled.html.twig | 1 - .../templates/account/2fa/app/login.html.twig | 4 +- .../2fa/app/manage.connected.html.twig | 25 ++++++ .../manage.enable.html.twig} | 0 .../account/2fa/email/disable.html.twig | 4 +- .../account/2fa/email/enable.html.twig | 6 +- .../account/2fa/email/login.html.twig | 6 +- ...d.html.twig => manage.connected.html.twig} | 6 +- ...nage.html.twig => manage.enable.html.twig} | 2 +- .../account/2fa/main.protected.html.twig | 22 +++++ .../templates/account/2fa/protected.html.twig | 18 ---- 17 files changed, 208 insertions(+), 105 deletions(-) delete mode 100644 system/templates/account/2fa/app/enabled.html.twig create mode 100644 system/templates/account/2fa/app/manage.connected.html.twig rename system/templates/account/2fa/{connect.html.twig => app/manage.enable.html.twig} (100%) rename system/templates/account/2fa/email/{enabled.html.twig => manage.connected.html.twig} (88%) rename system/templates/account/2fa/email/{manage.html.twig => manage.enable.html.twig} (95%) create mode 100644 system/templates/account/2fa/main.protected.html.twig delete mode 100644 system/templates/account/2fa/protected.html.twig diff --git a/system/pages/account/2fa/app/disable.php b/system/pages/account/2fa/app/disable.php index c6e8b539..8b820f22 100644 --- a/system/pages/account/2fa/app/disable.php +++ b/system/pages/account/2fa/app/disable.php @@ -3,19 +3,11 @@ defined('MYAAC') or die('Direct access not allowed!'); require __DIR__ . '/../base.php'; -$account = \MyAAC\Models\Account::find($account_logged->getId()); -if (!$account) { +if (!$account_logged->isLoaded()) { error('Account not found!'); return; } -if ($db->hasColumn('accounts', 'secret')) { - $account->secret = NULL; -} - -$account->{'2fa_secret'} = ''; -$account->save(); - $twoFactorAuth->disable(); $twig->display('success.html.twig', [ diff --git a/system/pages/account/2fa/app/enable.php b/system/pages/account/2fa/app/enable.php index 9a574fe3..972053d4 100644 --- a/system/pages/account/2fa/app/enable.php +++ b/system/pages/account/2fa/app/enable.php @@ -14,57 +14,60 @@ if (!empty($account_logged->getCustomField('2fa_secret'))) { return; } +$explodeRecoveryKey = explode('-', $account_logged->getCustomField('key')); +$newRecoveryKeyFormat = (count($explodeRecoveryKey) == 4); + if (ACTION == 'request') { - $clock = new NativeClock(); - $secret = generateRandom2faSecret(); + if ($newRecoveryKeyFormat) { + $key = $_POST['key1'] . '-' . $_POST['key2'] . '-' . $_POST['key3'] . '-' . $_POST['key4']; + } + else { + $key = $_POST['key']; + } - $otp = TOTP::createFromSecret($secret); + $accountKey = $account_logged->getCustomField('key'); + if (!empty($key) && $key == $accountKey) { + $clock = new NativeClock(); - setSession('2fa_secret', $secret); + $secret = getSession('2fa_secret'); + if ($secret === null) { + $secret = generateRandom2faSecret(); + setSession('2fa_secret', $secret); + } - $otp->setLabel($account_logged->getEmail()); - $otp->setIssuer(configLua('serverName')); + $twoFactorAuth->appDisplayEnable($secret); - $grCodeUri = $otp->getQrCodeUri( - 'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=200x200&ecc=M', - '[DATA]' - ); - - $twig->display('account/2fa/app/enable.html.twig', [ - 'grCodeUri' => $grCodeUri, - 'secret' => $secret, - ]); - - return; + return; + } + else { + if (empty($key)) { + $errors[] = 'Please enter the recovery key!'; + } + else { + $errors[] = 'Invalid recovery key!'; + } + } } if (ACTION == 'link') { $secret = getSession('2fa_secret'); if ($secret === null) { - $twig->display('error_box.html.twig', ['errors' => ['Secret not set']]); + $twig->display('error_box.html.twig', ['errors' => ['Secret not set. Go back and try again.']]); + return; } - $totp = $_POST['totp'] ?? ''; - if (!empty($totp)) { - $otp = TOTP::createFromSecret($secret); + $authCode = $_POST['auth-code'] ?? ''; + if (!empty($authCode)) { + $otp = $twoFactorAuth->appInitTOTP($secret); - $otp->setLabel($account_logged->getEmail()); - $otp->setIssuer(configLua('serverName')); + if (!$otp->verify($authCode)) { + $errors = ['Token is invalid!']; - if (!$otp->verify($totp)) { - $grCodeUri = $otp->getQrCodeUri( - 'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=200x200&ecc=M', - '[DATA]' - ); + $twig->display('error_box.html.twig', ['errors' => $errors]); - $twig->display('error_box.html.twig', ['errors' => ['Token is invalid!']]); - - $twig->display('account/2fa/app/enable.html.twig', [ - 'grCodeUri' => $grCodeUri, - 'secret' => $secret, - ]); + $twoFactorAuth->appDisplayEnable($secret, $otp, $errors); return; } @@ -85,10 +88,22 @@ if (ACTION == 'link') { return; } + else { + $errors = ['You have to enter the code generated by the authenticator!']; + + $twig->display('error_box.html.twig', ['errors' => $errors]); + $twoFactorAuth->appDisplayEnable($secret, null, $errors); + return; + } } if (!empty($errors)) { $twig->display('error_box.html.twig', ['errors' => $errors]); } -$twig->display('account/2fa/app/enable.warning.html.twig', ['wrongCode' => count($errors) > 0]); +$twig->display('account/2fa/app/enable.warning.html.twig', + [ + 'newRecoveryKeyFormat' => $newRecoveryKeyFormat, + 'errors' => $errors, + ] +); diff --git a/system/pages/account/2fa/email/disable.php b/system/pages/account/2fa/email/disable.php index ba78e6d1..9ae32b14 100644 --- a/system/pages/account/2fa/email/disable.php +++ b/system/pages/account/2fa/email/disable.php @@ -16,8 +16,8 @@ $twoFactorAuth->deleteOldCodes(); $twig->display('success.html.twig', [ - 'title' => 'Email Code Authentication Deactivated', - 'description' => 'You have successfully deactivated the Email Code Authentication for your account.' + 'title' => 'Email Code Authentication Disabled', + 'description' => 'You have successfully disabled the Email Code Authentication for your account.' ] ); /* diff --git a/system/src/TwoFactorAuth/TwoFactorAuth.php b/system/src/TwoFactorAuth/TwoFactorAuth.php index c17fabbf..d6e74d0e 100644 --- a/system/src/TwoFactorAuth/TwoFactorAuth.php +++ b/system/src/TwoFactorAuth/TwoFactorAuth.php @@ -5,6 +5,7 @@ namespace MyAAC\TwoFactorAuth; use MyAAC\Models\AccountEMailCode; use MyAAC\TwoFactorAuth\Gateway\AppAuthGateway; use MyAAC\TwoFactorAuth\Gateway\EmailAuthGateway; +use OTPHP\TOTP; class TwoFactorAuth { @@ -127,16 +128,17 @@ class TwoFactorAuth public function getAccountManageViews(): array { - $twoFactorView = 'account/2fa/protected.html.twig'; if ($this->authType == self::TYPE_EMAIL) { - $twoFactorView2 = 'account/2fa/email/enabled.html.twig'; + $twoFactorView = 'account/2fa/main.protected.html.twig'; + $twoFactorView2 = 'account/2fa/email/manage.connected.html.twig'; } elseif ($this->authType == self::TYPE_APP) { - $twoFactorView2 = 'account/2fa/app/enabled.html.twig'; + $twoFactorView = 'account/2fa/app/manage.connected.html.twig'; + $twoFactorView2 = 'account/2fa/main.protected.html.twig'; } else { - $twoFactorView = 'account/2fa/connect.html.twig'; - $twoFactorView2 = 'account/2fa/email/manage.html.twig'; + $twoFactorView = 'account/2fa/app/manage.enable.html.twig'; + $twoFactorView2 = 'account/2fa/email/manage.enable.html.twig'; } return [$twoFactorView, $twoFactorView2]; @@ -146,8 +148,17 @@ class TwoFactorAuth $this->account->setCustomField('2fa_type', $type); } - public function disable(): void { + public function disable(): void + { + global $db; + $this->account->setCustomField('2fa_type', self::TYPE_NONE); + + if ($db->hasColumn('accounts', 'secret')) { + $this->account->setCustomField('secret', null); + } + + $this->account->setCustomField('2fa_secret', ''); } public function isActive(): bool { @@ -170,6 +181,36 @@ class TwoFactorAuth AccountEMailCode::where('account_id', '=', $this->account->getId())->delete(); } + public function appInitTOTP(string $secret): TOTP + { + $otp = TOTP::createFromSecret($secret); + + $otp->setLabel($this->account->getEmail()); + $otp->setIssuer(configLua('serverName')); + + return $otp; + } + + public function appDisplayEnable(string $secret, TOTP $otp = null, array $errors = []): void + { + global $twig; + + if ($otp === null) { + $otp = $this->appInitTOTP($secret); + } + + $grCodeUri = $otp->getQrCodeUri( + 'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=200x200&ecc=M', + '[DATA]' + ); + + $twig->display('account/2fa/app/enable.html.twig', [ + 'grCodeUri' => $grCodeUri, + 'secret' => $secret, + 'errors' => $errors, + ]); + } + public function resendEmailCode(): void { global $twig; diff --git a/system/templates/account/2fa/app/enable.html.twig b/system/templates/account/2fa/app/enable.html.twig index ac11cdab..b97d1d92 100644 --- a/system/templates/account/2fa/app/enable.html.twig +++ b/system/templates/account/2fa/app/enable.html.twig @@ -23,7 +23,13 @@

  • -
    + + + {% if errors|length > 0 %} +
    +
    {{ errors[0] }}
    + {% endif %} +
  • Click on "Continue" to connect the authenticator app to your Tibia account. @@ -46,13 +52,14 @@
    - + - {{ csrf() }} + {{ csrf() }} - {% set button_color = 'green' %} - {% set button_name = 'Continue' %} - {{ include('buttons.base.html.twig') }} + {% set button_color = 'green' %} + {% set button_name = 'Continue' %} + {{ include('buttons.base.html.twig') }} +
    @@ -60,7 +67,7 @@ {{ csrf() }} {% set button_color = 'blue' %} - {% set button_name = 'Request' %} + {% set button_name = 'Cancel' %} {{ include('buttons.base.html.twig') }}
    diff --git a/system/templates/account/2fa/app/enable.warning.html.twig b/system/templates/account/2fa/app/enable.warning.html.twig index 145fedac..5e8089f3 100644 --- a/system/templates/account/2fa/app/enable.warning.html.twig +++ b/system/templates/account/2fa/app/enable.warning.html.twig @@ -6,12 +6,12 @@ - Please read this warning carefully as it contains important security information! If you skip this message, you might lose your Tibia account!

    + Please read this warning carefully as it contains important security information! If you skip this message, you might lose your {{ config.lua.serverName }} account!

    Before you connect your account with an authenticator app, you will be asked to enter your recovery key. If you do not have a valid recovery key, you need to order a new one before you can connect your account with an authenticator.

    Why?
    The recovery key is the only way to unlink the authenticator app from - your Tibia account in various cases, among others, if:

    + your {{ config.lua.serverName }} account in various cases, among others, if: