diff --git a/login.php b/login.php index a1d6a2bb..618e66e0 100644 --- a/login.php +++ b/login.php @@ -2,13 +2,10 @@ // Unified login (Agent + Client) using one email & password -// Enforce a Content Security Policy for security against cross-site scripting header("Content-Security-Policy: default-src 'self'"); -// Check if the config.php file exists if (!file_exists('config.php')) { - // Redirect to the setup page if config.php doesn't exist - header("Location: /setup"); // Must use header as functions aren't included yet + header("Location: /setup"); exit(); } @@ -16,12 +13,9 @@ require_once "config.php"; require_once "functions.php"; require_once "plugins/totp/totp.php"; -// Sessions & cookies if (session_status() === PHP_SESSION_NONE) { - // HTTP-Only cookies ini_set("session.cookie_httponly", true); - // Tell client to only send cookie(s) over HTTPS if ($config_https_only || !isset($config_https_only)) { ini_set("session.cookie_secure", true); } @@ -29,28 +23,25 @@ if (session_status() === PHP_SESSION_NONE) { session_start(); } -// Check if setup mode is enabled or the variable is missing if (!isset($config_enable_setup) || $config_enable_setup == 1) { - // Redirect to the setup page header("Location: /setup"); exit(); } -// Check if the application is configured for HTTPS-only access -if ($config_https_only && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') && (!isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || $_SERVER['HTTP_X_FORWARDED_PROTO'] !== 'https')) { +if ( + $config_https_only + && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') + && (!isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || $_SERVER['HTTP_X_FORWARDED_PROTO'] !== 'https') +) { echo "Login is restricted as ITFlow defaults to HTTPS-only for enhanced security. To login using HTTP, modify the config.php file by setting config_https_only to false. However, this is strongly discouraged, especially when accessing from potentially unsafe networks like the internet."; exit; } -// Set Timezone after session_start require_once "includes/inc_set_timezone.php"; -// IP & User Agent for logging $session_ip = sanitizeInput(getIP()); $session_user_agent = sanitizeInput($_SERVER['HTTP_USER_AGENT'] ?? ''); -// Block brute force password attacks - check recent failed login attempts for this IP -// Block access if more than 15 failed login attempts have happened in the last 10 minutes $row = mysqli_fetch_assoc(mysqli_query( $mysqli, "SELECT COUNT(log_id) AS failed_login_count @@ -63,15 +54,12 @@ $row = mysqli_fetch_assoc(mysqli_query( $failed_login_count = intval($row['failed_login_count']); if ($failed_login_count >= 15) { - logAction("Login", "Blocked", "$session_ip was blocked access to login due to IP lockout"); - - // Inform user & quit processing page header("HTTP/1.1 429 Too Many Requests"); exit("

$config_app_name

Your IP address has been blocked due to repeated failed login attempts. Please try again later.

This action has been logged."); } -// Query Settings for company +// Settings $sql_settings = mysqli_query($mysqli, " SELECT settings.*, companies.company_name, companies.company_logo FROM settings @@ -80,13 +68,11 @@ $sql_settings = mysqli_query($mysqli, " "); $row = mysqli_fetch_array($sql_settings); -// Company info $company_name = $row['company_name']; $company_logo = $row['company_logo']; $config_start_page = nullable_htmlentities($row['config_start_page']); $config_login_message = nullable_htmlentities($row['config_login_message']); -// Mail $config_smtp_host = $row['config_smtp_host']; $config_smtp_port = intval($row['config_smtp_port']); $config_smtp_encryption = $row['config_smtp_encryption']; @@ -95,49 +81,96 @@ $config_smtp_password = $row['config_smtp_password']; $config_mail_from_email = sanitizeInput($row['config_mail_from_email']); $config_mail_from_name = sanitizeInput($row['config_mail_from_name']); -// Client Portal Enabled $config_client_portal_enable = intval($row['config_client_portal_enable']); $config_login_remember_me_expire = intval($row['config_login_remember_me_expire']); -// Login key (if setup) $config_login_key_required = $row['config_login_key_required']; -$config_login_key_secret = $row['config_login_key_secret']; +$config_login_key_secret = $row['config_login_key_secret']; -// Azure / Entra for client $azure_client_id = $row['config_azure_client_id'] ?? null; -$response = null; -$token_field = null; -$show_role_choice = false; -$email = ''; -$password = ''; +$response = null; +$token_field = null; +$show_role_choice = false; -// Handle POST login request (normal login or role choice) -if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_POST['role_choice']))) { +$email = ''; +$password = ''; // only ever used in the initial POST request - $email = sanitizeInput($_POST['email'] ?? ''); - $password = $_POST['password'] ?? ''; - $role_choice = $_POST['role_choice'] ?? null; // 'agent' or 'client' +// Helpers +function pendingExpired($sess, $ttl_seconds = 120) { + return !$sess || empty($sess['created']) || (time() - intval($sess['created']) > $ttl_seconds); +} - // Basic validation - if (empty($email) || empty($password) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { - header("HTTP/1.1 401 Unauthorized"); - $response = " -
- Incorrect username or password. - -
"; - } else { +// POST handling +if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_POST['role_choice']) || isset($_POST['mfa_login']))) { - /* - * Unified lookup: - * - user_type = 1 → Agent - * - user_type = 2 → Client (must not be archived, client not archived) - * We fetch all possible matches for this email, then verify password per row. - * If both an agent and a client match with the same password: - * - First, show choice buttons (Agent / Client). - * - When user clicks a choice, we honor role_choice. - */ + $role_choice = $_POST['role_choice'] ?? null; + + $is_login_step = isset($_POST['login']); + $is_role_step = isset($_POST['role_choice']) && !$is_login_step && !isset($_POST['mfa_login']); + $is_mfa_step = isset($_POST['mfa_login']); + + // ----------------------------------- + // STEP 2: ROLE CHOICE (no email/pass) + // ----------------------------------- + if ($is_role_step) { + + $posted_token = $_POST['pending_login_token'] ?? ''; + $sess = $_SESSION['pending_dual_login'] ?? null; + + if (pendingExpired($sess) || empty($posted_token) || empty($sess['token']) || !hash_equals($sess['token'], $posted_token)) { + unset($_SESSION['pending_dual_login']); + header("HTTP/1.1 401 Unauthorized"); + $response = " +
+ Your login session expired. Please sign in again. +
"; + } else { + $email = sanitizeInput($sess['email'] ?? ''); + } + } + + // ----------------------------------- + // STEP 3: MFA SUBMIT (no email/pass) + // ----------------------------------- + if ($is_mfa_step && empty($response)) { + + $posted_token = $_POST['pending_mfa_token'] ?? ''; + $sess = $_SESSION['pending_mfa_login'] ?? null; + + if (pendingExpired($sess) || empty($posted_token) || empty($sess['token']) || !hash_equals($sess['token'], $posted_token)) { + unset($_SESSION['pending_mfa_login']); + header("HTTP/1.1 401 Unauthorized"); + $response = " +
+ Your MFA session expired. Please sign in again. +
"; + } else { + $email = sanitizeInput($sess['email'] ?? ''); + $role_choice = 'agent'; + } + } + + // ----------------------------------- + // STEP 1: INITIAL CREDENTIALS + // ----------------------------------- + if ($is_login_step && empty($response)) { + $email = sanitizeInput($_POST['email'] ?? ''); + $password = $_POST['password'] ?? ''; + + if (empty($email) || empty($password) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + header("HTTP/1.1 401 Unauthorized"); + $response = " +
+ Incorrect username or password. +
"; + } + } + + // Continue only if no response error + if (empty($response)) { + + // Query all possible matches for that email $sql = mysqli_query($mysqli, " SELECT users.*, user_settings.*, @@ -159,307 +192,372 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_ $agentRow = null; $clientRow = null; + // Step 1: verify password. Step 2/3: use stored allowed ids. + $allowed_agent_id = null; + $allowed_client_id = null; + + if ($is_role_step) { + $sess = $_SESSION['pending_dual_login'] ?? null; + $allowed_agent_id = isset($sess['agent_user_id']) ? intval($sess['agent_user_id']) : null; + $allowed_client_id = isset($sess['client_user_id']) ? intval($sess['client_user_id']) : null; + } + + if ($is_mfa_step) { + $sess = $_SESSION['pending_mfa_login'] ?? null; + $allowed_agent_id = isset($sess['agent_user_id']) ? intval($sess['agent_user_id']) : null; + } + while ($r = mysqli_fetch_assoc($sql)) { - if (!password_verify($password, $r['user_password'])) { - continue; + + $ut = intval($r['user_type']); + + if ($is_login_step) { + // Only Step 1 checks password + if (!password_verify($password, $r['user_password'])) { + continue; + } + } else { + // Step 2/3: restrict to ids we previously verified + if ($ut === 1 && $allowed_agent_id !== null && intval($r['user_id']) !== $allowed_agent_id) { + continue; + } + if ($ut === 2 && $allowed_client_id !== null && intval($r['user_id']) !== $allowed_client_id) { + continue; + } } - if (intval($r['user_type']) === 1 && $agentRow === null) { + + if ($ut === 1 && $agentRow === null) { $agentRow = $r; } - if (intval($r['user_type']) === 2 && $clientRow === null) { + if ($ut === 2 && $clientRow === null) { $clientRow = $r; } } - $selectedRow = null; - $selectedType = null; // 1 = agent, 2 = client - if ($agentRow === null && $clientRow === null) { - - // No matching user/password combo header("HTTP/1.1 401 Unauthorized"); logAction("Login", "Failed", "Failed login attempt using $email"); - $response = "
Incorrect username or password. -
"; - - } elseif ($agentRow !== null && $clientRow !== null) { - - // Both agent and client accounts share same email + password - if ($role_choice === 'agent') { - $selectedRow = $agentRow; - $selectedType = 1; - } elseif ($role_choice === 'client') { - $selectedRow = $clientRow; - $selectedType = 2; - } else { - // First time we realise this is a dual-role account: ask user to pick - $show_role_choice = true; - $response = " -
- This login can be used as either an Agent account or a Client Portal account. - Please choose how you want to continue. - -
"; - } - } else { - // Only one valid row (agent OR client) - if ($agentRow !== null) { - $selectedRow = $agentRow; - $selectedType = 1; - } else { - $selectedRow = $clientRow; - $selectedType = 2; - } - } - // If we have a specific user selected, proceed with actual login - if ($selectedRow !== null && $selectedType !== null) { + $selectedRow = null; + $selectedType = null; // 1 agent, 2 client - $user_id = intval($selectedRow['user_id']); - $user_email = sanitizeInput($selectedRow['user_email']); - $session_user_id = $user_id; // to pass the user_id to logAction function + // Dual role + if ($agentRow !== null && $clientRow !== null) { - // ========================= - // AGENT LOGIN FLOW - // ========================= - if ($selectedType === 1) { - // Login key verification - // If no/incorrect 'key' is supplied, send to client portal instead - if ($config_login_key_required) { - if (!isset($_GET['key']) || $_GET['key'] !== $config_login_key_secret) { - redirect(); + if ($role_choice === 'agent') { + $selectedRow = $agentRow; + $selectedType = 1; + } elseif ($role_choice === 'client') { + $selectedRow = $clientRow; + $selectedType = 2; + } else { + // Show role choice screen + $show_role_choice = true; + + // If this is the first time (Step 1), we need to stash allowed ids and (optional) decrypted agent encryption key + // WITHOUT storing password. + if ($is_login_step) { + + $pending_token = bin2hex(random_bytes(32)); + + // If agent has user-specific encryption ciphertext, decrypt it NOW while password is present. + $agent_master_key = null; + $agent_cipher = $agentRow['user_specific_encryption_ciphertext'] ?? null; + if (!empty($agent_cipher)) { + $agent_master_key = decryptUserSpecificKey($agent_cipher, $password); + } + + $_SESSION['pending_dual_login'] = [ + 'email' => $email, + 'agent_user_id' => intval($agentRow['user_id']), + 'client_user_id' => intval($clientRow['user_id']), + 'agent_master_key' => $agent_master_key, // may be null + 'token' => $pending_token, + 'created' => time() + ]; } + + $response = " +
+ This login can be used as either an Agent account or a Client Portal account. + Please choose how you want to continue. +
"; } - $user_name = sanitizeInput($selectedRow['user_name']); - $token = sanitizeInput($selectedRow['user_token']); - $force_mfa = intval($selectedRow['user_config_force_mfa']); - $user_role_id = intval($selectedRow['user_role_id']); - $user_encryption_ciphertext = $selectedRow['user_specific_encryption_ciphertext']; - $user_extension_key = $selectedRow['user_extension_key']; - - $current_code = 0; - if (isset($_POST['current_code'])) { - $current_code = intval($_POST['current_code']); + } else { + // Single role + if ($agentRow !== null) { + $selectedRow = $agentRow; + $selectedType = 1; + } else { + $selectedRow = $clientRow; + $selectedType = 2; } + } - $mfa_is_complete = false; - $extended_log = ''; + // Proceed if selected + if ($selectedRow !== null && $selectedType !== null) { - if (empty($token)) { - // MFA is not configured - $mfa_is_complete = true; - } + // Clear dual pending once we actually proceed + unset($_SESSION['pending_dual_login']); - // Validate MFA via a remember-me cookie - if (isset($_COOKIE['rememberme'])) { - $remember_tokens = mysqli_query($mysqli, " - SELECT remember_token_token - FROM remember_tokens - WHERE remember_token_user_id = $user_id - AND remember_token_created_at > (NOW() - INTERVAL $config_login_remember_me_expire DAY) - "); - while ($remember_row = mysqli_fetch_assoc($remember_tokens)) { - if (hash_equals($remember_row['remember_token_token'], $_COOKIE['rememberme'])) { - $mfa_is_complete = true; - $extended_log = 'with 2FA remember-me cookie'; - break; + $user_id = intval($selectedRow['user_id']); + $user_email = sanitizeInput($selectedRow['user_email']); + + // ========================= + // AGENT FLOW + // ========================= + if ($selectedType === 1) { + + if ($config_login_key_required) { + if (!isset($_GET['key']) || $_GET['key'] !== $config_login_key_secret) { + redirect(); } } - } - // Validate MFA code - if (!empty($current_code) && TokenAuth6238::verify($token, $current_code)) { - $mfa_is_complete = true; - $extended_log = 'with MFA'; - } + $user_name = sanitizeInput($selectedRow['user_name']); + $token = sanitizeInput($selectedRow['user_token']); + $force_mfa = intval($selectedRow['user_config_force_mfa']); + $user_encryption_ciphertext = $selectedRow['user_specific_encryption_ciphertext']; - if ($mfa_is_complete) { - // FULL AGENT LOGIN SUCCESS - - // Create a remember me token, if requested - if (isset($_POST['remember_me'])) { - $newRememberToken = bin2hex(random_bytes(64)); - setcookie( - 'rememberme', - $newRememberToken, - time() + 86400 * $config_login_remember_me_expire, - "/", - null, - true, - true - ); - mysqli_query($mysqli, " - INSERT INTO remember_tokens - SET remember_token_user_id = $user_id, - remember_token_token = '$newRememberToken' - "); - - $extended_log .= ", generated a new remember-me token"; + $current_code = 0; + if (isset($_POST['current_code'])) { + $current_code = intval($_POST['current_code']); } - // Check this login isn't suspicious - $sql_ip_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, " - SELECT COUNT(log_id) AS ip_previous_logins - FROM logs - WHERE log_type = 'Login' - AND log_action = 'Success' - AND log_ip = '$session_ip' - AND log_user_id = $user_id - ")); - $ip_previous_logins = sanitizeInput($sql_ip_prev_logins['ip_previous_logins']); + $mfa_is_complete = false; + $extended_log = ''; - $sql_ua_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, " - SELECT COUNT(log_id) AS ua_previous_logins - FROM logs - WHERE log_type = 'Login' - AND log_action = 'Success' - AND log_user_agent = '$session_user_agent' - AND log_user_id = $user_id - ")); - $ua_prev_logins = sanitizeInput($sql_ua_prev_logins['ua_previous_logins']); + if (empty($token)) { + $mfa_is_complete = true; // no MFA configured + } - // Notify if both the user agent and IP are different - if (!empty($config_smtp_host) && $ip_previous_logins == 0 && $ua_prev_logins == 0) { - $subject = "$config_app_name new login for $user_name"; - $body = "Hi $user_name,

A recent successful login to your $config_app_name account was considered a little unusual. If this was you, you can safely ignore this email!

IP Address: $session_ip
User Agent: $session_user_agent

If you did not perform this login, your credentials may be compromised.

Thanks,
ITFlow"; + // remember-me cookie allows bypass + if (isset($_COOKIE['rememberme'])) { + $remember_tokens = mysqli_query($mysqli, " + SELECT remember_token_token + FROM remember_tokens + WHERE remember_token_user_id = $user_id + AND remember_token_created_at > (NOW() - INTERVAL $config_login_remember_me_expire DAY) + "); + while ($remember_row = mysqli_fetch_assoc($remember_tokens)) { + if (hash_equals($remember_row['remember_token_token'], $_COOKIE['rememberme'])) { + $mfa_is_complete = true; + $extended_log = 'with 2FA remember-me cookie'; + break; + } + } + } - $data = [ - [ + // Validate MFA code + if (!empty($current_code) && TokenAuth6238::verify($token, $current_code)) { + $mfa_is_complete = true; + $extended_log = 'with MFA'; + } + + if ($mfa_is_complete) { + + // Clear pending MFA if exists + unset($_SESSION['pending_mfa_login']); + + // Remember me token creation + if (isset($_POST['remember_me'])) { + $newRememberToken = bin2hex(random_bytes(64)); + setcookie('rememberme', $newRememberToken, time() + 86400 * $config_login_remember_me_expire, "/", null, true, true); + + mysqli_query($mysqli, " + INSERT INTO remember_tokens + SET remember_token_user_id = $user_id, + remember_token_token = '$newRememberToken' + "); + $extended_log .= ", generated a new remember-me token"; + } + + // Suspicious login checks / email notify (kept from your code) + $sql_ip_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, " + SELECT COUNT(log_id) AS ip_previous_logins + FROM logs + WHERE log_type = 'Login' + AND log_action = 'Success' + AND log_ip = '$session_ip' + AND log_user_id = $user_id + ")); + $ip_previous_logins = sanitizeInput($sql_ip_prev_logins['ip_previous_logins']); + + $sql_ua_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, " + SELECT COUNT(log_id) AS ua_previous_logins + FROM logs + WHERE log_type = 'Login' + AND log_action = 'Success' + AND log_user_agent = '$session_user_agent' + AND log_user_id = $user_id + ")); + $ua_prev_logins = sanitizeInput($sql_ua_prev_logins['ua_previous_logins']); + + if (!empty($config_smtp_host) && $ip_previous_logins == 0 && $ua_prev_logins == 0) { + $subject = "$config_app_name new login for $user_name"; + $body = "Hi $user_name,

A recent successful login to your $config_app_name account was considered a little unusual. If this was you, you can safely ignore this email!

IP Address: $session_ip
User Agent: $session_user_agent

If you did not perform this login, your credentials may be compromised.

Thanks,
ITFlow"; + + $data = [[ 'from' => $config_mail_from_email, 'from_name' => $config_mail_from_name, 'recipient' => $user_email, 'recipient_name' => $user_name, 'subject' => $subject, 'body' => $body - ] - ]; - addToMailQueue($data); - } + ]]; + addToMailQueue($data); + } - logAction("Login", "Success", "$user_name successfully logged in $extended_log", 0, $user_id); + logAction("Login", "Success", "$user_name successfully logged in $extended_log", 0, $user_id); - // Session info - $_SESSION['user_id'] = $user_id; - $_SESSION['csrf_token'] = randomString(32); - $_SESSION['logged'] = true; + $_SESSION['user_id'] = $user_id; + $_SESSION['csrf_token'] = randomString(32); + $_SESSION['logged'] = true; - // Forcing MFA - if ($force_mfa == 1 && $token == NULL) { - $config_start_page = "user/mfa_enforcement.php"; - } + if ($force_mfa == 1 && $token == NULL) { + $config_start_page = "user/mfa_enforcement.php"; + } - // Setup encryption session key - if (!empty($user_encryption_ciphertext)) { - $site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password); - generateUserSessionKey($site_encryption_master_key); - } + // ✅ Setup encryption session key WITHOUT PASSWORD IN SESSION + // If we are coming from MFA step, master key is in pending_mfa_login. + // If we are coming from login step with no MFA, decrypt now. + $site_encryption_master_key = null; - // Redirect to last visited or config home - if (isset($_GET['last_visited']) && (str_starts_with(base64_decode($_GET['last_visited']), '/agent') || str_starts_with(base64_decode($_GET['last_visited']), '/admin'))) { + if ($is_mfa_step) { + $sess = $_SESSION['pending_mfa_login'] ?? null; + if ($sess && isset($sess['agent_master_key'])) { + $site_encryption_master_key = $sess['agent_master_key']; + } + } else { + // No MFA step: password exists in this request (Step 1) + if (!empty($user_encryption_ciphertext)) { + $site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password); + } + } - redirect($_SERVER["REQUEST_SCHEME"] . "://" . $config_base_url . base64_decode($_GET['last_visited'])); + if (!empty($site_encryption_master_key)) { + generateUserSessionKey($site_encryption_master_key); + } + + // Redirect + if (isset($_GET['last_visited']) && (str_starts_with(base64_decode($_GET['last_visited']), '/agent') || str_starts_with(base64_decode($_GET['last_visited']), '/admin'))) { + redirect($_SERVER["REQUEST_SCHEME"] . "://" . $config_base_url . base64_decode($_GET['last_visited'])); + } else { + redirect("agent/$config_start_page"); + } } else { - redirect("agent/$config_start_page"); - } - } else { + // MFA required — store *only what we need*, not password + $pending_mfa_token = bin2hex(random_bytes(32)); - // MFA is configured and needs to be confirmed, or was unsuccessful + // If we arrived here from role-choice step, the agent master key may be in pending_dual_login + // If we arrived from initial login step, decrypt now (password in memory) and store master key. + $agent_master_key = null; - // HTML code for the token input field - $token_field = " -
- -
-
- -
-
-
"; + if ($is_role_step) { + $sess = $_SESSION['pending_dual_login'] ?? null; + if ($sess && isset($sess['agent_master_key'])) { + $agent_master_key = $sess['agent_master_key']; + } + } else { + if (!empty($user_encryption_ciphertext)) { + $agent_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password); + } + } - if ($current_code !== 0) { - // Logging - logAction("Login", "MFA Failed", "$user_email failed MFA", 0, $user_id); + $_SESSION['pending_mfa_login'] = [ + 'email' => $user_email, + 'agent_user_id' => $user_id, + 'agent_master_key'=> $agent_master_key, // may be null + 'token' => $pending_mfa_token, + 'created' => time() + ]; - // Email the tech to advise their credentials may be compromised - if (!empty($config_smtp_host)) { - $subject = "Important: $config_app_name failed 2FA login attempt for $user_name"; - $body = "Hi $user_name,

A recent login to your $config_app_name account was unsuccessful due to an incorrect 2FA code. If you did not attempt this login, your credentials may be compromised.

Thanks,
ITFlow"; - $data = [ - [ + $token_field = " +
+ +
+
+ +
+
+
"; + + if ($current_code !== 0) { + logAction("Login", "MFA Failed", "$user_email failed MFA", 0, $user_id); + + if (!empty($config_smtp_host)) { + $subject = "Important: $config_app_name failed 2FA login attempt for $user_name"; + $body = "Hi $user_name,

A recent login to your $config_app_name account was unsuccessful due to an incorrect 2FA code. If you did not attempt this login, your credentials may be compromised.

Thanks,
ITFlow"; + $data = [[ 'from' => $config_mail_from_email, 'from_name' => $config_mail_from_name, 'recipient' => $user_email, 'recipient_name' => $user_name, 'subject' => $subject, 'body' => $body - ] - ]; - addToMailQueue($data); + ]]; + addToMailQueue($data); + } + + $response = " +
+ Please enter a valid 2FA code. +
"; } - - $response = " -
- Please Enter 2FA Code! - -
"; } - } - // ========================= - // CLIENT LOGIN FLOW - // ========================= - } elseif ($selectedType === 2) { - - if ($config_client_portal_enable != 1) { - // Client portal disabled - header("HTTP/1.1 401 Unauthorized"); - logAction("Client Login", "Failed", "Client portal disabled; login attempt using $email"); - $response = " -
- Incorrect username or password. - -
"; - } else { - - $client_id = intval($selectedRow['contact_client_id']); - $contact_id = intval($selectedRow['contact_id']); - $user_auth_method = sanitizeInput($selectedRow['user_auth_method']); - - if ($client_id && $contact_id && $user_auth_method === 'local') { - - $_SESSION['client_logged_in'] = true; - $_SESSION['client_id'] = $client_id; - $_SESSION['user_id'] = $user_id; - $_SESSION['user_type'] = 2; - $_SESSION['contact_id'] = $contact_id; - $_SESSION['login_method'] = "local"; - - logAction("Client Login", "Success", "Client contact $user_email successfully logged in locally", $client_id, $user_id); - - header("Location: client/index.php"); - exit(); - - } else { - - // Not allowed or invalid - logAction("Client Login", "Failed", "Failed client portal login attempt using $email (invalid auth method or missing contact/client)", $client_id ?? 0, $user_id); + // ========================= + // CLIENT FLOW + // ========================= + } elseif ($selectedType === 2) { + if ($config_client_portal_enable != 1) { header("HTTP/1.1 401 Unauthorized"); + logAction("Client Login", "Failed", "Client portal disabled; login attempt using $email"); $response = "
Incorrect username or password. -
"; + } else { + + $client_id = intval($selectedRow['contact_client_id']); + $contact_id = intval($selectedRow['contact_id']); + $user_auth_method = sanitizeInput($selectedRow['user_auth_method']); + + if ($client_id && $contact_id && $user_auth_method === 'local') { + + $_SESSION['client_logged_in'] = true; + $_SESSION['client_id'] = $client_id; + $_SESSION['user_id'] = $user_id; + $_SESSION['user_type'] = 2; + $_SESSION['contact_id'] = $contact_id; + $_SESSION['login_method'] = "local"; + + logAction("Client Login", "Success", "Client contact $user_email successfully logged in locally", $client_id, $user_id); + + header("Location: client/index.php"); + exit(); + + } else { + + logAction("Client Login", "Failed", "Failed client portal login attempt using $email (invalid auth method or missing contact/client)", $client_id ?? 0, $user_id); + + header("HTTP/1.1 401 Unauthorized"); + $response = " +
+ Incorrect username or password. +
"; + } } } } @@ -467,6 +565,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_ } } +// Form state +$show_mfa_form = (isset($token_field) && !empty($token_field)); +$show_login_form = (!$show_role_choice && !$show_mfa_form); + ?> @@ -474,21 +576,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_ <?php echo nullable_htmlentities($company_name); ?> | Login - - - - - @@ -505,66 +602,73 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
-

+

-

+

-
> - " - name="email" - value="" - required - > -
-
- + + +
+ " + name="email" + value="" + required autofocus + > +
+
+ +
-
-
> - -
-
- +
+ +
+
+ +
-
- -
-
- - -
-
- + + - + + +
- -
- - + + + + + + + + +
+
+ + +
+
+ + @@ -593,16 +697,9 @@ if (!$config_whitelabel_enabled) { } ?> - - - - - - -