mirror of
https://github.com/getnamingo/registry.git
synced 2025-07-05 18:43:19 +02:00
Improvements of the 2FA validation code UI
This commit is contained in:
parent
b46ff2286c
commit
6515bcda80
5 changed files with 162 additions and 29 deletions
|
@ -142,25 +142,31 @@ class Auth
|
|||
|
||||
$auth->login($email, $password, $rememberDuration);
|
||||
|
||||
// check if a valid code is provided
|
||||
global $container;
|
||||
$db = $container->get('db');
|
||||
$tfa = $db->selectRow('SELECT tfa_enabled, tfa_secret FROM users WHERE id = ?', [$auth->getUserId()]);
|
||||
$tfa_secret = $db->selectValue('SELECT tfa_secret FROM users WHERE id = ?', [$auth->getUserId()]);
|
||||
|
||||
if ($tfa) {
|
||||
if ($tfa['tfa_enabled'] == 1) {
|
||||
$tfaService = new \RobThree\Auth\TwoFactorAuth('Namingo');
|
||||
if ($tfaService->verifyCode($tfa['tfa_secret'], $code) === true) {
|
||||
return true;
|
||||
} else {
|
||||
self::$auth->logOut();
|
||||
redirect()->route('login')->with('error','Incorrect 2FA Code. Please check and enter the correct code. 2FA codes are time-sensitive. For continuous issues, contact support.');
|
||||
}
|
||||
} elseif ($tfa['tfa_enabled'] == 0) {
|
||||
return true;
|
||||
if (!is_null($tfa_secret)) {
|
||||
if (!is_null($code) && $code !== "" && preg_match('/^\d{6}$/', $code)) {
|
||||
// If tfa_secret exists and is not empty, verify the 2FA code
|
||||
$tfaService = new \RobThree\Auth\TwoFactorAuth('Namingo');
|
||||
if ($tfaService->verifyCode($tfa_secret, $code) === true) {
|
||||
// 2FA verification successful
|
||||
return true;
|
||||
} else {
|
||||
// 2FA verification failed
|
||||
self::$auth->logOut();
|
||||
redirect()->route('login')->with('error','Incorrect 2FA Code. Please check and enter the correct code. 2FA codes are time-sensitive. For continuous issues, contact support.');
|
||||
//return false; // Ensure to return false or handle accordingly
|
||||
}
|
||||
} else {
|
||||
self::$auth->logOut();
|
||||
redirect()->route('login')->with('error','2FA Code Required. Please enter your 6-digit 2FA code to proceed with the login.');
|
||||
//return false;
|
||||
}
|
||||
} else {
|
||||
self::$auth->logOut();
|
||||
redirect()->route('login')->with('error','Temporary Database Issue. Please try again shortly. If this problem persists, kindly reach out to our support team for assistance.');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (InvalidEmailException $e) {
|
||||
|
|
|
@ -34,6 +34,21 @@ class AuthController extends Controller
|
|||
return view($response,'auth/login.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show 2FA verification form.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return mixed
|
||||
*/
|
||||
public function verify2FA(Request $request, Response $response){
|
||||
if (isset($_SESSION['is2FAEnabled']) && $_SESSION['is2FAEnabled'] === true) {
|
||||
return view($response, 'auth/verify2fa.twig');
|
||||
} else {
|
||||
return $response->withHeader('Location', '/login')->withStatus(302);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
|
@ -42,20 +57,34 @@ class AuthController extends Controller
|
|||
*/
|
||||
public function login(Request $request, Response $response){
|
||||
global $container;
|
||||
|
||||
$data = $request->getParsedBody();
|
||||
if(isset($data['remember'])){
|
||||
$remember = $data['remember'];
|
||||
}else{
|
||||
$remember = null;
|
||||
$db = $container->get('db');
|
||||
$is2FAEnabled = $db->selectValue('SELECT tfa_enabled, tfa_secret FROM users WHERE email = ?', [$data['email']]);
|
||||
|
||||
// If 2FA is enabled and no code is provided, redirect to 2FA code entry
|
||||
if($is2FAEnabled && !isset($data['code'])) {
|
||||
$_SESSION['2fa_email'] = $data['email'];
|
||||
$_SESSION['2fa_password'] = $data['password'];
|
||||
$_SESSION['is2FAEnabled'] = true;
|
||||
return $response->withHeader('Location', '/login/verify')->withStatus(302);
|
||||
} else {
|
||||
$email = $data['email'];
|
||||
$password = $data['password'];
|
||||
$_SESSION['is2FAEnabled'] = false;
|
||||
}
|
||||
if(isset($data['code'])){
|
||||
$code = $data['code'];
|
||||
}else{
|
||||
$code = null;
|
||||
|
||||
// If the 2FA code is present, this might be a 2FA verification attempt
|
||||
if (isset($data['code']) && isset($_SESSION['2fa_email']) && isset($_SESSION['2fa_password'])) {
|
||||
$email = $_SESSION['2fa_email'];
|
||||
$password = $_SESSION['2fa_password'];
|
||||
// Clear the session variables immediately after use
|
||||
unset($_SESSION['2fa_email'], $_SESSION['2fa_password'], $_SESSION['is2FAEnabled']);
|
||||
}
|
||||
$login = Auth::login($data['email'], $data['password'], $remember, $code);
|
||||
if($login===true) {
|
||||
|
||||
$login = Auth::login($email, $password, $data['remember'] ?? null, $data['code'] ?? null);
|
||||
unset($_SESSION['2fa_email'], $_SESSION['2fa_password'], $_SESSION['is2FAEnabled']);
|
||||
|
||||
if ($login===true) {
|
||||
$db = $container->get('db');
|
||||
$currentDateTime = new \DateTime();
|
||||
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
||||
|
|
|
@ -33,10 +33,6 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">2FA Code</label>
|
||||
<input name="code" type="text" class="form-control" autocomplete="off" placeholder="Enter 6-digit code" pattern="\d{6}" maxlength="6" minlength="6" inputmode="numeric">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-check">
|
||||
<input name="remember" value="remember" id="remember" type="checkbox" class="form-check-input"/>
|
||||
|
|
101
cp/resources/views/auth/verify2fa.twig
Normal file
101
cp/resources/views/auth/verify2fa.twig
Normal file
|
@ -0,0 +1,101 @@
|
|||
{% extends "layouts/auth.twig" %}
|
||||
|
||||
{% block title %}Two-Factor Authentication{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page page-center">
|
||||
<div class="container container-tight py-4">
|
||||
<div class="text-center mb-4">
|
||||
<a href="." class="navbar-brand navbar-brand-autodark"><img src="./static/logo-bw.svg" height="36" alt=""></a>
|
||||
{% include 'partials/flash.twig' %}
|
||||
</div>
|
||||
<form class="card card-md" action="{{route('login')}}" id="login" method="post" autocomplete="off" novalidate>
|
||||
{{ csrf.field | raw }}
|
||||
<div class="card-body">
|
||||
<h2 class="card-title card-title-lg text-center mb-4">Two-Factor Authentication</h2>
|
||||
<p class="my-4 text-center">Please enter the <strong>6-digit</strong> verification code from your authenticator app to continue.</p>
|
||||
<div class="my-5">
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="text" class="form-control form-control-lg text-center py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<div class="btn-list flex-nowrap">
|
||||
<a href="{{route('login')}}" class="btn w-100">
|
||||
Cancel
|
||||
</a>
|
||||
<input type="hidden" name="code" id="fullCode">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
Verify
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelectorAll('[data-code-input]').forEach(function(input, idx, inputs) {
|
||||
// Handle input to move forward
|
||||
input.addEventListener('input', function(e) {
|
||||
// Move to the next box if the current one is filled and it's not the last box
|
||||
if (input.value && idx < inputs.length - 1) {
|
||||
inputs[idx + 1].focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle backspace to move backward
|
||||
input.addEventListener('keydown', function(e) {
|
||||
// If backspace is pressed and the input is empty or becomes empty, focus the previous box
|
||||
if (e.key === 'Backspace' && (input.value === '' || input.value.length === 1)) {
|
||||
if (idx > 0) {
|
||||
// Delay moving focus to handle case where backspace is hit and the input is not yet empty
|
||||
setTimeout(() => inputs[idx - 1].focus(), 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('login').addEventListener('submit', function(e) {
|
||||
e.preventDefault(); // Prevent the form from submitting immediately
|
||||
|
||||
// Concatenate the values from each input
|
||||
var inputs = document.querySelectorAll('[data-code-input]');
|
||||
var code = '';
|
||||
inputs.forEach(function(input) {
|
||||
code += input.value;
|
||||
});
|
||||
|
||||
// Set the concatenated value to the hidden input
|
||||
document.getElementById('fullCode').value = code;
|
||||
console.log(code);
|
||||
// Now submit the form
|
||||
this.submit();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -26,6 +26,7 @@ $app->get('/', HomeController::class .':index')->setName('index');
|
|||
|
||||
$app->group('', function ($route) {
|
||||
$route->get('/login', AuthController::class . ':createLogin')->setName('login');
|
||||
$route->map(['GET', 'POST'], '/login/verify', AuthController::class . ':verify2FA')->setName('verify2FA');
|
||||
$route->post('/login', AuthController::class . ':login');
|
||||
$route->post('/webauthn/login/challenge', AuthController::class . ':getLoginChallenge')->setName('webauthn.login.challenge');
|
||||
$route->post('/webauthn/login/verify', AuthController::class . ':verifyLogin')->setName('webauthn.login.verify');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue