clean up MFA code

This commit is contained in:
sheepish
2025-10-12 21:23:35 +00:00
committed by Spine
parent 23f206a75e
commit 291c488c48
9 changed files with 53 additions and 98 deletions

View File

@@ -1,37 +0,0 @@
<?php
/** @phpstan-var \Gazelle\User $Viewer */
/** @phpstan-var \Twig\Environment $Twig */
declare(strict_types=1);
namespace Gazelle;
$user = new Manager\User()->findById((int)($_REQUEST['userid'] ?? 0));
if (is_null($user)) {
Error404::error();
}
if ($user->MFA()->enabled()) {
Error400::error('MFA is already configured');
}
if (session_status() === PHP_SESSION_NONE) {
session_start(['read_and_close' => true]);
}
if (empty($_SESSION['private_key'])) {
Error404::error();
}
$recoveryKeys = $user->MFA()->create(new Manager\UserToken(), $_SESSION['private_key'], $Viewer);
if (!$recoveryKeys) {
Error400::error('failed to create MFA');
}
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
unset($_SESSION['private_key']);
session_write_close();
echo $Twig->render('user/mfa/complete.twig', [
'keys' => $recoveryKeys,
]);

View File

@@ -1,4 +1,5 @@
<?php
/** @phpstan-var \Gazelle\User $user */
/** @phpstan-var \Gazelle\User $Viewer */
/** @phpstan-var \Twig\Environment $Twig */
@@ -6,27 +7,39 @@ declare(strict_types=1);
namespace Gazelle;
if (session_status() === PHP_SESSION_NONE) {
session_start(['read_and_close' => true]);
if (!isset($user)) {
Error500::error();
}
$mfa = $user->MFA();
if ($mfa->enabled()) {
Error400::error('MFA is already configured');
}
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$mfa = $Viewer->MFA();
$valid = true;
if (!empty($_SESSION['private_key'])) {
if (isset($_SESSION['private_key'], $_POST['mfa'])) {
$secret = $_SESSION['private_key'];
if (isset($_POST['mfa'])) {
if ($mfa->verifyCode($secret, trim($_POST['mfa']))) {
header("Location: user.php?action=mfa&do=complete&userid={$Viewer->id}");
exit;
if ($mfa->verifyCode($secret, trim($_POST['mfa']))) {
$recoveryKeys = $user->MFA()->create(new Manager\UserToken(), $_SESSION['private_key'], $Viewer);
if (!$recoveryKeys) {
Error400::error('failed to create MFA');
}
$valid = false;
unset($_SESSION['private_key']);
session_write_close();
echo $Twig->render('user/mfa/complete.twig', [
'keys' => $recoveryKeys,
]);
exit;
}
session_abort();
$valid = false;
} else {
$secret = $mfa->generateSessionSecret();
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION['private_key'] = $secret;
authorize();
$_SESSION['private_key'] = $secret = $mfa->generateSessionSecret();
session_write_close();
}
@@ -34,5 +47,4 @@ echo $Twig->render('user/mfa/configure.twig', [
'valid' => $valid,
'qrcode' => $mfa->generateQrCode(secret: $secret, logo: QRCODE_LOGO),
'secret' => $secret,
'viewer' => $Viewer,
]);

View File

@@ -1,10 +0,0 @@
<?php
/** @phpstan-var \Twig\Environment $Twig */
declare(strict_types=1);
namespace Gazelle;
echo $Twig->render('user/mfa/remove.twig', [
'bad' => isset($_GET['invalid']),
]);

View File

@@ -7,28 +7,14 @@ namespace Gazelle;
$user = new Manager\User()->findById((int)($_REQUEST['userid'] ?? 0));
if (is_null($user)) {
Error404::error();
Error404::error('No such user');
}
if ($user->id != $Viewer->id && !$Viewer->permitted('users_mod')) {
Error403::error();
}
switch ($_GET['do'] ?? '') {
case 'configure':
if ($user->MFA()->enabled()) {
Error400::error('MFA is already configured');
}
include_once __DIR__ . '/configure.php';
break;
case 'complete':
include_once __DIR__ . '/complete.php';
break;
case 'remove':
include_once __DIR__ . '/remove.php';
break;
default:
Error404::error();
}
require_once __DIR__ . '/' . match ($_GET['do'] ?? '') {
'configure' => 'configure.php',
'remove' => 'remove.php',
default => Error404::error(),
};

View File

@@ -1,27 +1,30 @@
<?php
/** @phpstan-var \Gazelle\User $user */
/** @phpstan-var \Gazelle\User $Viewer */
/** @phpstan-var \Twig\Environment $Twig */
declare(strict_types=1);
namespace Gazelle;
// Remove MFA. Users have to enter their password, moderators skip this step.
$user = new Manager\User()->findById((int)($_GET['userid'] ?? 0));
if (is_null($user)) {
Error404::error();
if (!isset($user)) {
Error500::error();
}
if (!$user->MFA()->enabled()) {
Error400::error('No MFA configured');
}
if (!$Viewer->permitted('users_edit_password')) {
// Remove MFA. Users have to enter their password, moderators skip this step.
if ($Viewer->permitted('users_edit_password')) {
authorize();
} else {
if ($user->id !== $Viewer->id) {
Error403::error();
} elseif (empty($_POST['password'])) {
include_once 'confirm.php';
} elseif (!isset($_POST['password'])) {
echo $Twig->render('user/mfa/remove.twig', ['bad' => false]);
exit;
} elseif (!$user->validatePassword($_POST['password'])) {
header("Location: user.php?action=mfa&do=remove&invalid=1&userid={$user->id}");
echo $Twig->render('user/mfa/remove.twig', ['bad' => true]);
exit;
}
}

View File

@@ -9,7 +9,7 @@
<td class="label">Two-factor Authentication:</td>
<td>
{% if user.MFA.enabled %}
<a href="user.php?action=mfa&amp;page=user&amp;do=remove&amp;userid={{ user.id }}">Click here to remove</a>
<a href="user.php?action=mfa&amp;do=remove&amp;userid={{ user.id }}&amp;auth={{ viewer.auth }}">Click here to remove</a>
{% else %}
Currently inactive
{% endif %}

View File

@@ -120,7 +120,7 @@
{% endif %}
{% if viewer.permitted('users_edit_password') %}
{% include 'user/edit-password.twig' with {'user': user} only %}
{% include 'user/edit-password.twig' with {'user': user, 'viewer': viewer} only %}
{% endif %}
</table>

View File

@@ -6,17 +6,17 @@
<p>We have generated a secure secret that only we should share. Please
import it into your phone, either by scanning the QR key, or copying
in the Secret Text below it. We recommend using the Authy app which
you can find in the <a href="https://itunes.apple.com/gb/app/authy/id494168017?mt=8">App
in the Secret Text below it. We recommend using the 2FAS app which
you can find in the <a href="https://apps.apple.com/gb/app/2fa-authenticator-2fas/id1217793794">Apple App
Store</a> or
<a href="https://play.google.com/store/apps/details?id=com.authy.authy&hl=en_GB">Play
Store</a>. Other authenticator apps will work too.</p>
<a href="https://play.google.com/store/apps/details?id=com.twofasapp">Google Play
Store</a>. Other authenticator apps that support TOTP will work too.</p>
</div>
<div class="box box2">
<div class="center pad">
<div>
<img src="{{ qrcode.getDataUri }}">
<img alt="MFA QR code" src="{{ qrcode.getDataUri }}">
<div class="mfa_text">Secret Text: <span>{{ secret }}</span></div>
{% if not valid %}
<p class="warning">Please ensure you have imported the correct secret key
@@ -37,5 +37,6 @@
<label for="mfa"><strong>Authentication Key</strong></label>
&nbsp;&nbsp;&nbsp;<input type="text" size="8" name="mfa" id="mfa"/>
&nbsp;&nbsp;&nbsp;<input type="submit" value="Authenticate!">
</form>
</div>
{{ footer() }}

View File

@@ -85,7 +85,7 @@
{% set has_mfa = user.MFA.enabled %}
Multi Factor authentication is currently <strong class="{{ has_mfa ? 'r99' : 'warning' }}">{{ has_mfa ? 'enabled' : 'disabled' }}</strong> for your account.
<br><br>
<a href="user.php?action=mfa&amp;do={{ has_mfa ? 'remove' : 'configure' }}&amp;userid={{ user.id }}">Click here to {{ has_mfa ? 'remove' : 'configure' }}</a>
<a href="user.php?action=mfa&amp;do={{ has_mfa ? 'remove' : 'configure' }}&amp;userid={{ user.id }}&amp;auth={{ viewer.auth }}">Click here to {{ has_mfa ? 'remove' : 'configure' }}</a>
</td>
</tr>