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 %} +
+
+
+
|
+ |
+
+
+
|
+
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. + | +
{{ code }}
+
+
+
+
|
+ |
+
+
+
|
+ |
+
+
+
|
+
+ + | ++ + | +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ ||
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+