Sign-ins - who signs in, and from where. People: each agent and * portal contact with sign-in activity - their last sign in with its IP * address and device, how many sign-ins and failed ones (the wrong MFA codes * among them), how many IP addresses and, for a date range, how many of them * were new. IP addresses: each address - the sign-ins and failures from it, * the accounts it tried (and emails with no account), when it was first and * last seen, and whether it is being turned away now. Searched by name, email * or IP address, for a date range, sorted from the column headings, a page at * a time; each opens its sign-ins in Admin > Audit Logs. The work is in * functions/sign_in.php. */ // Default Column Sortby Filter $sort = "last_seen"; $order = "DESC"; require_once "includes/inc_all_admin.php"; $page_title = 'Sign-ins'; $view = ($_GET['view'] ?? '') === 'ips' ? 'ips' : ''; $types = "log_type IN ('" . implode("', '", SIGN_IN_TYPES) . "')"; $failed = "log_action IN ('" . implode("', '", SIGN_IN_FAILED) . "')"; $dated = $dtf !== '1970-01-01' || $dtt !== '2099-12-31'; $range = $dated ? "AND log_created_at >= '$dtf 00:00:00' AND log_created_at < '$dtt' + INTERVAL 1 DAY" : ''; $like = $q !== '' ? "AND (user_name LIKE '%$q%' OR user_email LIKE '%$q%' OR log_ip LIKE '%$q%')" : ''; $joins = "LEFT JOIN users ON users.user_id = log_user_id AND log_user_id > 0"; // Only these sort, for the view on show $sortable_columns = $view === 'ips' ? [ 'log_ip' => "log_ip $order", 'sign_ins' => "sign_ins $order, last_seen DESC", 'failures' => "failures $order, last_seen DESC", 'accounts' => "accounts $order, last_seen DESC", 'first_seen' => "first_seen $order", 'last_seen' => "last_seen $order", ] : [ 'user_name' => "user_name $order", 'sign_ins' => "sign_ins $order, last_seen DESC", 'failures' => "failures $order, last_seen DESC", 'ips' => "ips $order, last_seen DESC", 'last_seen' => "last_seen $order", ]; if (!isset($sortable_columns[$sort])) { $sort = 'last_seen'; } $rows = []; if ($view === 'ips') { $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS log_ip, SUM(log_action = 'Success') AS sign_ins, SUM($failed) AS failures, SUM(log_action = 'Blocked') AS blocked, COUNT(DISTINCT NULLIF(log_user_id, 0)) AS accounts, COUNT(DISTINCT IF(log_user_id = 0 AND log_action = 'Failed', log_description, NULL)) AS unknown, MIN(log_created_at) AS first_seen, MAX(log_created_at) AS last_seen, MAX(log_id) AS last_id FROM logs $joins WHERE $types AND log_ip <> '' $range $like GROUP BY log_ip ORDER BY {$sortable_columns[$sort]} LIMIT $record_from, $record_to"); } else { $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS log_user_id, user_name, user_email, user_avatar, user_type, user_status, user_archived_at, SUM(log_action = 'Success') AS sign_ins, SUM($failed) AS failures, SUM(log_action = 'MFA Failed') AS mfa_failures, COUNT(DISTINCT IF(log_action = 'Success', log_ip, NULL)) AS ips, MAX(IF(log_action = 'Success', log_id, NULL)) AS last_success_id, MAX(log_created_at) AS last_seen FROM logs JOIN users ON users.user_id = log_user_id WHERE $types AND log_user_id > 0 $range $like GROUP BY log_user_id ORDER BY {$sortable_columns[$sort]} LIMIT $record_from, $record_to"); } $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); while ($row = mysqli_fetch_assoc($sql)) { $rows[] = $row; } // How many of each, for the filters $counts = mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(DISTINCT users.user_id), COUNT(DISTINCT NULLIF(log_ip, '')) FROM logs $joins WHERE $types $range $like")); if ($view === 'ips') { // The accounts each address tried, its last device, and whether it is turned away now $ip_list = implode(', ', array_map(function ($row) { return "'" . escapeSql($row['log_ip']) . "'"; }, $rows)) ?: "''"; $tried = []; $sql = mysqli_query($mysqli, "SELECT log_ip, users.user_id, user_name, user_avatar, user_type, MAX(log_id) AS last_id FROM logs JOIN users ON users.user_id = log_user_id WHERE log_ip IN ($ip_list) AND $types $range GROUP BY log_ip, users.user_id ORDER BY last_id DESC"); while ($row = mysqli_fetch_assoc($sql)) { $tried[$row['log_ip']][] = $row; } $last_devices = []; $last_ids = array_filter(array_map('intval', array_column($rows, 'last_id'))); if ($last_ids) { $sql = mysqli_query($mysqli, "SELECT log_ip, log_user_agent FROM logs WHERE log_id IN (" . implode(',', $last_ids) . ")"); while ($row = mysqli_fetch_assoc($sql)) { $last_devices[$row['log_ip']] = auditLogDevice($row['log_user_agent']); } } $turned_away = []; $sql = mysqli_query($mysqli, "SELECT log_ip, COUNT(*) FROM logs WHERE log_ip IN ($ip_list) AND $types AND $failed AND log_created_at > NOW() - INTERVAL " . SIGN_IN_WINDOW_MINUTES . " MINUTE GROUP BY log_ip HAVING COUNT(*) >= " . SIGN_IN_LOCKOUT); while ($row = mysqli_fetch_row($sql)) { $turned_away[$row[0]] = true; } } else { // Each one's last sign in, and the addresses that were new in the date range $last = []; $last_ids = array_filter(array_map('intval', array_column($rows, 'last_success_id'))); if ($last_ids) { $sql = mysqli_query($mysqli, "SELECT log_id, log_ip, log_user_agent, log_created_at FROM logs WHERE log_id IN (" . implode(',', $last_ids) . ")"); while ($row = mysqli_fetch_assoc($sql)) { $last[intval($row['log_id'])] = $row; } } $new_ips = []; $user_ids = array_filter(array_map('intval', array_column($rows, 'log_user_id'))); if ($dated && $user_ids) { $sql = mysqli_query($mysqli, "SELECT log_user_id, COUNT(*) FROM ( SELECT log_user_id, MIN(log_created_at) AS first_at FROM logs WHERE log_user_id IN (" . implode(',', $user_ids) . ") AND $types AND log_action = 'Success' AND log_ip <> '' GROUP BY log_user_id, log_type, log_ip) firsts WHERE first_at >= '$dtf 00:00:00' AND first_at < '$dtt' + INTERVAL 1 DAY GROUP BY log_user_id"); while ($row = mysqli_fetch_row($sql)) { $new_ips[intval($row[0])] = intval($row[1]); } } // A portal contact opens on their contact page $contacts = []; if ($user_ids) { $sql = mysqli_query($mysqli, "SELECT contact_user_id, contact_id, contact_client_id FROM contacts WHERE contact_user_id IN (" . implode(',', $user_ids) . ")"); while ($row = mysqli_fetch_assoc($sql)) { $contacts[intval($row['contact_user_id'])] = $row; } } } $sort_link = function ($column, $label) use ($url_query_strings_sort, $sort, $disp, $order_icon) { return '' . $label . ($sort === $column ? " $order_icon" : '') . ''; }; // A view keeps the search and dates, and starts from the top $view_url = function ($key) { $query = array_diff_key($_GET, ['page' => 1, 'sort' => 1, 'order' => 1]); $query['view'] = $key; return '?' . http_build_query(array_filter($query, function ($value) { return $value !== '' && $value !== null && $value !== []; })); }; // Their sign-ins in Admin > Audit Logs, for the same dates $dates_query = $dated ? ['canned_date' => 'custom', 'dtf' => $dtf, 'dtt' => $dtt] : []; $audit_url = function (array $query) use ($dates_query) { return '/admin/audit_logs.php?' . http_build_query(['view' => 'signins'] + $query + $dates_query); }; ?>

Sign-ins

">
Last sign in
Turned away now Turned away ×
Portal' : '' ?> 3) { ?>and more
email with no account
Portal' : '' ?> ' . escapeHtml($state) . '' : '' ?>
None
MFA
new
$view === 'ips' ? 'IP addresses' : 'sign-ins', 'icon' => 'fa-sign-in-alt', 'filtered' => $q !== '' || $dated, 'views' => ['view'], 'text' => 'Agents and portal contacts signing in - and failing to - show here, by who and by where from.', ]; require_once "../includes/filter_footer.php"; ?>