Improve MFA integration

Cleanup the code. Add password validation and error messages.
This commit is contained in:
danpros 2024-05-24 10:12:33 +07:00
commit 505975e565
4 changed files with 176 additions and 148 deletions

View file

@ -40,7 +40,7 @@ function create_user($userName, $password, $role)
file_put_contents($file, "password = " . password_hash($password, PASSWORD_DEFAULT) . "\n" .
"encryption = password_hash\n" .
"role = " . $role . "\n" .
"mfa_secret = none\n", LOCK_EX);
"mfa_secret = disabled\n", LOCK_EX);
return true;
}
}
@ -56,6 +56,7 @@ function session($user, $pass)
$user_enc = user('encryption', $user);
$user_pass = user('password', $user);
$user_role = user('role', $user);
$mfa = user('mfa_secret', $user);
if(is_null($user_enc) || is_null($user_pass) || is_null($user_role)) {
return $str = '<div class="error-message"><ul><li class="alert alert-danger">' . i18n('Invalid_Error') . '</li></ul></div>';
@ -65,7 +66,6 @@ function session($user, $pass)
if (password_verify($pass, $user_pass)) {
if (session_status() == PHP_SESSION_NONE) session_start();
if (password_needs_rehash($user_pass, PASSWORD_DEFAULT)) {
$mfa = user('mfa_secret', $user);
update_user($user, $pass, $user_role, $mfa);
}
$_SESSION[site_url()]['user'] = $user;
@ -75,7 +75,6 @@ function session($user, $pass)
}
} else if (old_password_verify($pass, $user_enc, $user_pass)) {
if (session_status() == PHP_SESSION_NONE) session_start();
$mfa = user('mfa_secret', $user);
update_user($user, $pass, $user_role, $mfa);
$_SESSION[site_url()]['user'] = $user;
header('location: admin');

View file

@ -4,56 +4,61 @@ if (isset($_SESSION[site_url()]['user'])) {
$user = $_SESSION[site_url()]['user'];
}
use PragmaRX\Google2FA\Google2FA;
use BaconQrCode\Renderer\GDLibRenderer;
use BaconQrCode\Writer;
use PragmaRX\Google2FA\Google2FA;
use BaconQrCode\Renderer\GDLibRenderer;
use BaconQrCode\Writer;
if (user('mfa_secret', $user) == 'disabled') {
$google2fa = new Google2FA();
$mfasecret = $google2fa->generateSecretKey();
$mfa_state = user('mfa_secret', $user);
$g2faUrl = $google2fa->getQRCodeUrl(
$user,
site_url(),
$mfasecret
);
if (is_null($mfa_state) || $mfa_state == 'disabled') {
$google2fa = new Google2FA();
$mfasecret = $google2fa->generateSecretKey();
$renderer = new GDLibRenderer(400);
$writer = new Writer($renderer);
$g2faUrl = $google2fa->getQRCodeUrl(
$user,
site_url(),
$mfasecret
);
$qrcode_image = base64_encode($writer->writeString($g2faUrl));
$renderer = new GDLibRenderer(400);
$writer = new Writer($renderer);
$qrcode_image = base64_encode($writer->writeString($g2faUrl));
}
?>
<h2><?php echo i18n('config_mfa'); echo ': ' . $user; ?></h2>
<br>
<?php if (isset($error)) { ?>
<div class="error-message"><?php echo $error ?></div>
<?php } ?>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo get_csrf(); ?>">
<input type="hidden" name="username" value="<?php echo $user; ?>">
<?php if (user('mfa_secret', $user) == 'disabled') {
echo '<div style="text-align:center;width:100%;"><img style="margin:-10px auto;" src="data:image/png;base64, '.$qrcode_image.' "/></div>
<span style="text-align:center;width:100%;float:left;"><small>'.i18n('manualsetupkey').': '.$mfasecret.'</small></span>
<div class="form-group row">
<label for="site.url" class="col-sm-2 col-form-label">'.i18n('MFACode').'</label>
<div class="col-sm-10">
<input type="text" name="mfacode" class="form-control" id="mfacode" value="" placeholder="'.i18n('verify_code').'">
</div>
</div>
<div class="form-group row">
<label for="site.url" class="col-sm-2 col-form-label">'.i18n('Password').'</label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control" id="password" value="" placeholder="'.i18n('verify_password').'">
</div>
</div>
<input type="hidden" name="mfa_secret" value="'.$mfasecret.'">
<input type="submit" class="btn btn-primary" style="width:100px;" value="'.i18n('Save').'">';
} else {
echo '<input type="hidden" name="mfa_secret" value="disabled">
<div class="form-group row">
<label for="site.url" class="col-sm-2 col-form-label">'.i18n('Password').'</label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control" id="password" value="" placeholder="'.i18n('verify_password').'">
</div>
</div>
<input type="submit" class="btn btn-primary" style="width:100px;" value="'.i18n('disablemfa').'">';
} ?>
</form>
<?php if (is_null($mfa_state) || $mfa_state == 'disabled') {?>
<div style="text-align:center;width:100%;"><img style="margin:-10px auto;" src="data:image/png;base64, <?php echo $qrcode_image; ?>"/></div>
<span style="text-align:center;width:100%;float:left;"><small><?php echo i18n('manualsetupkey') . ': ' . $mfasecret; ?></small></span>
<div class="form-group row">
<label for="site.url" class="col-sm-2 col-form-label"><?php echo i18n('MFACode');?></label>
<div class="col-sm-10">
<input type="text" name="mfacode" class="form-control" id="mfacode" value="" placeholder="<?php echo i18n('verify_code');?>">
</div>
</div>
<div class="form-group row">
<label for="site.url" class="col-sm-2 col-form-label"><?php echo i18n('Password');?></label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control" id="password" value="" placeholder="<?php echo i18n('verify_password');?>">
</div>
</div>
<input type="hidden" name="mfa_secret" value="<?php echo $mfasecret;?>">
<input type="submit" class="btn btn-primary" style="width:100px;" value="<?php echo i18n('Save');?>">'
<?php } else { ?>
<input type="hidden" name="mfa_secret" value="disabled">
<div class="form-group row">
<label for="site.url" class="col-sm-2 col-form-label"><?php echo i18n('Password');?></label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control" id="password" value="" placeholder="<?php echo i18n('verify_password');?>">
</div>
</div>
<input type="submit" class="btn btn-primary" value="<?php echo i18n('disablemfa');?>">
<?php } ?>
</form>

View file

@ -121,82 +121,82 @@ get('/index', function () {
post('/login', function () {
$proper = (is_csrf_proper(from($_REQUEST, 'csrf_token')));
if (config('login.protect.system') === 'google') {
$captcha = isCaptcha(from($_REQUEST, 'g-recaptcha-response'));
} elseif (config('login.protect.system') === 'cloudflare') {
$captcha = isTurnstile(from($_REQUEST, 'cf-turnstile-response'));
} else {
$captcha = config('login.protect.system');
if (is_null($captcha) || $captcha === 'disabled') {
$captcha = true;
} elseif ($captcha === 'cloudflare') {
$captcha = isTurnstile(from($_REQUEST, 'cf-turnstile-response'));
} elseif ($captcha === 'google') {
$captcha = isCaptcha(from($_REQUEST, 'g-recaptcha-response'));
}
$user = from($_REQUEST, 'user');
$pass = from($_REQUEST, 'password');
if ($proper && $captcha && !empty($user) && !empty($pass)) {
if (user('mfa_secret', $user) !== "disabled") {
$mfa_secret = user('mfa_secret', $user);
$mfacode = from($_REQUEST, 'mfacode');
$google2fa = new Google2FA();
if ($google2fa->verifyKey($mfa_secret, $mfacode, '1')) {
session($user, $pass);
$log = session($user, $pass);
$mfa_secret = user('mfa_secret', $user);
if ($proper && $captcha && !empty($user) && !empty($pass)) {
if (!is_null($mfa_secret) && $mfa_secret !== "disabled") {
$mfacode = from($_REQUEST, 'mfacode');
$google2fa = new Google2FA();
if ($google2fa->verifyKey($mfa_secret, $mfacode, '1')) {
session($user, $pass);
$log = session($user, $pass);
if (!empty($log)) {
if (!empty($log)) {
config('views.root', 'system/admin/views');
config('views.root', 'system/admin/views');
render('login', array(
'title' => generate_title('is_default', i18n('Login')),
'description' => i18n('Login') . ' ' . blog_title(),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $log . '</ul>',
'type' => 'is_login',
'is_login' => true,
'bodyclass' => 'in-login',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; ' . i18n('Login')
));
}
} else {
$message['error'] .= '<li class="alert alert-danger">' . i18n('MFA_Error') . '</li>';
render('login', array(
'title' => generate_title('is_default', i18n('Login')),
'description' => i18n('Login') . ' ' . blog_title(),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $log . '</ul>',
'type' => 'is_login',
'is_login' => true,
'bodyclass' => 'in-login',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; ' . i18n('Login')
));
}
} else {
$message['error'] = '';
$message['error'] .= '<li class="alert alert-danger">' . i18n('MFA_Error') . '</li>';
config('views.root', 'system/admin/views');
config('views.root', 'system/admin/views');
render('login', array(
'title' => generate_title('is_default', i18n('Login')),
'description' => i18n('Login') . ' ' . blog_title(),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $message['error'] . '</ul>',
'username' => $user,
'password' => $pass,
'type' => 'is_login',
'is_login' => true,
'bodyclass' => 'in-login',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; ' . i18n('Login')
));
}
} else {
session($user, $pass);
$log = session($user, $pass);
render('login', array(
'title' => generate_title('is_default', i18n('Login')),
'description' => i18n('Login') . ' ' . blog_title(),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $message['error'] . '</ul>',
'username' => $user,
'password' => $pass,
'type' => 'is_login',
'is_login' => true,
'bodyclass' => 'in-login',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; ' . i18n('Login')
));
}
} else {
session($user, $pass);
$log = session($user, $pass);
if (!empty($log)) {
if (!empty($log)) {
config('views.root', 'system/admin/views');
config('views.root', 'system/admin/views');
render('login', array(
'title' => generate_title('is_default', i18n('Login')),
'description' => i18n('Login') . ' ' . blog_title(),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $log . '</ul>',
'type' => 'is_login',
'is_login' => true,
'bodyclass' => 'in-login',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; ' . i18n('Login')
));
}
}
render('login', array(
'title' => generate_title('is_default', i18n('Login')),
'description' => i18n('Login') . ' ' . blog_title(),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $log . '</ul>',
'type' => 'is_login',
'is_login' => true,
'bodyclass' => 'in-login',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; ' . i18n('Login')
));
}
}
} else {
$message['error'] = '';
if (empty($user)) {
@ -447,7 +447,7 @@ post('/edit/password', function() {
} else {
$login = site_url() . 'login';
header("location: $login");
}
}
});
get('/edit/mfa', function () {
@ -474,34 +474,57 @@ post('/edit/mfa', function() {
if (login() && $proper) {
$username = from($_REQUEST, 'username');
$mfa_secret = from($_REQUEST, 'mfa_secret');
$mfacode = from($_REQUEST, 'mfacode');
$user = $_SESSION[site_url()]['user'];
$role = user('role', $user);
$old_password = user('password', $user);
$password = from($_REQUEST, 'password');
$message['error'] = '';
if ($user === $username) {
if ($mfa_secret !== "disabled") {
$mfacode = from($_REQUEST, 'mfacode');
$google2fa = new Google2FA();
if ($google2fa->verifyKey($mfa_secret, $mfacode, '1')) {
$file = 'config/users/' . $user . '.ini';
if (file_exists($file)) {
if (!empty($mfa_secret)) {
update_user($user, $password, $role, $mfa_secret);
}
}
$redir = site_url() . 'admin';
header("location: $redir");
} else {
$redir = site_url() . 'admin';
header("location: $redir");
}
} else {
$file = 'config/users/' . $user . '.ini';
if (file_exists($file)) {
update_user($user, $password, $role, 'disabled');
}
$redir = site_url() . 'admin';
header("location: $redir");
}
if (!is_null($mfa_secret) && $mfa_secret !== "disabled") {
$google2fa = new Google2FA();
if ($google2fa->verifyKey($mfa_secret, $mfacode)) {
if (password_verify($password, $old_password)) {
if (!empty($mfa_secret)) {
update_user($user, $password, $role, $mfa_secret);
}
} else {
$message['error'] .= '<li class="alert alert-danger">' . i18n('Pass_Error') . '</li>';
}
} else {
$message['error'] .= '<li class="alert alert-danger">' . i18n('MFA_Error') . '</li>';
}
config('views.root', 'system/admin/views');
render('edit-mfa', array(
'title' => generate_title('is_default', i18n('config_mfa')),
'description' => safe_html(strip_tags(blog_description())),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $message['error'] . '</ul>',
'type' => 'is_profile',
'is_admin' => true,
'bodyclass' => 'edit-mfa',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; '. i18n('config_mfa'),
));
} else {
if (password_verify($password, $old_password)) {
update_user($user, $password, $role, 'disabled');
} else {
$message['error'] .= '<li class="alert alert-danger">' . i18n('Pass_Error') . '</li>';
}
config('views.root', 'system/admin/views');
render('edit-mfa', array(
'title' => generate_title('is_default', i18n('config_mfa')),
'description' => safe_html(strip_tags(blog_description())),
'canonical' => site_url(),
'metatags' => generate_meta(null, null),
'error' => '<ul>' . $message['error'] . '</ul>',
'type' => 'is_profile',
'is_admin' => true,
'bodyclass' => 'edit-mfa',
'breadcrumb' => '<a href="' . site_url() . '">' . config('breadcrumb.home') . '</a> &#187; '. i18n('config_mfa'),
));
}
} else {
$redir = site_url();
header("location: $redir");
@ -509,8 +532,9 @@ post('/edit/mfa', function() {
} else {
$login = site_url() . 'login';
header("location: $login");
}
}
});
// Edit the frontpage
get('/edit/frontpage', function () {

View file

@ -1226,7 +1226,7 @@ function get_author($name)
$author->about = MarkdownExtra::defaultTransform(remove_html_comments($content));
$author->description = get_content_tag("d", $content, get_description($author->about));
$author->avatar = get_content_tag("image", $content, site_url() . 'system/resources/images/logo-small.png');
$toc = explode('<!--toc-->', $author->about);
@ -3440,9 +3440,9 @@ function head_contents()
$output .= '<meta charset="utf-8" />' . "\n";
$output .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
$output .= '<meta name="viewport" content="width=device-width, initial-scale=1" />' . "\n";
if (config('show.version') == 'true') {
$output .= '<meta name="generator" content="' . $version . '" />' . "\n";
}
if (config('show.version') == 'true') {
$output .= '<meta name="generator" content="' . $version . '" />' . "\n";
}
$output .= $favicon;
$output .= '<link rel="sitemap" href="' . site_url() . 'sitemap.xml" />' . "\n";
$output .= '<link rel="alternate" type="application/rss+xml" title="' . blog_title() . ' Feed" href="' . site_url() . 'feed/rss" />' . "\n";
@ -3583,8 +3583,8 @@ function isCaptcha($reCaptchaResponse)
// Cloudflare Turnstile
function isTurnstile($turnstileResponse)
{
$public = config("login.protect.public");
$private = config("login.protect.private");
$public = config("login.protect.public");
$private = config("login.protect.private");
$ip = $_SERVER['REMOTE_ADDR'];
$url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';