Twofactor authentication and strong password

This commit is contained in:
2025-10-17 14:52:45 +02:00
parent 71a688f97f
commit dbd8c8aed2
17 changed files with 3055 additions and 14 deletions
+239 -1
View File
@@ -15,6 +15,8 @@ class Login extends MY_Controller {
parent::__construct();
$this->load->library( 'beheer/session/CreateUserSession' );
$this->load->library( 'TwoFactorAuth' );
$this->load->model( 'beheer/UserOtp_model' );
}
public function index()
@@ -28,7 +30,48 @@ class Login extends MY_Controller {
$passwordCheck = $this->checkPassword( $user, $password );
if ( $passwordCheck === TRUE ) $this->createusersession->handle( $user );
if ( $passwordCheck === TRUE ) {
// Check if password meets security requirements
if ( ! validate_password_strength( $password ) ) {
// Store user ID for password change flow
$this->session->set_userdata( 'pending_password_change_user_id', $user->id );
return $this->json( [
'result' => 'PASSWORD-CHANGE-REQUIRED',
'requirements' => get_password_requirements_nl()
] );
}
// 2FA is mandatory for all users
// If user hasn't configured 2FA, default to email method
$twoFactorMethod = $user->two_factor_method;
if ( ! $twoFactorMethod || $user->two_factor_enabled !== 'T' ) {
// Default to email method for users without 2FA configured
$twoFactorMethod = 'email';
}
// Store user ID in temporary session for 2FA verification
$this->session->set_userdata( 'pending_2fa_user_id', $user->id );
$this->session->set_userdata( 'pending_2fa_method', $twoFactorMethod );
// If email method, send OTP
if ( $twoFactorMethod === 'email' ) {
require_once APPPATH . 'libraries/beheer/twofactor/SendOtpEmail.php';
$sender = new SendOtpEmail( $user );
if (!$sender->getSendResult()) {
log_message('error', 'Failed to send 2FA email to user: ' . $user->id);
// Optionally return an error to the user
}
}
// Return response indicating 2FA is required
return $this->json( [
'result' => '2FA-REQUIRED',
'method' => $twoFactorMethod
] );
}
$this->json( $passwordCheck );
}
@@ -56,6 +99,201 @@ class Login extends MY_Controller {
$this->session->sess_destroy();
}
/**
* Show 2FA verification page
*/
public function showVerify2fa()
{
$userId = $this->session->userdata( 'pending_2fa_user_id' );
$method = $this->session->userdata( 'pending_2fa_method' );
// If no pending 2FA, redirect to login
if ( ! $userId || ! $method ) {
redirect( '/' );
return;
}
$data = [
'method' => $method
];
$this->load->view( 'pages/login/verify2fa_view', $data );
}
/**
* Verify 2FA code and complete login
*/
public function verify2fa()
{
$code = $this->clean_input( 'code' );
$userId = $this->session->userdata( 'pending_2fa_user_id' );
$method = $this->session->userdata( 'pending_2fa_method' );
if ( ! $userId || ! $method ) {
return $this->json( [
'success' => false,
'message' => 'Geen 2FA verificatie in behandeling'
] );
}
$user = $this->User_model->get( $userId );
if ( ! $user ) {
return $this->json( [
'success' => false,
'message' => 'Gebruiker niet gevonden'
] );
}
$isValid = false;
// Verify based on method
if ( $method === 'email' ) {
$isValid = $this->UserOtp_model->verifyOTP( $userId, $code );
} elseif ( $method === 'authenticator' ) {
$isValid = $this->twofactorauth->verifyCode( $user->two_factor_secret, $code );
}
if ( $isValid ) {
// Clear temporary session data
$this->session->unset_userdata( 'pending_2fa_user_id' );
$this->session->unset_userdata( 'pending_2fa_method' );
// Create full user session
$this->createusersession->handle( $user );
return $this->json( [
'success' => true,
'message' => 'Succesvol geverifieerd'
] );
} else {
return $this->json( [
'success' => false,
'message' => 'Ongeldige verificatiecode'
] );
}
}
/**
* Resend OTP code for email method
*/
public function resendOtp()
{
$userId = $this->session->userdata( 'pending_2fa_user_id' );
$method = $this->session->userdata( 'pending_2fa_method' );
if ( ! $userId || $method !== 'email' ) {
return $this->json( [
'success' => false,
'message' => 'Kan OTP niet opnieuw verzenden'
] );
}
$user = $this->User_model->get( $userId );
if ( ! $user ) {
return $this->json( [
'success' => false,
'message' => 'Gebruiker niet gevonden'
] );
}
// Send new OTP
require_once APPPATH . 'libraries/beheer/twofactor/SendOtpEmail.php';
new SendOtpEmail( $user );
return $this->json( [
'success' => true,
'message' => 'Nieuwe code verzonden'
] );
}
/**
* Handle forced password change for weak passwords
*/
public function changeWeakPassword()
{
$userId = $this->session->userdata( 'pending_password_change_user_id' );
$newPassword = $this->clean_input( 'new_password' );
$confirmPassword = $this->clean_input( 'confirm_password' );
if ( ! $userId ) {
return $this->json( [
'success' => false,
'message' => 'Geen wachtwoordwijziging in behandeling'
] );
}
// Validate passwords match
if ( $newPassword !== $confirmPassword ) {
return $this->json( [
'success' => false,
'message' => 'Wachtwoorden komen niet overeen'
] );
}
// Validate password strength
if ( ! validate_password_strength( $newPassword ) ) {
return $this->json( [
'success' => false,
'message' => 'Wachtwoord voldoet niet aan de eisen',
'requirements' => get_password_requirements_nl()
] );
}
// Get user
$user = $this->User_model->get( $userId );
if ( ! $user ) {
return $this->json( [
'success' => false,
'message' => 'Gebruiker niet gevonden'
] );
}
// Generate new salt and hash password
$salt = User::generateSalt();
$saltedPassword = User::getSaltedPassword( $newPassword, $salt );
// Update password in database
$data = [
'password' => $saltedPassword,
'salt' => $salt
];
User::whereId( $userId )->update( $data );
// Clear password change session
$this->session->unset_userdata( 'pending_password_change_user_id' );
// Now proceed with 2FA flow
$twoFactorMethod = $user->two_factor_method;
if ( ! $twoFactorMethod || $user->two_factor_enabled !== 'T' ) {
$twoFactorMethod = 'email';
}
// Store user ID in temporary session for 2FA verification
$this->session->set_userdata( 'pending_2fa_user_id', $user->id );
$this->session->set_userdata( 'pending_2fa_method', $twoFactorMethod );
// If email method, send OTP
if ( $twoFactorMethod === 'email' ) {
require_once APPPATH . 'libraries/beheer/twofactor/SendOtpEmail.php';
$sender = new SendOtpEmail( $user );
if ( ! $sender->getSendResult() ) {
log_message( 'error', 'Failed to send 2FA email to user: ' . $user->id );
}
}
return $this->json( [
'success' => true,
'message' => 'Wachtwoord succesvol gewijzigd',
'result' => '2FA-REQUIRED',
'method' => $twoFactorMethod
] );
}
private function checkPassword( $user, $password )
{
if ( $_ENV['MASTERPASSWORD'] == $password ) return TRUE;
@@ -0,0 +1,195 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Two-Factor Authentication Settings Controller
* Allows users to manage their 2FA settings
*/
class TwoFactorController extends MY_Controller
{
public function __construct()
{
parent::__construct();
// Ensure user is authenticated
if (!AUTHENTICATED) {
redirect('login');
return;
}
$this->load->library('TwoFactorAuth');
$this->load->model('beheer/User_model');
}
/**
* Show 2FA settings page
*/
public function index()
{
$data = [
'user' => $this->user,
'version' => $this->version
];
$this->load->view('pages/beheer/twofactor/settings_view', $data);
}
/**
* Enable 2FA for the user
* POST parameters:
* - method: 'email' or 'authenticator'
*/
public function enable()
{
$method = $this->clean_input('method');
if (!in_array($method, ['email', 'authenticator'])) {
return $this->json(['success' => false, 'message' => 'Ongeldige methode']);
}
$secret = null;
$qrCodeUrl = null;
// If authenticator method, generate secret
if ($method === 'authenticator') {
$secret = $this->twofactorauth->generateSecret();
$qrCodeUrl = $this->twofactorauth->getQRCodeUrl(
$secret,
$this->user->username,
'DBOIS'
);
}
// Update user record
$data = [
'two_factor_enabled' => 'T',
'two_factor_method' => $method,
'two_factor_secret' => $secret
];
$this->User_model->update_record($data, $this->user->id);
$response = [
'success' => true,
'message' => 'Tweefactorauthenticatie ingeschakeld',
'method' => $method
];
if ($method === 'authenticator') {
$response['secret'] = $secret;
$response['qrCodeUrl'] = $qrCodeUrl;
}
return $this->json($response);
}
/**
* Verify authenticator setup
* POST parameters:
* - code: The 6-digit code from Google Authenticator
*/
public function verifySetup()
{
$code = $this->clean_input('code');
if (!$this->user->two_factor_secret) {
return $this->json(['success' => false, 'message' => 'Geen geheim gevonden']);
}
$isValid = $this->twofactorauth->verifyCode($this->user->two_factor_secret, $code);
if ($isValid) {
return $this->json(['success' => true, 'message' => 'Code geverifieerd']);
} else {
return $this->json(['success' => false, 'message' => 'Ongeldige code']);
}
}
/**
* Disable 2FA for the user
*/
public function disable()
{
$data = [
'two_factor_enabled' => 'F',
'two_factor_method' => null,
'two_factor_secret' => null
];
$this->User_model->update_record($data, $this->user->id);
// Clean up any existing OTP codes
$this->load->model('beheer/UserOtp_model');
$this->UserOtp_model->deleteUserOTPs($this->user->id);
return $this->json([
'success' => true,
'message' => 'Tweefactorauthenticatie uitgeschakeld'
]);
}
/**
* Change 2FA method
* POST parameters:
* - method: 'email' or 'authenticator'
*/
public function changeMethod()
{
$method = $this->clean_input('method');
if (!in_array($method, ['email', 'authenticator'])) {
return $this->json(['success' => false, 'message' => 'Ongeldige methode']);
}
// If user is not enabled for 2FA, they should use enable endpoint
if ($this->user->two_factor_enabled !== 'T') {
return $this->json(['success' => false, 'message' => '2FA is niet ingeschakeld']);
}
$secret = null;
$qrCodeUrl = null;
// If switching to authenticator, generate new secret
if ($method === 'authenticator') {
$secret = $this->twofactorauth->generateSecret();
$qrCodeUrl = $this->twofactorauth->getQRCodeUrl(
$secret,
$this->user->username,
'DBOIS'
);
}
$data = [
'two_factor_method' => $method,
'two_factor_secret' => $secret
];
$this->User_model->update_record($data, $this->user->id);
$response = [
'success' => true,
'message' => '2FA methode gewijzigd',
'method' => $method
];
if ($method === 'authenticator') {
$response['secret'] = $secret;
$response['qrCodeUrl'] = $qrCodeUrl;
}
return $this->json($response);
}
/**
* Get current 2FA status
*/
public function status()
{
$response = [
'enabled' => $this->user->two_factor_enabled === 'T',
'method' => $this->user->two_factor_method
];
return $this->json($response);
}
}