Fix login not passing master key if agent is client and agent and if MFA is enabled

This commit is contained in:
johnnyq 2026-01-17 17:07:22 -05:00
parent 0a658d7cab
commit c1f0b63101
2 changed files with 119 additions and 94 deletions

View File

@ -17,7 +17,7 @@ $client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);
$contact_name = nullable_htmlentities($row['contact_name']);
$contact_title = nullable_htmlentities($row['contact_title']);
$contact_department =nullable_htmlentities($row['contact_department']);
$contact_department = nullable_htmlentities($row['contact_department']);
$contact_phone_country_code = nullable_htmlentities($row['contact_phone_country_code']);
$contact_phone = nullable_htmlentities(formatPhoneNumber($row['contact_phone'], $contact_phone_country_code));
$contact_extension = nullable_htmlentities($row['contact_extension']);
@ -196,87 +196,10 @@ ob_start();
<div class="modal-body">
<div class="row">
<?php if ($contact_phone || $contact_mobile || $contact_extension || $contact_email) { ?>
<div class="col-4">
<div class="media">
<i class="fas fa-fw fa-user fa-2x text-secondary"></i>
<div class="media-body ml-2">
<dt>Contact Details</dt>
<?php if ($contact_phone) { ?>
<div>
<i class="fas fa-fw fa-phone-alt text-secondary mt-2"></i>
<a href="tel:<?= $contact_phone ?>"><?= $contact_phone ?></a>
<?php if ($contact_extension) { ?>
<span>ext: <?= $contact_extension ?></span>
<?php } ?>
</div>
<?php } ?>
<?php if ($contact_mobile) { ?>
<div>
<i class="fas fa-fw fa-mobile-alt text-secondary mt-2"></i>
<a href="tel:<?= $contact_mobile ?>"><?= $contact_mobile ?></a>
</div>
<?php } ?>
<?php if ($contact_email) { ?>
<div>
<i class="fas fa-fw fa-envelope text-secondary mt-2"></i>
<a href='mailto:<?= $contact_email ?>'><?= $contact_email ?></a>
<button type="button" class='btn btn-sm clipboardjs' data-clipboard-text='<?= $contact_email ?>'>
<i class='far fa-copy text-secondary'></i></button>
</div>
<?php } ?>
</div>
</div>
</div>
<?php } ?>
<?php if ($location_name) { ?>
<div class="col-4">
<div class="media">
<i class="fas fa-fw fa-map-marker-alt text-secondary fa-2x"></i>
<div class="media-body ml-2">
<dt><?= $location_name ?></dt>
<dd>
<div><?= $location_address ?></div>
<div><?= "$location_city $location_state $location_zip" ?></div>
<div><?= $location_country ?></div>
</dd>
</div>
</div>
</div>
<?php } ?>
<div class="col-4">
<div class="media">
<i class="fa fa-fw fa-info-circle text-secondary fa-2x"></i>
<div class="media-body ml-2">
<?php if ($contact_primary) { ?>
<span class="text-success text-bold">Primary Contact</span><br>
<?php } ?>
<?php if ($contact_billing) { ?>
<span class="text-dark font-weight-bold">Billing</span><br>
<?php } ?>
<?php if ($contact_technical) { ?>
<span class="text-secondary">Technical</span><br>
<?php } ?>
<?php if ($contact_important) { ?>
<span class="text-dark font-weight-bold">Important</span>
<?php } ?>
<?php if ($contact_pin) { ?>
<div>
Pin: <?= $contact_pin ?>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<ul class="nav nav-pills nav-justified mt-3">
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-contact-details"><i class="fas fa-fw fa-user fa-2x"></i><br>Details</a>
</li>
<?php if ($asset_count) { ?>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-contact-assets<?= $contact_id ?>"><i class="fas fa-fw fa-desktop fa-2x"></i><br>Assets (<?= $asset_count ?>)</a>
@ -321,6 +244,90 @@ ob_start();
<div class="tab-content">
<div class="tab-pane fade" id="pills-contact-details">
<div class="row">
<?php if ($contact_phone || $contact_mobile || $contact_extension || $contact_email) { ?>
<div class="col-4">
<div class="media">
<i class="fas fa-fw fa-user fa-2x text-secondary"></i>
<div class="media-body ml-2">
<dt>Contact Details</dt>
<?php if ($contact_phone) { ?>
<div>
<i class="fas fa-fw fa-phone-alt text-secondary mt-2"></i>
<a href="tel:<?= $contact_phone ?>"><?= $contact_phone ?></a>
<?php if ($contact_extension) { ?>
<span>ext: <?= $contact_extension ?></span>
<?php } ?>
</div>
<?php } ?>
<?php if ($contact_mobile) { ?>
<div>
<i class="fas fa-fw fa-mobile-alt text-secondary mt-2"></i>
<a href="tel:<?= $contact_mobile ?>"><?= $contact_mobile ?></a>
</div>
<?php } ?>
<?php if ($contact_email) { ?>
<div>
<i class="fas fa-fw fa-envelope text-secondary mt-2"></i>
<a href='mailto:<?= $contact_email ?>'><?= $contact_email ?></a>
<button type="button" class='btn btn-sm clipboardjs' data-clipboard-text='<?= $contact_email ?>'>
<i class='far fa-copy text-secondary'></i></button>
</div>
<?php } ?>
</div>
</div>
</div>
<?php } ?>
<?php if ($location_name) { ?>
<div class="col-4">
<div class="media">
<i class="fas fa-fw fa-map-marker-alt text-secondary fa-2x"></i>
<div class="media-body ml-2">
<dt><?= $location_name ?></dt>
<dd>
<div><?= $location_address ?></div>
<div><?= "$location_city $location_state $location_zip" ?></div>
<div><?= $location_country ?></div>
</dd>
</div>
</div>
</div>
<?php } ?>
<div class="col-4">
<div class="media">
<i class="fa fa-fw fa-info-circle text-secondary fa-2x"></i>
<div class="media-body ml-2">
<?php if ($contact_primary) { ?>
<span class="text-success text-bold">Primary Contact</span><br>
<?php } ?>
<?php if ($contact_billing) { ?>
<span class="text-dark font-weight-bold">Billing</span><br>
<?php } ?>
<?php if ($contact_technical) { ?>
<span class="text-secondary">Technical</span><br>
<?php } ?>
<?php if ($contact_important) { ?>
<span class="text-dark font-weight-bold">Important</span>
<?php } ?>
<?php if ($contact_pin) { ?>
<div>
Pin: <?= $contact_pin ?>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
</div>
<?php if ($asset_count) { ?>
<div class="tab-pane fade" id="pills-contact-assets<?= $contact_id ?>">

View File

@ -305,8 +305,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
// Proceed if selected
if ($selectedRow !== null && $selectedType !== null) {
// Clear dual pending once we actually proceed
unset($_SESSION['pending_dual_login']);
// Cache pending sessions BEFORE unsetting anything (needed for role->MFA and MFA success)
$pending_dual = $_SESSION['pending_dual_login'] ?? null;
$pending_mfa = $_SESSION['pending_mfa_login'] ?? null;
// NOTE: do NOT unset pending_dual_login here anymore; we may still need agent_master_key for MFA or role-step agent login
$user_id = intval($selectedRow['user_id']);
$user_email = sanitizeInput($selectedRow['user_email']);
@ -364,9 +367,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
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));
@ -429,17 +429,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
}
// 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'])) {
// Step 3: MFA submit
$sess = $pending_mfa ?? ($_SESSION['pending_mfa_login'] ?? null);
if ($sess && !empty($sess['agent_master_key'])) {
$site_encryption_master_key = $sess['agent_master_key'];
}
} elseif ($is_role_step) {
// Step 2: role choice (no password available)
$sess = $pending_dual ?? ($_SESSION['pending_dual_login'] ?? null);
if ($sess && !empty($sess['agent_master_key'])) {
$site_encryption_master_key = $sess['agent_master_key'];
}
} else {
// No MFA step: password exists in this request (Step 1)
// Step 1: initial login (password available in this request)
if (!empty($user_encryption_ciphertext)) {
$site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password);
}
@ -449,6 +456,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
generateUserSessionKey($site_encryption_master_key);
}
// NOW safe to clear pending sessions AFTER we used them
unset($_SESSION['pending_mfa_login']);
unset($_SESSION['pending_dual_login']);
// 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']));
@ -461,13 +472,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
// 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 here from role-choice step, the agent master key may be in cached pending_dual
// 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'])) {
$sess = $pending_dual ?? ($_SESSION['pending_dual_login'] ?? null);
if ($sess && array_key_exists('agent_master_key', $sess)) {
$agent_master_key = $sess['agent_master_key'];
}
} else {
@ -484,6 +495,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
'created' => time()
];
// Now that we've transferred what we need, it's safe to clear the dual-role pending session.
unset($_SESSION['pending_dual_login']);
$token_field = "
<div class='input-group mb-3'>
<input type='text' inputmode='numeric' pattern='[0-9]*' maxlength='6'
@ -567,6 +581,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_
$session_user_id = $user_id;
logAction("Client Login", "Success", "Client contact $user_email successfully logged in locally", $client_id, $user_id);
// Clear any pending sessions (avoid stale dual-role/MFA state)
unset($_SESSION['pending_dual_login']);
unset($_SESSION['pending_mfa_login']);
header("Location: client/index.php");
exit();