Add two-factor authentication (2FA) support

Introduces two-factor authentication (2FA) with TOTP, backup codes, and email codes. Adds controllers, services, views, and migration for 2FA setup, verification, and management. Updates user and settings models, email helper, and relevant controllers to support 2FA policy enforcement, configuration, and user flows. Enhances security by allowing admins to require or disable 2FA, and provides backup code generation and management for account recovery.
This commit is contained in:
Hosteroid
2025-10-16 17:25:06 +03:00
parent 1edde3645c
commit 6e8fef9b79
18 changed files with 2072 additions and 24 deletions

View File

@@ -7,18 +7,21 @@ use Core\Auth;
use App\Models\User;
use App\Models\SessionManager;
use App\Models\RememberToken;
use App\Services\Logger;
class ProfileController extends Controller
{
private User $userModel;
private SessionManager $sessionModel;
private RememberToken $rememberTokenModel;
private Logger $logger;
public function __construct()
{
$this->userModel = new User();
$this->sessionModel = new SessionManager();
$this->rememberTokenModel = new RememberToken();
$this->logger = new Logger('profile');
}
/**
@@ -66,6 +69,7 @@ class ProfileController extends Controller
$this->view('profile/index', [
'user' => $user,
'sessions' => $formattedSessions,
'userModel' => $this->userModel,
'title' => 'My Profile'
]);
}
@@ -108,6 +112,10 @@ class ProfileController extends Controller
return;
}
// Get current user data to check if email changed
$currentUser = $this->userModel->find($userId);
$emailChanged = $currentUser['email'] !== $email;
// Check if email is already taken by another user
$existingUsers = $this->userModel->where('email', $email);
foreach ($existingUsers as $existingUser) {
@@ -118,18 +126,46 @@ class ProfileController extends Controller
}
}
// Update user
$this->userModel->update($userId, [
// Prepare update data
$updateData = [
'full_name' => $fullName,
'email' => $email,
]);
];
// If email changed, mark as unverified and send verification email
if ($emailChanged) {
$updateData['email_verified'] = null;
// Generate new verification token
$verificationToken = bin2hex(random_bytes(32));
$updateData['email_verification_token'] = $verificationToken;
}
// Update user
$this->userModel->update($userId, $updateData);
// Update session
$_SESSION['full_name'] = $fullName;
$_SESSION['email'] = $email;
$_SESSION['full_name'] = $fullName;
$_SESSION['email'] = $email;
$_SESSION['success'] = 'Profile updated successfully';
$this->redirect('/profile');
// Send verification email if email changed
if ($emailChanged) {
try {
\App\Helpers\EmailHelper::sendVerificationEmail($email, $fullName, $verificationToken);
$_SESSION['success'] = 'Profile updated successfully. Please check your new email address for a verification link.';
} catch (\Exception $e) {
$_SESSION['success'] = 'Profile updated successfully, but verification email could not be sent. Please try resending verification.';
$this->logger->error("Failed to send verification email after profile update", [
'user_id' => $userId,
'email' => $email,
'error' => $e->getMessage()
]);
}
} else {
$_SESSION['success'] = 'Profile updated successfully';
}
$this->redirect('/profile');
}
/**
@@ -226,11 +262,29 @@ class ProfileController extends Controller
return;
}
// Use AuthController logic
$authController = new AuthController();
$_SESSION['pending_verification_email'] = $user['email'];
$_SESSION['success'] = 'Verification email sent! Please check your inbox.';
try {
// Generate new verification token
$token = bin2hex(random_bytes(32));
// Debug logging
$this->logger->info("Generated new verification token for user {$userId}: " . substr($token, 0, 10) . "...");
// Update verification token in database
$this->userModel->updateEmailVerificationToken($userId, $token);
// Send verification email
\App\Helpers\EmailHelper::sendVerificationEmail($user['email'], $user['full_name'], $token);
$_SESSION['success'] = 'Verification email sent! Please check your inbox.';
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to resend verification email. Please try again.';
$this->logger->error("Failed to resend verification email", [
'user_id' => $userId,
'email' => $user['email'],
'error' => $e->getMessage()
]);
}
$this->redirect('/profile');
}