Fix Login flow where user agent and client exists and agent has MFA but will not let them continue, also update some wording and button colors. Also dont show email password fields again after success and login as agent and client is shown.

This commit is contained in:
johnnyq
2025-12-20 14:30:57 -05:00
parent cab81ca170
commit e60a7a59f9

381
login.php
View File

@@ -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("<h2>$config_app_name</h2>Your IP address has been blocked due to repeated failed login attempts. Please try again later. <br><br>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'];
// Azure / Entra for client
$azure_client_id = $row['config_azure_client_id'] ?? null;
$response = null;
$token_field = null;
$show_role_choice = false;
$email = '';
$password = '';
$password = ''; // only ever used in the initial POST request
// Handle POST login request (normal login or role choice)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_POST['role_choice']))) {
// 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 = "
<div class='alert alert-danger'>
Your login session expired. Please sign in again.
</div>";
} 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 = "
<div class='alert alert-danger'>
Your MFA session expired. Please sign in again.
</div>";
} 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'] ?? '';
$role_choice = $_POST['role_choice'] ?? null; // 'agent' or 'client'
// Basic validation
if (empty($email) || empty($password) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
header("HTTP/1.1 401 Unauthorized");
$response = "
<div class='alert alert-danger'>
Incorrect username or password.
<button class='close' data-dismiss='alert'>&times;</button>
</div>";
} else {
}
}
/*
* 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.
*/
// 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,36 +192,63 @@ 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)) {
$ut = intval($r['user_type']);
if ($is_login_step) {
// Only Step 1 checks password
if (!password_verify($password, $r['user_password'])) {
continue;
}
if (intval($r['user_type']) === 1 && $agentRow === null) {
} 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 (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 = "
<div class='alert alert-danger'>
Incorrect username or password.
<button class='close' data-dismiss='alert'>&times;</button>
</div>";
} else {
} elseif ($agentRow !== null && $clientRow !== null) {
$selectedRow = null;
$selectedType = null; // 1 agent, 2 client
// Dual role
if ($agentRow !== null && $clientRow !== null) {
// Both agent and client accounts share same email + password
if ($role_choice === 'agent') {
$selectedRow = $agentRow;
$selectedType = 1;
@@ -196,18 +256,41 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
$selectedRow = $clientRow;
$selectedType = 2;
} else {
// First time we realise this is a dual-role account: ask user to pick
// 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 = "
<div class='alert alert-info'>
<div class='alert alert-light'>
This login can be used as either an Agent account or a Client Portal account.
Please choose how you want to continue.
<button class='close' data-dismiss='alert'>&times;</button>
</div>";
}
} else {
// Only one valid row (agent OR client)
// Single role
if ($agentRow !== null) {
$selectedRow = $agentRow;
$selectedType = 1;
@@ -217,19 +300,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
}
}
// If we have a specific user selected, proceed with actual login
// 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']);
$session_user_id = $user_id; // to pass the user_id to logAction function
// =========================
// AGENT LOGIN FLOW
// AGENT 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();
@@ -239,9 +323,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
$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'])) {
@@ -252,11 +334,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
$extended_log = '';
if (empty($token)) {
// MFA is not configured
$mfa_is_complete = true;
$mfa_is_complete = true; // no MFA configured
}
// Validate MFA via a remember-me cookie
// remember-me cookie allows bypass
if (isset($_COOKIE['rememberme'])) {
$remember_tokens = mysqli_query($mysqli, "
SELECT remember_token_token
@@ -280,30 +361,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
}
if ($mfa_is_complete) {
// FULL AGENT LOGIN SUCCESS
// Create a remember me token, if requested
// 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
);
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";
}
// Check this login isn't suspicious
// 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
@@ -324,60 +399,91 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
"));
$ua_prev_logins = sanitizeInput($sql_ua_prev_logins['ua_previous_logins']);
// 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, <br><br>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!<br><br>IP Address: $session_ip<br> User Agent: $session_user_agent <br><br>If you did not perform this login, your credentials may be compromised. <br><br>Thanks, <br>ITFlow";
$data = [
[
$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 info
$_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";
}
// Setup encryption session 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;
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 to last visited or config home
// 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 is configured and needs to be confirmed, or was unsuccessful
// 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()
];
// HTML code for the token input field
$token_field = "
<div class='input-group mb-3'>
<input type='text' inputmode='numeric' pattern='[0-9]*' maxlength='6'
class='form-control' placeholder='Enter your 2FA code'
class='form-control' placeholder='Verify your 2FA code'
name='current_code' required autofocus>
<div class='input-group-append'>
<div class='input-group-text'>
@@ -387,47 +493,40 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
</div>";
if ($current_code !== 0) {
// Logging
logAction("Login", "MFA Failed", "$user_email failed MFA", 0, $user_id);
// 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, <br><br>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. <br><br>Thanks, <br>ITFlow";
$data = [
[
$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 = "
<div class='alert alert-warning'>
Please Enter 2FA Code!
<button class='close' data-dismiss='alert'>&times;</button>
<div class='alert alert-danger'>
Please enter a valid 2FA code.
</div>";
}
}
// =========================
// CLIENT LOGIN FLOW
// CLIENT 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 = "
<div class='alert alert-danger'>
Incorrect username or password.
<button class='close' data-dismiss='alert'>&times;</button>
</div>";
} else {
@@ -451,14 +550,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
} 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);
header("HTTP/1.1 401 Unauthorized");
$response = "
<div class='alert alert-danger'>
Incorrect username or password.
<button class='close' data-dismiss='alert'>&times;</button>
</div>";
}
}
@@ -466,6 +563,11 @@ 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);
?>
<!DOCTYPE html>
@@ -474,21 +576,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><?php echo nullable_htmlentities($company_name); ?> | Login</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<!-- Font Awesome -->
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
<!-- Favicon -->
<?php if(file_exists('uploads/favicon.ico')) { ?>
<link rel="icon" type="image/x-icon" href="/uploads/favicon.ico">
<?php } ?>
<!-- Theme style -->
<link rel="stylesheet" href="plugins/adminlte/css/adminlte.min.css">
</head>
<body class="hold-transition login-page">
@@ -514,12 +611,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
<form method="post">
<div class="input-group mb-3" <?php if (isset($token_field) && $token_field) { echo "hidden"; } ?>>
<?php if ($show_login_form): ?>
<!-- STEP 1: Email + Password -->
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="<?php if ($config_login_key_required) { if (!isset($_GET['key']) || $_GET['key'] !== $config_login_key_secret) { echo "Client "; } } echo "Email"; ?>"
name="email"
value="<?php echo htmlspecialchars($email ?? '', ENT_QUOTES); ?>"
required <?php if (!isset($token_field) || !$token_field) { echo "autofocus"; } ?>
required autofocus
>
<div class="input-group-append">
<div class="input-group-text">
@@ -528,10 +627,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
</div>
</div>
<div class="input-group mb-3" <?php if (isset($token_field) && $token_field) { echo "hidden"; } ?>>
<input type="password" class="form-control" placeholder="Password" name="password"
value="<?php echo isset($token_field) && $token_field ? htmlspecialchars($password ?? '', ENT_QUOTES) : ''; ?>"
required>
<div class="input-group mb-3">
<input type="password" class="form-control" placeholder="Password" name="password" required>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
@@ -539,32 +636,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
</div>
</div>
<?php
// If agent needs MFA, show 2FA field + remember me
if (isset($token_field) && $token_field) {
<button type="submit" class="btn btn-primary btn-block mb-3" name="login">Sign In</button>
<?php endif; ?>
<?php if ($show_role_choice): ?>
<!-- STEP 2: Role choice only -->
<input type="hidden" name="pending_login_token"
value="<?php echo htmlspecialchars($_SESSION['pending_dual_login']['token'] ?? '', ENT_QUOTES); ?>">
<div class="mb-2 text-center">
<button type="submit" class="btn btn-dark btn-block mb-2" name="role_choice" value="agent">
Log in as Agent
</button>
<button type="submit" class="btn btn-light btn-block" name="role_choice" value="client">
Log in as Client
</button>
</div>
<?php endif; ?>
<?php if ($show_mfa_form): ?>
<!-- STEP 3: MFA only -->
<?php echo $token_field; ?>
<input type="hidden" name="pending_mfa_token"
value="<?php echo htmlspecialchars($_SESSION['pending_mfa_login']['token'] ?? '', ENT_QUOTES); ?>">
echo $token_field;
?>
<div class="form-group mb-3">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="remember_me" name="remember_me">
<label class="custom-control-label" for="remember_me">Remember Me</label>
</div>
</div>
<?php } ?>
<?php if ($show_role_choice): ?>
<!-- When both agent & client accounts exist with same email/password -->
<div class="mb-2 text-center">
<button type="submit" class="btn btn-primary btn-block mb-2" name="role_choice" value="agent">
Log in as Agent
</button>
<button type="submit" class="btn btn-success btn-block" name="role_choice" value="client">
Log in as Client
</button>
</div>
<?php else: ?>
<button type="submit" class="btn btn-primary btn-block mb-3" name="login">Sign In</button>
<button type="submit" class="btn btn-dark btn-block mb-3" name="mfa_login">Verify & Sign In</button>
<?php endif; ?>
</form>
@@ -593,16 +697,9 @@ if (!$config_whitelabel_enabled) {
}
?>
<!-- jQuery -->
<script src="plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- AdminLTE App -->
<script src="plugins/adminlte/js/adminlte.min.js"></script>
<!-- Prevents resubmit on refresh or back -->
<script src="js/login_prevent_resubmit.js"></script>
</body>