[WIP] 2fa - Optimize code, views

This commit is contained in:
slawkens
2026-01-21 20:12:41 +01:00
parent 1975fb8ebe
commit 867e3e2c38
17 changed files with 208 additions and 105 deletions

View File

@@ -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', [

View File

@@ -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,
]
);

View File

@@ -16,8 +16,8 @@ $twoFactorAuth->deleteOldCodes();
$twig->display('success.html.twig',
[
'title' => 'Email Code Authentication Deactivated',
'description' => 'You have successfully <b>deactivated</b> the <b>Email Code Authentication</b> for your account.'
'title' => 'Email Code Authentication Disabled',
'description' => 'You have successfully <strong>disabled</strong> the <b>Email Code Authentication</b> for your account.'
]
);
/*

View File

@@ -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;

View File

@@ -23,7 +23,13 @@
<li><label for="totp">Enter the verification code you have received from the used
authenticator app:</label><br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<input form="form" id="totp" name="totp" autocomplete="off"></div>
<input form="form" id="auth-code" name="auth-code" maxlength="6" autocomplete="off">
{% if errors|length > 0 %}
<br/>
<div class="FormFieldError">{{ errors[0] }}</div>
{% endif %}
</div>
</li>
<li>Click on "Continue" to connect the authenticator app to your
Tibia account.
@@ -46,13 +52,14 @@
<td>
<form id="form" method="post" action="{{ getLink('account/2fa/app/enable') }}">
<input type="hidden" name="action" value="link">
<input type="hidden" name="action" value="link">
{{ 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') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
@@ -60,7 +67,7 @@
{{ csrf() }}
{% set button_color = 'blue' %}
{% set button_name = 'Request' %}
{% set button_name = 'Cancel' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>

View File

@@ -6,12 +6,12 @@
<tbody>
<tr>
<td>
<span class="red"><b>Please read this warning carefully as it contains important security information! If you skip this message, you might lose your Tibia account!</b></span><br><br>
<span class="red"><b>Please read this warning carefully as it contains important security information! If you skip this message, you might lose your {{ config.lua.serverName }} account!</b></span><br><br>
<p>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.</p>
<p>Why?<br>The recovery key is the only way to unlink the authenticator app from
your Tibia account in various cases, among others, if:</p>
your {{ config.lua.serverName }} account in various cases, among others, if:</p>
<ul style="list-style-type:square">
<li>you lose your device (mobile phone, tablet, etc.) with the authenticator
app
@@ -26,17 +26,30 @@
(e.g. Google or iTunes sync) even if you have app data backup&amp;synchronisation
activated in the settings of your device!</p>
<p>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
{{ config.lua.serverName }} 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.<br>For this reason, make
sure to store your recovery key always in a safe place!</p><br>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?<br><br><b>Enter your
recovery key:</b><br>
start connecting your {{ config.lua.serverName }} account to an authenticator app?<br><br><b>Enter your
recovery key:</b><br/>
<div style="margin-top: 15px; margin-bottom: 15px;">
<input class="UpperCaseInput" name="key1" value="" size="5" maxlength="5" autocomplete="off"> -
<input class="UpperCaseInput" name="key2" value="" size="5" maxlength="5" autocomplete="off"> - <input class="UpperCaseInput" name="key3" value="" size="5" maxlength="5" autocomplete="off"> -
<input class="UpperCaseInput" name="key4" value="" size="5" maxlength="5" autocomplete="off"></div>
{% if newRecoveryKeyFormat %}
<input form="form" class="UpperCaseInput" name="key1" value="" size="5" maxlength="5" autocomplete="off"> -
<input form="form" class="UpperCaseInput" name="key2" value="" size="5" maxlength="5" autocomplete="off"> - <input class="UpperCaseInput" name="key3" value="" size="5" maxlength="5" autocomplete="off"> -
<input form="form" class="UpperCaseInput" name="key4" value="" size="5" maxlength="5" autocomplete="off">
{% else %}
<input form="form" class="UpperCaseInput" name="key" value="" autocomplete="off">
{% endif %}
{% if errors|length > 0 %}
<br/>
<div class="FormFieldError">{{ errors[0] }}</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
@@ -49,14 +62,15 @@
<tbody>
<tr align="center">
<td>
<form action="{{ getLink('account/2fa/app/enable') }}" method="post" style="padding:0;margin:0;">
<form id="form" action="{{ getLink('account/2fa/app/enable') }}" method="post" style="padding:0;margin:0;">
<input type="hidden" name="action" value="request" />
<input type="hidden" name="action" value="request" />
{{ csrf() }}
{{ csrf() }}
{% set button_name = 'Request' %}
{{ include('buttons.base.html.twig') }}
{% set button_name = 'Request' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/register') }}" method="post" style="padding:0;margin:0;">
@@ -79,3 +93,9 @@
</tr>
</tbody>
</table>
<style>
.UpperCaseInput {
text-transform: uppercase;
}
</style>

View File

@@ -1 +0,0 @@
TODO

View File

@@ -15,7 +15,7 @@
<div class="LabelV200" style="float:left;">Authenticator App
Token:
</div>
<input form="form-code" id="auth-code" name="auth-code" maxlength="6" autocomplete="off"></div>
<input form="form" id="auth-code" name="auth-code" maxlength="6" autocomplete="off"></div>
</td>
</tr>
</tbody>
@@ -33,7 +33,7 @@
<tbody>
<tr align="center" valign="top">
<td>
<form id="form-code" action="{{ getLink('account/manage') }}" method="post">
<form id="form" action="{{ getLink('account/manage') }}" method="post">
{{ csrf() }}

View File

@@ -0,0 +1,25 @@
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right; width: 135px;">
<form action="{{ getLink('account/2fa/app/disable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Unlink' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
<b>Your Tibia account is <span style="color: green">connected</span> to an authenticator app.</b>
<p>If you do not want to use an authenticator app any longer, you can "Unlink" the authenticator
App. Note, however, an authenticator app is an important security feature which helps to
prevent any unauthorized access to your Tibia account.</p></td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>

View File

@@ -63,7 +63,7 @@
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV150 {{ wrongCode ? 'red' : '' }}" style="float:left;"><label
for="email-code">Email Code:</label></div>
<input form="form-code" id="auth-code" name="email-code" maxlength="15"
<input form="form" id="auth-code" name="email-code" maxlength="15"
autocomplete="off">
{% if wrongCode %}
<br/>
@@ -86,7 +86,7 @@
<tbody>
<tr align="center" valign="top">
<td>
<form id="form-code" method="post" action="{{ getLink('account/2fa/email/disable') }}">
<form id="form" method="post" action="{{ getLink('account/2fa/email/disable') }}">
{{ csrf() }}
<input type="hidden" name="save" value="1">

View File

@@ -12,7 +12,7 @@
<td>Enter the email code below to enable <b>two-factor email code authentication</b>. Note
that this code is only valid for 24 hours.<br><br>
<div class="AttentionSign"><img src="{{ template_path }}/images/global/content/attentionsign.gif"></div>
<b>Note:</b> Once you have email code authentication activated, an <b>email code</b> will be
<b>Note:</b> Once you have email code authentication enabled, an <b>email code</b> 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 <b>most recent
email code</b> you have received.
@@ -64,7 +64,7 @@
the email code you received at the email address assigned to your account.
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV150 {{ wrongCode ? 'red' : '' }}" style="float:left;">Email Code:</div>
<input form="confirmActivateForm" name="auth-code" maxlength="6">
<input form="form" name="auth-code" maxlength="6" autocomplete="off">
{% if wrongCode %}
<br/>
<div class="LabelV150" style="float:left;">&nbsp; </div>
@@ -87,7 +87,7 @@
<tbody>
<tr align="center" valign="top">
<td>
<form id="confirmActivateForm" action="{{ getLink('account/2fa/email/enable') }}" method="post" style="padding:0;margin:0;">
<form id="form" action="{{ getLink('account/2fa/email/enable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
<input type="hidden" name="save" value="1">

View File

@@ -38,11 +38,11 @@
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td><b>Email code authentication is activated for your account.</b><br><br>Please enter the <b>most
<td><b>Email code authentication is enabled for your account.</b><br><br>Please enter the <b>most
recent email code</b> you have received in order to log in.<br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV150 {{ wrongCode ? 'red' : '' }}" style="float:left;"><label for="email-code">Email Code:</label></div>
<input form="form-code" id="auth-code" name="auth-code" maxlength="15" autocomplete="off">
<input form="form" id="auth-code" name="auth-code" maxlength="15" autocomplete="off">
{% if wrongCode %}
<br/>
<div class="LabelV150" style="float:left;">&nbsp; </div>
@@ -64,7 +64,7 @@
<tbody>
<tr align="center" valign="top">
<td>
<form id="form-code" method="post" action="{{ getLink('account/manage') }}">
<form id="form" method="post" action="{{ getLink('account/manage') }}">
{{ csrf() }}
<input type="hidden" name="account_login" value="{{ account_login ?? '' }}" />

View File

@@ -9,12 +9,12 @@
<form action="{{ getLink('account/2fa/email/disable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Deactivate' %}
{% set button_name = 'Disable' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
<b>Two-Factor Email Code Authentication <span style="color: green">Activated</span>!</b>
<p>To disable <b>email code authentication</b>, click on the "Deactivate" button.</p>
<b>Two-Factor Email Code Authentication <span style="color: green">Enabled</span>!</b>
<p>To disable <b>email code authentication</b>, click on the "Disable" button.</p>
<!--p>You will have to confirm the deactivation by entering an <b>email code</b> which will be sent
to the email address assigned to your account.</p-->
</td>

View File

@@ -8,7 +8,7 @@
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td class="LabelV"><b>Activate email code authentication for your account!</b>
<td class="LabelV"><b>Enable email code authentication for your account!</b>
<div style="float: right; font-size: 1px;">
<form action="{{ getLink('account/2fa/email/enable') }}" method="post" style="margin: 0; padding: 0;">
{{ csrf() }}

View File

@@ -0,0 +1,22 @@
{% if logged and account_logged.getCustomField('2fa_type') == 1 %}
{% set header = 'Two-Factor Email Code Authentication' %}
{% set text = 'Your account is currently protected by email code authentication. If you prefer to use a <strong>two-factor authentication app</strong>, you have to "Disable" email code authentication first.' %}
{% else %}
{% set header = 'Two-Factor App Code Authentication' %}
{% set text = 'Your account is currently protected by an authenticator app. If you prefer to use the <strong>two-factor email code authentication</strong>, you have to "Unlink" the authenticator app first.' %}
{% endif %}
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td><b>{{ header|raw }}</b>
<p>{{ text|raw }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>

View File

@@ -1,18 +0,0 @@
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div class="InTableRightButtonContainer"></div>
<b>Two-Factor Authenticator App</b>
<p>Your account is currently protected by email code authentication. If you prefer to use a <b>two-factor
authentication app</b>, you have to "Deactivate" email code authentication first.</p>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>