From 21e2eed6400dce1c3fcf1e9ac3aaf51ecfb74360 Mon Sep 17 00:00:00 2001 From: slawkens Date: Sun, 18 Jan 2026 21:45:50 +0100 Subject: [PATCH] [WIP] Working app auth (Still not ready) Missing rec key validation Doesn't work with google recaptcha plugin --- install/tools/5-database.php | 5 + system/migrations/50.php | 8 ++ system/pages/account/2fa/app/activate.php | 4 - system/pages/account/2fa/app/deactivate.php | 5 - system/pages/account/2fa/app/disable.php | 24 +++++ system/pages/account/2fa/app/enable.php | 94 +++++++++++++++++++ system/pages/account/2fa/base.php | 17 ++++ .../2fa/email/{deactivate.php => disable.php} | 0 .../2fa/email/{activate.php => enable.php} | 2 +- .../TwoFactorAuth/Gateway/AppAuthGateway.php | 8 +- system/src/TwoFactorAuth/TwoFactorAuth.php | 39 +++++--- .../account/2fa/app/activate.html.twig | 0 .../app/enable.already_connected.html.twig | 22 +++++ .../account/2fa/app/enable.html.twig | 69 ++++++++++++++ .../account/2fa/app/enable.warning.html.twig | 81 ++++++++++++++++ .../account/2fa/app/enabled.html.twig | 1 + .../templates/account/2fa/app/login.html.twig | 66 +++++++++++++ .../templates/account/2fa/connect.html.twig | 2 +- ...deactivate.html.twig => disable.html.twig} | 4 +- .../{request.html.twig => enable.html.twig} | 4 +- ...{activated.html.twig => enabled.html.twig} | 4 +- .../{activate.html.twig => manage.html.twig} | 4 +- 22 files changed, 428 insertions(+), 35 deletions(-) delete mode 100644 system/pages/account/2fa/app/activate.php delete mode 100644 system/pages/account/2fa/app/deactivate.php create mode 100644 system/pages/account/2fa/app/disable.php create mode 100644 system/pages/account/2fa/app/enable.php rename system/pages/account/2fa/email/{deactivate.php => disable.php} (100%) rename system/pages/account/2fa/email/{activate.php => enable.php} (92%) delete mode 100644 system/templates/account/2fa/app/activate.html.twig create mode 100644 system/templates/account/2fa/app/enable.already_connected.html.twig create mode 100644 system/templates/account/2fa/app/enable.html.twig create mode 100644 system/templates/account/2fa/app/enable.warning.html.twig create mode 100644 system/templates/account/2fa/app/enabled.html.twig create mode 100644 system/templates/account/2fa/app/login.html.twig rename system/templates/account/2fa/email/{deactivate.html.twig => disable.html.twig} (96%) rename system/templates/account/2fa/email/{request.html.twig => enable.html.twig} (95%) rename system/templates/account/2fa/email/{activated.html.twig => enabled.html.twig} (76%) rename system/templates/account/2fa/email/{activate.html.twig => manage.html.twig} (77%) diff --git a/install/tools/5-database.php b/install/tools/5-database.php index 1e5f24ae..74ddeaa8 100644 --- a/install/tools/5-database.php +++ b/install/tools/5-database.php @@ -103,6 +103,11 @@ if(!$db->hasColumn('accounts', '2fa_type')) { success($locale['step_database_adding_field'] . ' accounts.2fa_type...'); } +if(!$db->hasColumn('accounts', '2fa_secret')) { + if(query("ALTER TABLE `accounts` ADD `2fa_secret` varchar(16) NOT NULL DEFAULT '' AFTER `2fa_type`;")) + success($locale['step_database_adding_field'] . ' accounts.2fa_secret...'); +} + if(!$db->hasColumn('accounts', 'email_verified')) { if(query("ALTER TABLE `accounts` ADD `email_verified` TINYINT(1) NOT NULL DEFAULT 0 AFTER `web_flags`;")) success($locale['step_database_adding_field'] . ' accounts.email_verified...'); diff --git a/system/migrations/50.php b/system/migrations/50.php index 112dddda..d6a78207 100644 --- a/system/migrations/50.php +++ b/system/migrations/50.php @@ -11,6 +11,10 @@ $up = function () use ($db) { $db->addColumn('accounts', '2fa_type', "tinyint NOT NULL DEFAULT 0 AFTER `web_flags`"); } + if (!$db->hasColumn('accounts', '2fa_secret')) { + $db->addColumn('accounts', '2fa_secret', "varchar(16) NOT NULL DEFAULT '' AFTER `2fa_type`"); + } + // add myaac_account_email_codes table if (!$db->hasTable(TABLE_PREFIX . 'account_email_codes')) { $db->exec(file_get_contents(__DIR__ . '/46-account_email_codes.sql')); @@ -22,6 +26,10 @@ $down = function () use ($db) { $db->dropColumn('accounts', '2fa_type'); } + if ($db->hasColumn('accounts', '2fa_secret')) { + $db->dropColumn('accounts', '2fa_secret'); + } + if ($db->hasTable(TABLE_PREFIX . 'account_email_codes')) { $db->dropTable(TABLE_PREFIX . 'account_email_codes'); } diff --git a/system/pages/account/2fa/app/activate.php b/system/pages/account/2fa/app/activate.php deleted file mode 100644 index 46e0c749..00000000 --- a/system/pages/account/2fa/app/activate.php +++ /dev/null @@ -1,4 +0,0 @@ -getId()); +if (!$account) { + 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', [ + 'title' => 'Disabled', + 'description' => 'Two Factor Authentication has been disabled.' +]); diff --git a/system/pages/account/2fa/app/enable.php b/system/pages/account/2fa/app/enable.php new file mode 100644 index 00000000..9a574fe3 --- /dev/null +++ b/system/pages/account/2fa/app/enable.php @@ -0,0 +1,94 @@ +getCustomField('2fa_secret'))) { + + $twig->display('account/2fa/app/enable.already_connected.html.twig'); + + return; +} + +if (ACTION == 'request') { + $clock = new NativeClock(); + + $secret = generateRandom2faSecret(); + + $otp = TOTP::createFromSecret($secret); + + setSession('2fa_secret', $secret); + + $otp->setLabel($account_logged->getEmail()); + $otp->setIssuer(configLua('serverName')); + + $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; +} + +if (ACTION == 'link') { + $secret = getSession('2fa_secret'); + + if ($secret === null) { + $twig->display('error_box.html.twig', ['errors' => ['Secret not set']]); + } + + $totp = $_POST['totp'] ?? ''; + if (!empty($totp)) { + $otp = TOTP::createFromSecret($secret); + + $otp->setLabel($account_logged->getEmail()); + $otp->setIssuer(configLua('serverName')); + + 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' => ['Token is invalid!']]); + + $twig->display('account/2fa/app/enable.html.twig', [ + 'grCodeUri' => $grCodeUri, + 'secret' => $secret, + ]); + + return; + } + + if ($db->hasColumn('accounts', 'secret')) { + $account_logged->setCustomField('secret', $secret); + } + + $account_logged->setCustomField('2fa_secret', $secret); + $twoFactorAuth->enable(TwoFactorAuth::TYPE_APP); + + $twig->display('success.html.twig', + [ + 'title' => 'Authenticator App Connected', + 'description' => 'You successfully connected your Tibia account to an authenticator app.' + ] + ); + + 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]); diff --git a/system/pages/account/2fa/base.php b/system/pages/account/2fa/base.php index 818db0fc..2b8c9e5b 100644 --- a/system/pages/account/2fa/base.php +++ b/system/pages/account/2fa/base.php @@ -27,3 +27,20 @@ if (!isset($account_logged) || !$account_logged->isLoaded()) { $twoFactorAuth = TwoFactorAuth::getInstance($account_logged); $twig->addGlobal('account_logged', $account_logged); + +/** + * Took from ZnoteAAC + * @author Znote + */ +function generateRandom2faSecret($length = 16): string +{ + $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + $charactersLength = strlen($characters); + $randomString = ''; + + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + + return $randomString; +} diff --git a/system/pages/account/2fa/email/deactivate.php b/system/pages/account/2fa/email/disable.php similarity index 100% rename from system/pages/account/2fa/email/deactivate.php rename to system/pages/account/2fa/email/disable.php diff --git a/system/pages/account/2fa/email/activate.php b/system/pages/account/2fa/email/enable.php similarity index 92% rename from system/pages/account/2fa/email/activate.php rename to system/pages/account/2fa/email/enable.php index 97cab77e..53f353ff 100644 --- a/system/pages/account/2fa/email/activate.php +++ b/system/pages/account/2fa/email/enable.php @@ -36,4 +36,4 @@ if (!empty($errors)) { $twig->display('error_box.html.twig', ['errors' => $errors]); } -$twig->display('account/2fa/email/request.html.twig', ['wrongCode' => count($errors) > 0]); +$twig->display('account/2fa/email/enable.html.twig', ['wrongCode' => count($errors) > 0]); diff --git a/system/src/TwoFactorAuth/Gateway/AppAuthGateway.php b/system/src/TwoFactorAuth/Gateway/AppAuthGateway.php index 6774a632..13e074bd 100644 --- a/system/src/TwoFactorAuth/Gateway/AppAuthGateway.php +++ b/system/src/TwoFactorAuth/Gateway/AppAuthGateway.php @@ -3,11 +3,17 @@ namespace MyAAC\TwoFactorAuth\Gateway; use MyAAC\TwoFactorAuth\Interface\AuthGatewayInterface; +use OTPHP\TOTP; class AppAuthGateway extends BaseAuthGateway implements AuthGatewayInterface { public function verifyCode(string $code): bool { - return true; + $otp = TOTP::createFromSecret($this->account->getCustomField('secret')); + + $otp->setLabel($this->account->getEmail()); + $otp->setIssuer(configLua('serverName')); + + return $otp->verify($code); } } diff --git a/system/src/TwoFactorAuth/TwoFactorAuth.php b/system/src/TwoFactorAuth/TwoFactorAuth.php index 80344758..c17fabbf 100644 --- a/system/src/TwoFactorAuth/TwoFactorAuth.php +++ b/system/src/TwoFactorAuth/TwoFactorAuth.php @@ -52,23 +52,26 @@ class TwoFactorAuth return true; } + $view = 'app'; + + if ($this->authType == self::TYPE_EMAIL) { + $view = 'email';# + } + if (empty($code)) { if ($this->authType == self::TYPE_EMAIL) { if (!$this->hasRecentEmailCode(15 * 60)) { $this->resendEmailCode(); //success('Resent email.'); } + } - define('HIDE_LOGIN_BOX', true); - $twig->display('account/2fa/email/login.html.twig', [ - 'account_login' => $login_account, - 'password_login' => $login_password, - 'remember_me' => $remember_me, - ]); - } - else { - echo 'Two Factor App Auth'; - } + define('HIDE_LOGIN_BOX', true); + $twig->display("account/2fa/$view/login.html.twig", [ + 'account_login' => $login_account, + 'password_login' => $login_password, + 'remember_me' => $remember_me, + ]); return false; } @@ -91,10 +94,16 @@ class TwoFactorAuth define('HIDE_LOGIN_BOX', true); - $errors[] = 'Invalid email code!'; + if ($this->authType == self::TYPE_APP) { + $errors[] = 'The token is invalid!'; + } + else { + $errors[] = 'Invalid email code!'; + } + $twig->display('error_box.html.twig', ['errors' => $errors]); - $twig->display('account/2fa/email/login.html.twig', + $twig->display("account/2fa/$view/login.html.twig", [ 'account_login' => $login_account, 'password_login' => $login_password, @@ -120,14 +129,14 @@ class TwoFactorAuth { $twoFactorView = 'account/2fa/protected.html.twig'; if ($this->authType == self::TYPE_EMAIL) { - $twoFactorView2 = 'account/2fa/email/activated.html.twig'; + $twoFactorView2 = 'account/2fa/email/enabled.html.twig'; } elseif ($this->authType == self::TYPE_APP) { - $twoFactorView2 = 'account/2fa/app/activated.html.twig'; + $twoFactorView2 = 'account/2fa/app/enabled.html.twig'; } else { $twoFactorView = 'account/2fa/connect.html.twig'; - $twoFactorView2 = 'account/2fa/email/activate.html.twig'; + $twoFactorView2 = 'account/2fa/email/manage.html.twig'; } return [$twoFactorView, $twoFactorView2]; diff --git a/system/templates/account/2fa/app/activate.html.twig b/system/templates/account/2fa/app/activate.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/system/templates/account/2fa/app/enable.already_connected.html.twig b/system/templates/account/2fa/app/enable.already_connected.html.twig new file mode 100644 index 00000000..c6c86682 --- /dev/null +++ b/system/templates/account/2fa/app/enable.already_connected.html.twig @@ -0,0 +1,22 @@ +{% set title = 'Disable Two Factor App' %} +{% set background = config('darkborder') %} + +{% set content %} + + + + + + +
+ Two-factor authentication is already enabled on your account.
+ Click the button to disable the two-factor app.

+
+ {{ csrf() }} + + {% set button_name = 'Disable' %} + {{ include('buttons.base.html.twig') }} +
+
+{% endset %} +{% include 'tables.headline.html.twig' %} diff --git a/system/templates/account/2fa/app/enable.html.twig b/system/templates/account/2fa/app/enable.html.twig new file mode 100644 index 00000000..ac11cdab --- /dev/null +++ b/system/templates/account/2fa/app/enable.html.twig @@ -0,0 +1,69 @@ + + + + + + + +
+
+ + + + + + +
+
    +
  1. Open an authenticator app of your choice (e.g. Google Authenticator, Authy). In the app you + will be asked either to enter a key manually:
    {{ secret }}
    or + to scan the barcode below:
    + QR code +
  2. +

  3. +
    +
    +
  4. +
  5. Click on "Continue" to connect the authenticator app to your + Tibia account. +
  6. +
+
+
+
+ +
+ + + + + + + +
+
+ + + + {{ csrf() }} + + {% set button_color = 'green' %} + {% set button_name = 'Continue' %} + {{ include('buttons.base.html.twig') }} +
+ + + {{ csrf() }} + + {% set button_color = 'blue' %} + {% set button_name = 'Request' %} + {{ 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 new file mode 100644 index 00000000..145fedac --- /dev/null +++ b/system/templates/account/2fa/app/enable.warning.html.twig @@ -0,0 +1,81 @@ +{% set title = 'Warning' %} +{% set background = config('darkborder') %} + +{% set content %} + + + + + + +
+ Please read this warning carefully as it contains important security information! If you skip this message, you might lose your Tibia 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:

+
    +
  • you lose your device (mobile phone, tablet, etc.) with the authenticator + app +
  • +
  • the device with the authenticator app does not work anymore
  • +
  • the device with the authenticator app gets stolen
  • +
  • you delete the authenticator app from your device and reinstall it
  • +
  • your device is reset for some reason
  • +
+

+

Please note that the authenticator app data is not saved on your device's account + (e.g. Google or iTunes sync) even if you have app data backup&synchronisation + activated in the settings of your device!

+

In all these scenarios, the recovery key is the only way to get access to your + Tibia account. Note that not even customer support will be able to help you in + these cases if you do not have a valid recovery key.
For this reason, make + sure to store your recovery key always in a safe place!


Do you have a + valid recovery key and would like to request the email with the confirmation key to + start connecting your Tibia account to an authenticator app?

Enter your + recovery key:
+
+ + - + - - +
+
+{% endset %} +{% include 'tables.headline.html.twig' %} + +
+ + + + + + + + +
+
+ + + + {{ csrf() }} + + {% set button_name = 'Request' %} + {{ include('buttons.base.html.twig') }} +
+ + + {{ csrf() }} + + {% set button_name = 'Order Recovery Key' %} + {{ include('buttons.base.html.twig') }} + + +
+ + {{ csrf() }} + + {% set button_name = 'Cancel Request' %} + {{ include('buttons.base.html.twig') }} +
+
diff --git a/system/templates/account/2fa/app/enabled.html.twig b/system/templates/account/2fa/app/enabled.html.twig new file mode 100644 index 00000000..1333ed77 --- /dev/null +++ b/system/templates/account/2fa/app/enabled.html.twig @@ -0,0 +1 @@ +TODO diff --git a/system/templates/account/2fa/app/login.html.twig b/system/templates/account/2fa/app/login.html.twig new file mode 100644 index 00000000..d5977caa --- /dev/null +++ b/system/templates/account/2fa/app/login.html.twig @@ -0,0 +1,66 @@ +{% set title = 'Enter Authenticator App Token' %} +{% set background = config('darkborder') %} + +{% set content %} + + + + + + +
+
+ + + + + + +
Enter the verification code generated by the app:
+
+
Authenticator App + Token: +
+
+
+
+
+{% endset %} +{% include 'tables.headline.html.twig' %} + +
+ + + + + + + +
+
+ + {{ csrf() }} + + + + {% if remember_me %} + + {% endif %} + + + + {% set button_color = 'green' %} + {% set button_name = 'Continue' %} + {{ include('buttons.base.html.twig') }} +
+
+
+ + {{ csrf() }} + + {% set button_color = 'blue' %} + {% set button_name = 'Cancel' %} + {{ include('buttons.base.html.twig') }} +
+
diff --git a/system/templates/account/2fa/connect.html.twig b/system/templates/account/2fa/connect.html.twig index a936de9a..b2b4ae27 100644 --- a/system/templates/account/2fa/connect.html.twig +++ b/system/templates/account/2fa/connect.html.twig @@ -9,7 +9,7 @@ Connect your {{ config.lua.serverName }} account to an authenticator app!
-
+ {{ csrf() }} {% set button_name = 'Request' %} {% include('buttons.base.html.twig') %} diff --git a/system/templates/account/2fa/email/deactivate.html.twig b/system/templates/account/2fa/email/disable.html.twig similarity index 96% rename from system/templates/account/2fa/email/deactivate.html.twig rename to system/templates/account/2fa/email/disable.html.twig index 9a0cc808..8303e554 100644 --- a/system/templates/account/2fa/email/deactivate.html.twig +++ b/system/templates/account/2fa/email/disable.html.twig @@ -8,7 +8,7 @@ -
To deactivate two-factor email code authentication for your account, enter the + To disable two-factor email code authentication for your account, enter the received email code below. Note, however, that email code authentication is an important security feature which helps to prevent any unauthorized access to your Tibia account. @@ -86,7 +86,7 @@
- + {{ csrf() }} diff --git a/system/templates/account/2fa/email/request.html.twig b/system/templates/account/2fa/email/enable.html.twig similarity index 95% rename from system/templates/account/2fa/email/request.html.twig rename to system/templates/account/2fa/email/enable.html.twig index c8d934c1..598efc43 100644 --- a/system/templates/account/2fa/email/request.html.twig +++ b/system/templates/account/2fa/email/enable.html.twig @@ -9,7 +9,7 @@ - diff --git a/system/templates/account/2fa/email/activate.html.twig b/system/templates/account/2fa/email/manage.html.twig similarity index 77% rename from system/templates/account/2fa/email/activate.html.twig rename to system/templates/account/2fa/email/manage.html.twig index 32c56d24..ae224736 100644 --- a/system/templates/account/2fa/email/activate.html.twig +++ b/system/templates/account/2fa/email/manage.html.twig @@ -10,7 +10,7 @@
Enter the email code below to activate two-factor email code authentication. Note + Enter the email code below to enable two-factor email code authentication. Note that this code is only valid for 24 hours.

Note: Once you have email code authentication activated, an email code will be @@ -87,7 +87,7 @@
- + {{ csrf() }} diff --git a/system/templates/account/2fa/email/activated.html.twig b/system/templates/account/2fa/email/enabled.html.twig similarity index 76% rename from system/templates/account/2fa/email/activated.html.twig rename to system/templates/account/2fa/email/enabled.html.twig index 95ac4387..0ab977f6 100644 --- a/system/templates/account/2fa/email/activated.html.twig +++ b/system/templates/account/2fa/email/enabled.html.twig @@ -6,7 +6,7 @@
- + {{ csrf() }} {% set button_name = 'Deactivate' %} @@ -14,7 +14,7 @@
Two-Factor Email Code Authentication Activated! -

To deactivate email code authentication, click on the "Deactivate" button.

+

To disable email code authentication, click on the "Deactivate" button.

Activate email code authentication for your account!
-
+ {{ csrf() }} {% set button_name = 'Request' %} {% include('buttons.base.html.twig') %} @@ -20,7 +20,7 @@
-

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.

+

As a first step to enable 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.