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.
2025-10-16 17:25:06 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Controllers;
|
|
|
|
|
|
|
|
|
|
use Core\Controller;
|
|
|
|
|
use Core\Auth;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Services\TwoFactorService;
|
|
|
|
|
use App\Services\Logger;
|
|
|
|
|
|
|
|
|
|
class TwoFactorController extends Controller
|
|
|
|
|
{
|
|
|
|
|
private User $userModel;
|
|
|
|
|
private TwoFactorService $twoFactorService;
|
|
|
|
|
private Logger $logger;
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
$this->userModel = new User();
|
|
|
|
|
$this->twoFactorService = new TwoFactorService();
|
|
|
|
|
$this->logger = new Logger('2fa');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show 2FA setup page
|
|
|
|
|
*/
|
|
|
|
|
public function setup()
|
|
|
|
|
{
|
|
|
|
|
Auth::require();
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
|
|
|
|
|
if (!$user) {
|
|
|
|
|
$_SESSION['error'] = 'User not found';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if 2FA is disabled by admin
|
|
|
|
|
$policy = $this->twoFactorService->getTwoFactorPolicy();
|
|
|
|
|
if ($policy === 'disabled') {
|
|
|
|
|
$_SESSION['error'] = 'Two-factor authentication is disabled';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if email is verified
|
|
|
|
|
if (!$user['email_verified']) {
|
|
|
|
|
$_SESSION['error'] = 'You must verify your email address before enabling 2FA';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if already enabled
|
|
|
|
|
if ($user['two_factor_enabled']) {
|
|
|
|
|
$_SESSION['info'] = 'Two-factor authentication is already enabled';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate or reuse existing secret for this setup session
|
|
|
|
|
if (!isset($_SESSION['2fa_setup_secret'])) {
|
|
|
|
|
$_SESSION['2fa_setup_secret'] = $this->twoFactorService->generateSecret();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$secret = $_SESSION['2fa_setup_secret'];
|
|
|
|
|
$qrCodeUrl = $this->twoFactorService->generateQrCodeDataUri($user['email'], $secret);
|
|
|
|
|
|
|
|
|
|
$this->view('2fa/setup', [
|
|
|
|
|
'user' => $user,
|
|
|
|
|
'secret' => $secret,
|
|
|
|
|
'qrCodeUrl' => $qrCodeUrl,
|
|
|
|
|
'title' => 'Setup Two-Factor Authentication'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify 2FA setup and enable it
|
|
|
|
|
*/
|
|
|
|
|
public function verifySetup()
|
|
|
|
|
{
|
|
|
|
|
Auth::require();
|
|
|
|
|
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/2fa/setup');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/2fa/setup');
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
$verificationCode = $_POST['verification_code'] ?? '';
|
|
|
|
|
|
|
|
|
|
if (!$user || !$user['email_verified'] || $user['two_factor_enabled']) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid request';
|
|
|
|
|
$this->redirect('/2fa/setup');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the secret from session (should exist from setup page)
|
|
|
|
|
if (!isset($_SESSION['2fa_setup_secret'])) {
|
|
|
|
|
$_SESSION['error'] = 'Setup session expired. Please start over.';
|
|
|
|
|
$this->redirect('/2fa/setup');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$secret = $_SESSION['2fa_setup_secret'];
|
|
|
|
|
|
|
|
|
|
if (empty($verificationCode)) {
|
|
|
|
|
$_SESSION['error'] = 'Please enter the verification code';
|
|
|
|
|
$this->redirect('/2fa/setup');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the code
|
|
|
|
|
if (!$this->twoFactorService->verifyTotpCode($secret, $verificationCode)) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid verification code. Please try again.';
|
|
|
|
|
$this->redirect('/2fa/setup');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate backup codes
|
|
|
|
|
$backupCodes = $this->twoFactorService->generateBackupCodes();
|
|
|
|
|
|
|
|
|
|
// Enable 2FA
|
|
|
|
|
if ($this->twoFactorService->enableTwoFactor($userId, $secret, $backupCodes)) {
|
|
|
|
|
$_SESSION['success'] = 'Two-factor authentication enabled successfully!';
|
|
|
|
|
|
|
|
|
|
// Clear the setup secret from session
|
|
|
|
|
unset($_SESSION['2fa_setup_secret']);
|
|
|
|
|
|
|
|
|
|
// Store backup codes in session for display
|
|
|
|
|
$_SESSION['backup_codes'] = $backupCodes;
|
|
|
|
|
|
|
|
|
|
$this->redirect('/2fa/backup-codes');
|
|
|
|
|
} else {
|
|
|
|
|
$_SESSION['error'] = 'Failed to enable two-factor authentication';
|
|
|
|
|
$this->redirect('/2fa/setup');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cancel 2FA setup (clear session secret)
|
|
|
|
|
*/
|
|
|
|
|
public function cancelSetup()
|
|
|
|
|
{
|
|
|
|
|
Auth::require();
|
|
|
|
|
|
|
|
|
|
// Clear the setup secret from session
|
|
|
|
|
unset($_SESSION['2fa_setup_secret']);
|
|
|
|
|
|
|
|
|
|
$_SESSION['info'] = '2FA setup cancelled';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show backup codes page
|
|
|
|
|
*/
|
|
|
|
|
public function backupCodes()
|
|
|
|
|
{
|
|
|
|
|
Auth::require();
|
|
|
|
|
|
|
|
|
|
$backupCodes = $_SESSION['backup_codes'] ?? null;
|
|
|
|
|
|
|
|
|
|
if (!$backupCodes) {
|
|
|
|
|
$_SESSION['error'] = 'No backup codes found';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear backup codes from session after display
|
|
|
|
|
unset($_SESSION['backup_codes']);
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
|
|
|
|
|
$this->view('2fa/backup-codes', [
|
|
|
|
|
'user' => $user,
|
|
|
|
|
'backupCodes' => $backupCodes,
|
|
|
|
|
'title' => 'Backup Codes'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show 2FA verification page (during login)
|
|
|
|
|
*/
|
|
|
|
|
public function showVerify()
|
|
|
|
|
{
|
|
|
|
|
// Check if user is in 2FA verification state
|
|
|
|
|
if (!isset($_SESSION['2fa_required']) || !$_SESSION['2fa_required']) {
|
|
|
|
|
$this->redirect('/');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
|
|
|
|
|
if (!$user || !$user['two_factor_enabled']) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid request';
|
|
|
|
|
$this->redirect('/login');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->view('2fa/verify', [
|
|
|
|
|
'user' => $user,
|
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
|
|
|
'canSendEmailCode' => !empty($user['email_verified']),
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
'title' => 'Two-Factor Authentication'
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process 2FA verification
|
|
|
|
|
*/
|
|
|
|
|
public function verify()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/2fa/verify');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/2fa/verify');
|
|
|
|
|
|
|
|
|
|
// Check if user is in 2FA verification state
|
|
|
|
|
if (!isset($_SESSION['2fa_required']) || !$_SESSION['2fa_required']) {
|
|
|
|
|
$this->redirect('/');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '';
|
|
|
|
|
|
|
|
|
|
if (!$user || !$user['two_factor_enabled']) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid request';
|
|
|
|
|
$this->redirect('/login');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check rate limiting
|
|
|
|
|
if (!$this->twoFactorService->checkRateLimit($ipAddress, $userId)) {
|
|
|
|
|
$_SESSION['error'] = 'Too many failed attempts. Please try again later.';
|
|
|
|
|
$this->redirect('/2fa/verify');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$verificationCode = trim($_POST['verification_code'] ?? '');
|
|
|
|
|
$verified = false;
|
|
|
|
|
|
|
|
|
|
if (!empty($verificationCode)) {
|
|
|
|
|
// Try TOTP code first (6 digits)
|
|
|
|
|
if (strlen($verificationCode) === 6 && is_numeric($verificationCode)) {
|
|
|
|
|
if ($this->twoFactorService->verifyTotpCode($user['two_factor_secret'], $verificationCode)) {
|
|
|
|
|
$verified = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try email code if TOTP failed (6 digits)
|
|
|
|
|
if (!$verified && strlen($verificationCode) === 6 && is_numeric($verificationCode)) {
|
|
|
|
|
if ($this->twoFactorService->verifyEmailCode($userId, $verificationCode)) {
|
|
|
|
|
$verified = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try backup code (8 characters)
|
|
|
|
|
if (!$verified && strlen($verificationCode) === 8) {
|
|
|
|
|
if ($this->twoFactorService->verifyBackupCode($userId, $verificationCode)) {
|
|
|
|
|
$verified = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record attempt
|
|
|
|
|
$this->twoFactorService->recordAttempt($userId, $ipAddress, $verified);
|
|
|
|
|
|
|
|
|
|
if ($verified) {
|
Improve security, validation, and isolation checks
Add multiple security and validation improvements across the app:
- Prevent session fixation: regenerate session ID on login and after successful 2FA; tighten session cookie params (Secure, HttpOnly, SameSite=Lax).
- Harden installer: add CSRF checks for install/update flows and use PDO::quote when injecting admin credentials into SQL migration to avoid injection; add csrf_field() to installer templates.
- Template hardening: add safe_url and safe_mailto Twig filters, escape tag names for JS, and add rel="noopener noreferrer" to external links to mitigate XSS/opener risks.
- Domain controller: validate referrer to avoid open redirects, enforce user isolation mode when finding/deleting/updating domains and when assigning notification groups (ensures users only affect their own resources).
- Notification groups: verify channel belongs to group before deleting or toggling to prevent unauthorized access.
- ErrorLog: whitelist allowed sort columns to avoid arbitrary column injection in ORDER BY.
- Routes: move the debug whois route to protected/admin area.
These changes collectively reduce attack surface (XSS, open redirect, session fixation, SQL injection) and enforce proper resource isolation and input validation.
2026-03-11 00:03:54 +02:00
|
|
|
// Regenerate session ID to prevent session fixation
|
|
|
|
|
session_regenerate_id(true);
|
|
|
|
|
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
// Clear 2FA requirement and complete login
|
2026-03-10 23:04:20 +02:00
|
|
|
$pendingRemember = !empty($_SESSION['pending_remember']);
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
unset($_SESSION['2fa_required']);
|
2026-03-10 23:04:20 +02:00
|
|
|
unset($_SESSION['pending_remember']);
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
|
|
|
|
|
// Determine which method was used
|
|
|
|
|
$method = 'unknown';
|
|
|
|
|
if (strlen($verificationCode) === 6 && is_numeric($verificationCode)) {
|
|
|
|
|
// Try to determine if it was TOTP or email by checking which one succeeded
|
|
|
|
|
if ($this->twoFactorService->verifyTotpCode($user['two_factor_secret'], $verificationCode)) {
|
|
|
|
|
$method = 'totp';
|
|
|
|
|
} else {
|
|
|
|
|
$method = 'email';
|
|
|
|
|
}
|
|
|
|
|
} elseif (strlen($verificationCode) === 8) {
|
|
|
|
|
$method = 'backup';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->logger->info('2FA verification successful', [
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'method' => $method
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-10 23:04:20 +02:00
|
|
|
// Handle remember me (carried over from login form)
|
|
|
|
|
if ($pendingRemember) {
|
|
|
|
|
$authController = new \App\Controllers\AuthController();
|
|
|
|
|
$authController->createRememberTokenPublic($userId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 22:58:59 +02:00
|
|
|
// Update last login timestamp
|
|
|
|
|
$this->userModel->updateLastLogin($userId);
|
|
|
|
|
|
|
|
|
|
// Create login notification
|
|
|
|
|
try {
|
|
|
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
|
|
|
|
|
$notificationService = new \App\Services\NotificationService();
|
|
|
|
|
$notificationService->notifyNewLogin($userId, "2FA ($method)", $ipAddress, $userAgent);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Don't block login if notification fails
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-15 17:48:55 +02:00
|
|
|
$_SESSION['success'] = 'Login successful! Welcome back, ' . htmlspecialchars($user['full_name']) . '.';
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
$this->redirect('/');
|
|
|
|
|
} else {
|
2026-02-08 22:58:59 +02:00
|
|
|
// Notify user about failed 2FA attempt
|
|
|
|
|
try {
|
|
|
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
|
|
|
|
|
$notificationService = new \App\Services\NotificationService();
|
|
|
|
|
$notificationService->notifyFailedLogin($userId, 'Failed 2FA verification', $ipAddress, $userAgent, $user['username']);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
// Don't block response if notification fails
|
|
|
|
|
}
|
|
|
|
|
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
$_SESSION['error'] = 'Invalid verification code. Please try again.';
|
|
|
|
|
$this->redirect('/2fa/verify');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Send email verification code
|
|
|
|
|
*/
|
|
|
|
|
public function sendEmailCode()
|
|
|
|
|
{
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/2fa/verify');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Improve security, validation, and isolation checks
Add multiple security and validation improvements across the app:
- Prevent session fixation: regenerate session ID on login and after successful 2FA; tighten session cookie params (Secure, HttpOnly, SameSite=Lax).
- Harden installer: add CSRF checks for install/update flows and use PDO::quote when injecting admin credentials into SQL migration to avoid injection; add csrf_field() to installer templates.
- Template hardening: add safe_url and safe_mailto Twig filters, escape tag names for JS, and add rel="noopener noreferrer" to external links to mitigate XSS/opener risks.
- Domain controller: validate referrer to avoid open redirects, enforce user isolation mode when finding/deleting/updating domains and when assigning notification groups (ensures users only affect their own resources).
- Notification groups: verify channel belongs to group before deleting or toggling to prevent unauthorized access.
- ErrorLog: whitelist allowed sort columns to avoid arbitrary column injection in ORDER BY.
- Routes: move the debug whois route to protected/admin area.
These changes collectively reduce attack surface (XSS, open redirect, session fixation, SQL injection) and enforce proper resource isolation and input validation.
2026-03-11 00:03:54 +02:00
|
|
|
$this->verifyCsrf('/2fa/verify');
|
|
|
|
|
|
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.
2025-10-16 17:25:06 +03:00
|
|
|
try {
|
|
|
|
|
// Check if user is in 2FA verification state
|
|
|
|
|
if (!isset($_SESSION['2fa_required']) || !$_SESSION['2fa_required']) {
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'Invalid request']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
if (!$userId) {
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'User not authenticated']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
if (!$user) {
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'User not found']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$user['two_factor_enabled']) {
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'Two-factor authentication not enabled']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$user['email_verified']) {
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'Email not verified']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check rate limit
|
|
|
|
|
if (!$this->twoFactorService->checkRateLimit($_SERVER['REMOTE_ADDR'] ?? '', $userId)) {
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'Rate limit exceeded. Please try again later.']);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = $this->twoFactorService->generateEmailCode($userId);
|
|
|
|
|
$this->jsonResponse($result);
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->logger->error('Error sending 2FA email code', [
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
|
|
]);
|
|
|
|
|
$this->jsonResponse(['success' => false, 'error' => 'Failed to send email code']);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Disable 2FA
|
|
|
|
|
*/
|
|
|
|
|
public function disable()
|
|
|
|
|
{
|
|
|
|
|
Auth::require();
|
|
|
|
|
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/profile');
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
|
|
|
|
|
if (!$user || !$user['two_factor_enabled']) {
|
|
|
|
|
$_SESSION['error'] = 'Two-factor authentication is not enabled';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Require 2FA verification to disable 2FA
|
|
|
|
|
$verificationCode = trim($_POST['verification_code'] ?? '');
|
|
|
|
|
if (empty($verificationCode)) {
|
|
|
|
|
$_SESSION['error'] = 'Please enter your 2FA verification code to disable two-factor authentication';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the code using any available method
|
|
|
|
|
$verified = false;
|
|
|
|
|
|
|
|
|
|
// Try TOTP code first
|
|
|
|
|
if ($this->twoFactorService->verifyTotpCode($user['two_factor_secret'], $verificationCode)) {
|
|
|
|
|
$verified = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try email code if TOTP failed
|
|
|
|
|
if (!$verified && $user['email_verified']) {
|
|
|
|
|
if ($this->twoFactorService->verifyEmailCode($userId, $verificationCode)) {
|
|
|
|
|
$verified = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try backup code if other methods failed
|
|
|
|
|
if (!$verified) {
|
|
|
|
|
if ($this->twoFactorService->verifyBackupCode($userId, $verificationCode)) {
|
|
|
|
|
$verified = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$verified) {
|
|
|
|
|
$_SESSION['error'] = 'Invalid verification code. Please enter a valid 2FA code to disable two-factor authentication';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if 2FA is forced
|
|
|
|
|
if ($this->twoFactorService->isTwoFactorRequired($userId)) {
|
|
|
|
|
$_SESSION['error'] = 'Two-factor authentication is required and cannot be disabled';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->twoFactorService->disableTwoFactor($userId)) {
|
|
|
|
|
$_SESSION['success'] = 'Two-factor authentication has been disabled';
|
|
|
|
|
} else {
|
|
|
|
|
$_SESSION['error'] = 'Failed to disable two-factor authentication';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->redirect('/profile#twofactor');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Regenerate backup codes
|
|
|
|
|
*/
|
|
|
|
|
public function regenerateBackupCodes()
|
|
|
|
|
{
|
|
|
|
|
Auth::require();
|
|
|
|
|
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->verifyCsrf('/profile');
|
|
|
|
|
|
|
|
|
|
$userId = Auth::id();
|
|
|
|
|
$user = $this->userModel->find($userId);
|
|
|
|
|
|
|
|
|
|
if (!$user || !$user['two_factor_enabled']) {
|
|
|
|
|
$_SESSION['error'] = 'Two-factor authentication is not enabled';
|
|
|
|
|
$this->redirect('/profile');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate new backup codes
|
|
|
|
|
$backupCodes = $this->twoFactorService->generateBackupCodes();
|
|
|
|
|
|
|
|
|
|
// Update user with new backup codes
|
|
|
|
|
if ($this->userModel->update($userId, [
|
|
|
|
|
'two_factor_backup_codes' => json_encode($backupCodes)
|
|
|
|
|
])) {
|
|
|
|
|
$_SESSION['success'] = 'New backup codes generated successfully!';
|
|
|
|
|
|
|
|
|
|
// Store backup codes in session for display
|
|
|
|
|
$_SESSION['backup_codes'] = $backupCodes;
|
|
|
|
|
|
|
|
|
|
$this->redirect('/2fa/backup-codes');
|
|
|
|
|
} else {
|
|
|
|
|
$_SESSION['error'] = 'Failed to generate new backup codes';
|
|
|
|
|
$this->redirect('/profile#twofactor');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Send JSON response
|
|
|
|
|
*/
|
|
|
|
|
private function jsonResponse(array $data): void
|
|
|
|
|
{
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
echo json_encode($data);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|