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); }; ?>
| = $sort_link('log_ip', 'IP address') ?> | = $sort_link('accounts', 'Accounts tried') ?> | = $sort_link('sign_ins', 'Sign-ins') ?> | = $sort_link('failures', 'Failed') ?> | = $sort_link('first_seen', 'First seen') ?> | = $sort_link('last_seen', 'Last seen') ?> | |
|---|---|---|---|---|---|---|
| = $sort_link('user_name', 'Who') ?> | Last sign in | = $sort_link('sign_ins', 'Sign-ins') ?> | = $sort_link('failures', 'Failed') ?> | = $sort_link('ips', 'IP addresses') ?> | = $sort_link('last_seen', 'Last seen') ?> | |
|
= escapeHtml($ip) ?>
= escapeHtml($last_devices[$ip] ?? '') ?>
Turned away now
Turned away = number_format(intval($row['blocked'])) ?>×
|
= userAvatar($person['user_id'], $person['user_name'], (string) $person['user_avatar'], 'xs') ?>= escapeHtml($person['user_name']) ?>= intval($person['user_type']) === 2 ? ' Portal' : '' ?>
3) { ?>and = count($people) - 3 ?> more
= $unknown ?> email= $unknown === 1 ? '' : 's' ?> with no account
|
= number_format(intval($row['sign_ins'])) ?> | = number_format(intval($row['failures'])) ?> | = escapeHtml(date('M j, Y g:i A', strtotime($row['first_seen']))) ?> | = escapeHtml(timeAgo($row['last_seen'])) ?> | |
|
= userAvatar($user_id, $row['user_name'], (string) $row['user_avatar'], 'sm') ?>
= escapeHtml($row['user_name']) ?>
= escapeHtml($row['user_name']) ?>
= escapeHtml($row['user_name']) ?>
= $portal ? 'Portal' : '' ?>
= $state !== '' ? '' . escapeHtml($state) . '' : '' ?>
= escapeHtml($row['user_email']) ?>
|
= escapeHtml(timeAgo($success['log_created_at'])) ?>
= escapeHtml($success['log_ip']) ?>= auditLogDevice($success['log_user_agent']) !== '' ? ' · ' . escapeHtml(auditLogDevice($success['log_user_agent'])) : '' ?>
None= $dated ? ' in these dates' : '' ?>
|
= number_format(intval($row['sign_ins'])) ?> |
= number_format(intval($row['failures'])) ?>
= number_format(intval($row['mfa_failures'])) ?> MFA
|
= number_format(intval($row['ips'])) ?>
= $new_ips[$user_id] ?> new
|
= escapeHtml(timeAgo($row['last_seen'])) ?> |