(NOW() - INTERVAL 10 MINUTE)" )); $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"); 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."); } // Settings $sql_settings = mysqli_query($mysqli, " SELECT settings.*, companies.company_name, companies.company_logo FROM settings LEFT JOIN companies ON settings.company_id = companies.company_id WHERE settings.company_id = 1 "); $row = mysqli_fetch_array($sql_settings); $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']); $config_smtp_host = $row['config_smtp_host']; $config_smtp_port = intval($row['config_smtp_port']); $config_smtp_encryption = $row['config_smtp_encryption']; $config_smtp_username = $row['config_smtp_username']; $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']); $config_client_portal_enable = intval($row['config_client_portal_enable']); $config_login_remember_me_expire = intval($row['config_login_remember_me_expire']); $config_login_key_required = $row['config_login_key_required']; $config_login_key_secret = $row['config_login_key_secret']; $azure_client_id = $row['config_azure_client_id'] ?? null; $response = null; $token_field = null; $show_role_choice = false; $email = ''; $password = ''; // only ever used in the initial POST request // Helpers function pendingExpired($sess, $ttl_seconds = 120) { return !$sess || empty($sess['created']) || (time() - intval($sess['created']) > $ttl_seconds); } // POST handling if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_POST['role_choice']) || isset($_POST['mfa_login']))) { $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.*, contacts.*, clients.* FROM users LEFT JOIN user_settings ON users.user_id = user_settings.user_id LEFT JOIN contacts ON users.user_id = contacts.contact_user_id LEFT JOIN clients ON contacts.contact_client_id = clients.client_id WHERE user_email = '$email' AND user_archived_at IS NULL AND user_status = 1 AND ( user_type = 1 OR (user_type = 2 AND client_archived_at IS NULL) ) "); $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)) { $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 ($ut === 1 && $agentRow === null) { $agentRow = $r; } if ($ut === 2 && $clientRow === null) { $clientRow = $r; } } if ($agentRow === null && $clientRow === null) { header("HTTP/1.1 401 Unauthorized"); logAction("Login", "Failed", "Failed login attempt using $email"); $response = "
Incorrect username or password.
"; } else { $selectedRow = null; $selectedType = null; // 1 agent, 2 client // Dual role if ($agentRow !== null && $clientRow !== null) { 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() ]; } } } else { // Single role if ($agentRow !== null) { $selectedRow = $agentRow; $selectedType = 1; } else { $selectedRow = $clientRow; $selectedType = 2; } } // Proceed if selected if ($selectedRow !== null && $selectedType !== null) { // Clear dual pending once we actually proceed unset($_SESSION['pending_dual_login']); $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(); } } $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']; $current_code = 0; if (isset($_POST['current_code'])) { $current_code = intval($_POST['current_code']); } $mfa_is_complete = false; $extended_log = ''; if (empty($token)) { $mfa_is_complete = true; // no MFA configured } // 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; } } } // 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); } logAction("Login", "Success", "$user_name successfully logged in $extended_log", 0, $user_id); $_SESSION['user_id'] = $user_id; $_SESSION['csrf_token'] = randomString(32); $_SESSION['logged'] = true; if ($force_mfa == 1 && $token == NULL) { $config_start_page = "user/mfa_enforcement.php"; } // ✅ 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; 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); } } 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 { // MFA required — store *only what we need*, not password $pending_mfa_token = bin2hex(random_bytes(32)); // 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; 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); } } $_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() ]; $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); } $response = "
Please enter a valid 2FA code.
"; } } // ========================= // 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.
"; } } } } } } } // Form state $show_mfa_form = (isset($token_field) && !empty($token_field)); $show_login_form = (!$show_role_choice && !$show_mfa_form); ?> <?php echo nullable_htmlentities($company_name); ?> | Login
Powered by ITFlow'; } ?>