From 71b0448004094c5ed4c8d0058a944ec824588ede Mon Sep 17 00:00:00 2001 From: Yasen Pramatarov Date: Tue, 8 Apr 2025 10:30:18 +0300 Subject: [PATCH] Adds 2fa to the login page --- app/pages/login.php | 166 ++++++++++++++++++++++++++++++-------------- 1 file changed, 115 insertions(+), 51 deletions(-) diff --git a/app/pages/login.php b/app/pages/login.php index fdc6f4d..082f40a 100644 --- a/app/pages/login.php +++ b/app/pages/login.php @@ -4,20 +4,20 @@ * User login * * This page ("login") handles user login, session management, cookie handling, and error logging. - * Supports "remember me" functionality to extend session duration. + * Supports "remember me" functionality to extend session duration and two-factor authentication. * * Actions Performed: - * - Validates login credentials. - * - Manages session and cookies based on "remember me" option. - * - Logs successful and failed login attempts. - * - Displays login form and optional custom messages. + * - Validates login credentials + * - Handles two-factor authentication if enabled + * - Manages session and cookies based on "remember me" option + * - Logs successful and failed login attempts + * - Displays login form and optional custom messages */ // clear the global error var before login unset($error); try { - // connect to database $dbWeb = connectDB($config)['db']; @@ -28,7 +28,44 @@ try { // Get user IP $user_IP = getUserIP(); - if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) { + $action = $_REQUEST['action'] ?? ''; + + if ($action === 'verify' && isset($_SESSION['2fa_pending_user_id'])) { + // Handle 2FA verification + $code = $_POST['code'] ?? ''; + $userId = $_SESSION['2fa_pending_user_id']; + $username = $_SESSION['2fa_pending_username']; + $rememberMe = isset($_SESSION['2fa_pending_remember']); + + require_once '../app/classes/twoFactorAuth.php'; + $twoFactorAuth = new TwoFactorAuthentication($dbWeb); + + if ($twoFactorAuth->verify($userId, $code)) { + // Complete login + handleSuccessfulLogin($userId, $username, $rememberMe, $config, $logObject, $user_IP); + + // Clean up 2FA session data + unset($_SESSION['2fa_pending_user_id']); + unset($_SESSION['2fa_pending_username']); + unset($_SESSION['2fa_pending_remember']); + + exit(); + } + + // If we get here (and we have code submitted), verification failed + if (!empty($code)) { + Feedback::flash('ERROR', 'DEFAULT', 'Invalid verification code'); + } + + // Get any new feedback messages + include '../app/helpers/feedback.php'; + + // Load the 2FA verification template + include '../app/templates/credentials-2fa-verify.php'; + exit(); + } + + if ( $_SERVER['REQUEST_METHOD'] == 'POST' && $action !== 'verify' ) { try { // Validate form data $security = SecurityHelper::getInstance(); @@ -44,7 +81,7 @@ try { 'password' => [ 'type' => 'string', 'required' => true, - 'min' => 2 + 'min' => 5 ] ]; @@ -72,51 +109,35 @@ try { $rateLimiter->attempt($username, $user_IP); } - // login successful - if ( $userObject->login($username, $password) ) { - // if remember_me is checked, max out the session - if (isset($formData['remember_me'])) { - // 30*24*60*60 = 30 days - $cookie_lifetime = 30 * 24 * 60 * 60; - $setcookie_lifetime = time() + 30 * 24 * 60 * 60; - } else { - // 0 - session end on browser close - $cookie_lifetime = 0; - $setcookie_lifetime = 0; + // Attempt login + $loginResult = $userObject->login($username, $password); + + if (is_array($loginResult)) { + switch ($loginResult['status']) { + case 'requires_2fa': + // Store pending 2FA info + $_SESSION['2fa_pending_user_id'] = $loginResult['user_id']; + $_SESSION['2fa_pending_username'] = $loginResult['username']; + if (isset($formData['remember_me'])) { + $_SESSION['2fa_pending_remember'] = true; + } + + // Redirect to 2FA verification + header('Location: ?page=login&action=verify'); + exit(); + + case 'success': + // Complete login + handleSuccessfulLogin($loginResult['user_id'], $loginResult['username'], + isset($formData['remember_me']), $config, $logObject, $user_IP); + exit(); + + default: + throw new Exception($loginResult['message'] ?? 'Login failed'); } - - // Regenerate session ID to prevent session fixation - session_regenerate_id(true); - - // set session lifetime and cookies - setcookie('username', $username, [ - 'expires' => $setcookie_lifetime, - 'path' => $config['folder'], - 'domain' => $config['domain'], - 'secure' => isset($_SERVER['HTTPS']), - 'httponly' => true, - 'samesite' => 'Strict' - ]); - - // Set session variables - $_SESSION['USER_ID'] = $userObject->getUserId($username)[0]['id']; - $_SESSION['USERNAME'] = $username; - $_SESSION['LAST_ACTIVITY'] = time(); - if (isset($formData['remember_me'])) { - $_SESSION['REMEMBER_ME'] = true; - } - - // Log successful login - $user_id = $userObject->getUserId($username)[0]['id']; - $logObject->insertLog($user_id, "Login: User \"$username\" logged in. IP: $user_IP", 'user'); - - // Set success message and redirect - Feedback::flash('LOGIN', 'LOGIN_SUCCESS'); - header('Location: ' . htmlspecialchars($app_root)); - exit(); - } else { - throw new Exception(Feedback::get('LOGIN', 'LOGIN_FAILED')['message']); } + + throw new Exception(Feedback::get('LOGIN', 'LOGIN_FAILED')['message']); } catch (Exception $e) { // Log the failed attempt Feedback::flash('ERROR', 'DEFAULT', $e->getMessage()); @@ -140,3 +161,46 @@ include '../app/helpers/feedback.php'; // Load the template include '../app/templates/form-login.php'; + +/** + * Handle successful login by setting up session and cookies + */ +function handleSuccessfulLogin($userId, $username, $rememberMe, $config, $logObject, $userIP) { + if ($rememberMe) { + // 30*24*60*60 = 30 days + $cookie_lifetime = 30 * 24 * 60 * 60; + $setcookie_lifetime = time() + 30 * 24 * 60 * 60; + } else { + // 0 - session end on browser close + $cookie_lifetime = 0; + $setcookie_lifetime = 0; + } + + // Regenerate session ID to prevent session fixation + session_regenerate_id(true); + + // set session lifetime and cookies + setcookie('username', $username, [ + 'expires' => $setcookie_lifetime, + 'path' => $config['folder'], + 'domain' => $config['domain'], + 'secure' => isset($_SERVER['HTTPS']), + 'httponly' => true, + 'samesite' => 'Strict' + ]); + + // Set session variables + $_SESSION['user_id'] = $userId; + $_SESSION['USERNAME'] = $username; + $_SESSION['LAST_ACTIVITY'] = time(); + if ($rememberMe) { + $_SESSION['REMEMBER_ME'] = true; + } + + // Log successful login + $logObject->insertLog($userId, "Login: User \"$username\" logged in. IP: $userIP", 'user'); + + // Set success message and redirect + Feedback::flash('LOGIN', 'LOGIN_SUCCESS'); + header('Location: ' . htmlspecialchars($app_root)); +}