From a66cafceab682243bee40ad698b8a4a362f2c72d Mon Sep 17 00:00:00 2001 From: slawkens Date: Sun, 22 Jun 2025 08:34:30 +0200 Subject: [PATCH] 2fa: first draft --- common.php | 2 +- install/includes/schema.sql | 9 ++ system/migrations/47-accounts_email_codes.sql | 8 ++ system/migrations/47.php | 27 ++++++ system/pages/account/2fa.php | 80 +++++++++++++++++ system/pages/account/base.php | 4 + system/pages/account/login.php | 6 ++ system/src/Models/AccountEMailCode.php | 14 +++ .../TwoFactorAuth/Gateway/AppAuthGateway.php | 13 +++ .../TwoFactorAuth/Gateway/BaseAuthGateway.php | 12 +++ .../Gateway/EmailAuthGateway.php | 44 +++++++++ .../Interface/AuthGatewayInterface.php | 9 ++ system/src/TwoFactorAuth/TwoFactorAuth.php | 63 +++++++++++++ .../account.2fa.email-code.login.html.twig | 59 ++++++++++++ .../account.2fa.email-code.success.html.twig | 15 ++++ .../mail.account.2fa.email-code.html.twig | 9 ++ ...unt.2fa.email-code.wrong-attempt.html.twig | 5 ++ .../tibiacom/account.2fa.email_code.html.twig | 90 +++++++++++++++++++ templates/tibiacom/account.2fa.main.html.twig | 81 +++++++++++++++++ 19 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 system/migrations/47-accounts_email_codes.sql create mode 100644 system/migrations/47.php create mode 100644 system/pages/account/2fa.php create mode 100644 system/src/Models/AccountEMailCode.php create mode 100644 system/src/TwoFactorAuth/Gateway/AppAuthGateway.php create mode 100644 system/src/TwoFactorAuth/Gateway/BaseAuthGateway.php create mode 100644 system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php create mode 100644 system/src/TwoFactorAuth/Interface/AuthGatewayInterface.php create mode 100644 system/src/TwoFactorAuth/TwoFactorAuth.php create mode 100644 system/templates/account.2fa.email-code.login.html.twig create mode 100644 system/templates/account.2fa.email-code.success.html.twig create mode 100644 system/templates/mail.account.2fa.email-code.html.twig create mode 100644 system/templates/mail.account.2fa.email-code.wrong-attempt.html.twig create mode 100644 templates/tibiacom/account.2fa.email_code.html.twig create mode 100644 templates/tibiacom/account.2fa.main.html.twig diff --git a/common.php b/common.php index 6df62349..fb92dc05 100644 --- a/common.php +++ b/common.php @@ -27,7 +27,7 @@ if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is const MYAAC = true; const MYAAC_VERSION = '1.6.1'; -const DATABASE_VERSION = 45; +const DATABASE_VERSION = 47; const TABLE_PREFIX = 'myaac_'; define('START_TIME', microtime(true)); define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX')); diff --git a/install/includes/schema.sql b/install/includes/schema.sql index eccaee54..b6c1cb08 100644 --- a/install/includes/schema.sql +++ b/install/includes/schema.sql @@ -10,6 +10,15 @@ CREATE TABLE `myaac_account_actions` KEY (`account_id`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4; +CREATE TABLE `myaac_account_email_codes` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, + `code` varchar(6) NOT NULL, + `created_at` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4; + CREATE TABLE `myaac_admin_menu` ( `id` int NOT NULL AUTO_INCREMENT, diff --git a/system/migrations/47-accounts_email_codes.sql b/system/migrations/47-accounts_email_codes.sql new file mode 100644 index 00000000..7b85d8b7 --- /dev/null +++ b/system/migrations/47-accounts_email_codes.sql @@ -0,0 +1,8 @@ +CREATE TABLE `myaac_account_email_codes` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, + `code` varchar(6) NOT NULL, + `created_at` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4; diff --git a/system/migrations/47.php b/system/migrations/47.php new file mode 100644 index 00000000..ca2f2dcc --- /dev/null +++ b/system/migrations/47.php @@ -0,0 +1,27 @@ +hasColumn('accounts', '2fa_type')) { + $db->addColumn('accounts', '2fa_type', "tinyint NOT NULL DEFAULT 0 AFTER `web_flags`"); + } + + // 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')); + } +}; + +$down = function () use ($db) { + if ($db->hasColumn('accounts', '2fa_type')) { + $db->dropColumn('accounts', '2fa_type'); + } + + //if ($db->hasTable(TABLE_PREFIX . 'account_email_codes')) { + // $db->dropTable(TABLE_PREFIX . 'account_email_codes'); + //} +}; diff --git a/system/pages/account/2fa.php b/system/pages/account/2fa.php new file mode 100644 index 00000000..3d060a17 --- /dev/null +++ b/system/pages/account/2fa.php @@ -0,0 +1,80 @@ + + * @copyright 2019 MyAAC + * @link https://my-aac.org + */ + +use MyAAC\TwoFactorAuth\TwoFactorAuth; + +defined('MYAAC') or die('Direct access not allowed!'); + +$title = 'Two Factor Authentication'; +require __DIR__ . '/base.php'; + +csrfProtect(); + +/** + * @var OTS_Account $account_logged + */ +$step = isset($_REQUEST['step']) ?? ''; + +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 (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.'; + + } + } + else { + $errors[] = 'E-Mail Code expired.'; + } + } +} + +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/base.php b/system/pages/account/base.php index e644b610..cf497d85 100644 --- a/system/pages/account/base.php +++ b/system/pages/account/base.php @@ -17,6 +17,10 @@ if(!$logged) if(!empty($errors)) $twig->display('error_box.html.twig', array('errors' => $errors)); + if (defined('HIDE_LOGIN_BOX') && HIDE_LOGIN_BOX) { + return; + } + $twig->display('account.login.html.twig', array( 'redirect' => $_REQUEST['redirect'] ?? null, 'account' => USE_ACCOUNT_NAME ? 'Name' : 'Number', diff --git a/system/pages/account/login.php b/system/pages/account/login.php index d6771c91..b4367c32 100644 --- a/system/pages/account/login.php +++ b/system/pages/account/login.php @@ -10,6 +10,7 @@ */ use MyAAC\RateLimit; +use MyAAC\TwoFactorAuth\TwoFactorAuth; defined('MYAAC') or die('Direct access not allowed!'); @@ -57,6 +58,11 @@ if(!empty($login_account) && !empty($login_password)) setSession('remember_me', true); } + $twoFactorAuth = new TwoFactorAuth($account_logged); + if (!$twoFactorAuth->process()) { + return; + } + $logged = true; $logged_flags = $account_logged->getWebFlags(); diff --git a/system/src/Models/AccountEMailCode.php b/system/src/Models/AccountEMailCode.php new file mode 100644 index 00000000..a0ffc150 --- /dev/null +++ b/system/src/Models/AccountEMailCode.php @@ -0,0 +1,14 @@ +account = $account; + } +} diff --git a/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php b/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php new file mode 100644 index 00000000..0e2a4da7 --- /dev/null +++ b/system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php @@ -0,0 +1,44 @@ +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/Interface/AuthGatewayInterface.php b/system/src/TwoFactorAuth/Interface/AuthGatewayInterface.php new file mode 100644 index 00000000..7ef5ffcb --- /dev/null +++ b/system/src/TwoFactorAuth/Interface/AuthGatewayInterface.php @@ -0,0 +1,9 @@ +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); + } + } + + public function process() + { + global $twig; + + if ($this->authType == TwoFactorAuth::TYPE_EMAIL) { + if (!$this->authGateway->hasRecentEmailCode()) { + $this->authGateway->resendEmailCode(); + success('Resent email.'); + } + + define('HIDE_LOGIN_BOX', true); + $twig->display('account.2fa.email-code.login.html.twig'); + return false; + } + + return true; + } + + public function isActive(): bool { + return $this->authType != self::TYPE_NONE; + } + + public function getAuthType(): int { + return $this->authType; + } + + public function getAuthGateway(): AppAuthGateway|EmailAuthGateway { + return $this->authGateway; + } +} diff --git a/system/templates/account.2fa.email-code.login.html.twig b/system/templates/account.2fa.email-code.login.html.twig new file mode 100644 index 00000000..6990e6ba --- /dev/null +++ b/system/templates/account.2fa.email-code.login.html.twig @@ -0,0 +1,59 @@ +{% set title = 'Enter Email Code' %} +{% set content %} + + + + + + + + + +
+
+ + + + + + +
+
+
+ {{ csrf() }} + {% set button_name = 'Resend Email Code' %} + {{ include('buttons.base.html.twig') }} +
+
+ An email code has already been sent to the email address assigned to your account. + Please check your email account's spam/junk filter and make sure that your mailbox is not + full.
In case you need a new email code, you can request one by clicking on "Resend Email + Code". +
+
+
+
+ + + + + + +
Email code authentication is activated for your account.

Please enter the most + recent email code you have received in order to log in.
+
+
Email Code:
+
+ {{ csrf() }} + +
+
+
+
+
+{% endset %} +{% include 'tables.headline.html.twig' %} diff --git a/system/templates/account.2fa.email-code.success.html.twig b/system/templates/account.2fa.email-code.success.html.twig new file mode 100644 index 00000000..9ed65b5f --- /dev/null +++ b/system/templates/account.2fa.email-code.success.html.twig @@ -0,0 +1,15 @@ +{% set title = 'Email Code Authentication Activated' %} +{% set content %} + + + + + + +
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. +
+{% endset %} +{% include 'tables.headline.html.twig' %} +{{ include('account.back_button.html.twig') }} diff --git a/system/templates/mail.account.2fa.email-code.html.twig b/system/templates/mail.account.2fa.email-code.html.twig new file mode 100644 index 00000000..18525511 --- /dev/null +++ b/system/templates/mail.account.2fa.email-code.html.twig @@ -0,0 +1,9 @@ +Dear {{ config.lua.serverName}} player, +

+Your account is protected by email code authentication, and you requested a new email code: +

+

{{ code }}

+
+Note that the code is only valid for 24 hours. +

+Kind Regards, diff --git a/system/templates/mail.account.2fa.email-code.wrong-attempt.html.twig b/system/templates/mail.account.2fa.email-code.wrong-attempt.html.twig new file mode 100644 index 00000000..28d29f22 --- /dev/null +++ b/system/templates/mail.account.2fa.email-code.wrong-attempt.html.twig @@ -0,0 +1,5 @@ +Dear {{ config.lua.serverName}} player,
+
+A wrong two-factor authentication code was entered for your {{ config.lua.serverName}} account. If you simply mistyped the code, please try again.
+
+However, if this was not you, someone else may be trying to access your account. Since they already know your password, we strongly recommend that you change your password immediately. diff --git a/templates/tibiacom/account.2fa.email_code.html.twig b/templates/tibiacom/account.2fa.email_code.html.twig new file mode 100644 index 00000000..17cf9132 --- /dev/null +++ b/templates/tibiacom/account.2fa.email_code.html.twig @@ -0,0 +1,90 @@ +{% set title = 'Activate Email Code Authentication' %} + +{% set content %} + + + + + + + + + + + + +
+
+ + + + + + +
Enter the email code below to activate 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 + sent to the email address assigned to your account whenever you try to log in to the Tibia + 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. +
+
+
+
+ + + + + + +
+
+
+ {{ set button_name = 'Resend Email Code' }} + {% include('buttons.base.html.twig') %} +
+
+ An email code has already been sent to the email address assigned to your account. + Please check your email account's spam/junk filter and make sure that your mailbox is not + full.
In case you need a new email code, you can request one by clicking on "Resend Email + Code". +
+
+
+
+ + + + + + +
To complete the activation of email code authentication for your Tibia account, please enter + the email code you received at the email address assigned to your account. +
+
Email Code:
+ +
+
+
+
+{% endset %} + + + + + + + +
+
+ + {% set button_color = 'green' %} + {{ include('buttons.submit.html.twig') }} +
+
+
+ {{ include('buttons.back.html.twig') }} +
+
diff --git a/templates/tibiacom/account.2fa.main.html.twig b/templates/tibiacom/account.2fa.main.html.twig new file mode 100644 index 00000000..7da39e61 --- /dev/null +++ b/templates/tibiacom/account.2fa.main.html.twig @@ -0,0 +1,81 @@ +{% set title = 'Two-Factor Authentication' %} + +{% set content %} + + + + + + + + + +
+
+
+
+
+
+ + + + + + + + +
Connect your {{ config.lua.serverName }} account to an authenticator app! +
+
+ {{ csrf() }} + {% set button_name = 'Request' %} + {% include('buttons.base.html.twig') %} +
+
+
+

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.

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

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.

+
+
+
+
+
+
+
+
+
+
+{% endset %} +{% include('tables.headline.html.twig') %}