[Web] Show users last PW change, allow to select n days for last logins

master
andryyy 2021-06-09 07:19:57 +02:00
parent da20d5dc38
commit 47b57df3a2
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
11 changed files with 65 additions and 56 deletions

View File

@ -57,8 +57,9 @@ table tbody tr td input[type="checkbox"] {
font-size: 8pt !important; font-size: 8pt !important;
} }
.label-last-login { .label-last-login {
line-height: 2.5; line-height: 2.2;
color: #4a4a4a!important; color: #4a4a4a!important;
padding: .2em .4em .3em !important;
background-color: #ececec!important; background-color: #ececec!important;
} }

View File

@ -125,4 +125,8 @@ border-bottom-width: 3px;
} }
.xmpp-logo-user { .xmpp-logo-user {
width:64px; width:64px;
}
.recent-login-success {
margin-top:2px;
margin-right:10px;
} }

View File

@ -251,21 +251,21 @@ function password_check($password1, $password2) {
return true; return true;
} }
function last_login($action, $username, $sasl_limit = 10) { function last_login($action, $username, $sasl_limit_days = 7) {
global $pdo; global $pdo;
global $redis; global $redis;
$sasl_limit = intval($sasl_limit); $sasl_limit_days = intval($sasl_limit_days);
switch ($action) { switch ($action) {
case 'get': case 'get':
if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service`, `app_password` FROM `sasl_logs` $stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service`, `app_password` FROM `sasl_logs`
LEFT OUTER JOIN `app_passwd` on `sasl_logs`.`app_password` = `app_passwd`.`id` LEFT OUTER JOIN `app_passwd` on `sasl_logs`.`app_password` = `app_passwd`.`id`
WHERE `username` = :username WHERE `username` = :username
AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days
AND `success` = 1 AND `success` = 1
GROUP BY `real_rip`, `service`, `app_password` GROUP BY `real_rip`, `service`, `app_password`
ORDER BY `datetime` DESC ORDER BY `datetime` DESC;');
LIMIT :sasl_limit;'); $stmt->execute(array(':username' => $username, ':sasl_limit_days' => ($sasl_limit_days * 24)));
$stmt->execute(array(':username' => $username, ':sasl_limit' => $sasl_limit));
$sasl = $stmt->fetchAll(PDO::FETCH_ASSOC); $sasl = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($sasl as $k => $v) { foreach ($sasl as $k => $v) {
if (!filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { if (!filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {

View File

@ -172,29 +172,12 @@ function exception_handler($e) {
set_exception_handler('exception_handler'); set_exception_handler('exception_handler');
// TODO: Move function // TODO: Move function
function get_remote_ip($anonymize = null) { function get_remote_ip() {
global $ANONYMIZE_IPS;
if ($anonymize === null) {
$anonymize = $ANONYMIZE_IPS;
}
elseif ($anonymize !== true && $anonymize !== false) {
$anonymize = true;
}
$remote = $_SERVER['REMOTE_ADDR']; $remote = $_SERVER['REMOTE_ADDR'];
if (filter_var($remote, FILTER_VALIDATE_IP) === false) { if (filter_var($remote, FILTER_VALIDATE_IP) === false) {
return '0.0.0.0'; return '0.0.0.0';
} }
if ($anonymize) { return $remote;
if (strlen(inet_pton($remote)) == 4) {
return inet_ntop(inet_pton($remote) & inet_pton("255.255.255.0"));
}
elseif (strlen(inet_pton($remote)) == 16) {
return inet_ntop(inet_pton($remote) & inet_pton('ffff:ffff:ffff:ffff:0000:0000:0000:0000'));
}
}
else {
return $remote;
}
} }
// Load core functions first // Load core functions first

View File

@ -82,24 +82,24 @@ $DEFAULT_LANG = 'en';
// https://www.iso.org/obp/ui/#search // https://www.iso.org/obp/ui/#search
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
$AVAILABLE_LANGUAGES = array( $AVAILABLE_LANGUAGES = array(
'cs' => 'Čeština (Czech)', 'cs' => 'Čeština (Czech)',
'da' => 'Danish (Dansk)', 'da' => 'Danish (Dansk)',
'de' => 'Deutsch (German)', 'de' => 'Deutsch (German)',
'en' => 'English', 'en' => 'English',
'es' => 'Español (Spanish)', 'es' => 'Español (Spanish)',
'fi' => 'Suomi (Finish)', 'fi' => 'Suomi (Finish)',
'fr' => 'Français (French)', 'fr' => 'Français (French)',
'hu' => 'Magyar (Hungarian)', 'hu' => 'Magyar (Hungarian)',
'it' => 'Italiano (Italian)', 'it' => 'Italiano (Italian)',
'ko' => '한국어 (Korean)', 'ko' => '한국어 (Korean)',
'lv' => 'latviešu (Latvian)', 'lv' => 'latviešu (Latvian)',
'nl' => 'Nederlands (Dutch)', 'nl' => 'Nederlands (Dutch)',
'pl' => 'Język Polski (Polish)', 'pl' => 'Język Polski (Polish)',
'pt' => 'Português (Portuguese)', 'pt' => 'Português (Portuguese)',
'ro' => 'Română (Romanian)', 'ro' => 'Română (Romanian)',
'ru' => 'Pусский (Russian)', 'ru' => 'Pусский (Russian)',
'sk' => 'Slovenčina (Slovak)', 'sk' => 'Slovenčina (Slovak)',
'sv' => 'Svenska (Swedish)', 'sv' => 'Svenska (Swedish)',
'zh' => '中文 (Chinese)' 'zh' => '中文 (Chinese)'
); );
@ -139,9 +139,6 @@ $OTP_LABEL = "mailcow UI";
// How long to wait (in s) for cURL Docker requests // How long to wait (in s) for cURL Docker requests
$DOCKER_TIMEOUT = 60; $DOCKER_TIMEOUT = 60;
// Anonymize IPs logged via UI
$ANONYMIZE_IPS = true;
// Split DKIM key notation (bind format) // Split DKIM key notation (bind format)
$SPLIT_DKIM_255 = false; $SPLIT_DKIM_255 = false;

View File

@ -371,6 +371,7 @@ jQuery(function($){
'<div class="label label-last-login">POP3 @ ' + unix_time_format(Number(res[1])) + '</div><br>' + '<div class="label label-last-login">POP3 @ ' + unix_time_format(Number(res[1])) + '</div><br>' +
'<div class="label label-last-login">SMTP @ ' + unix_time_format(Number(res[2])) + '</div>'; '<div class="label label-last-login">SMTP @ ' + unix_time_format(Number(res[2])) + '</div>';
}}, }},
{"name":"last_pw_change","filterable": false,"title":lang.last_pw_change,"breakpoints":"all"},
{"name":"quarantine_notification","filterable": false,"title":lang.quarantine_notification,"breakpoints":"all"}, {"name":"quarantine_notification","filterable": false,"title":lang.quarantine_notification,"breakpoints":"all"},
{"name":"quarantine_category","filterable": false,"title":lang.quarantine_category,"breakpoints":"all"}, {"name":"quarantine_category","filterable": false,"title":lang.quarantine_category,"breakpoints":"all"},
{"name":"in_use","filterable": false,"type":"html","title":lang.in_use,"sortValue": function(value){ {"name":"in_use","filterable": false,"type":"html","title":lang.in_use,"sortValue": function(value){
@ -408,6 +409,12 @@ jQuery(function($){
} }
*/ */
item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />'; item.chkbox = '<input type="checkbox" data-id="mailbox" name="multi_select" value="' + encodeURIComponent(item.username) + '" />';
if (item.attributes.passwd_update != '0') {
var last_pw_change = new Date(item.attributes.passwd_update.replace(/-/g, "/"));
item.last_pw_change = last_pw_change.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
} else {
item.last_pw_change = '-';
}
item.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>'; item.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
item.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>'; item.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
item.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"></i>'; item.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';

View File

@ -72,17 +72,15 @@ jQuery(function($){
} }
acl_data = JSON.parse(acl); acl_data = JSON.parse(acl);
$('.clear-last-logins').on('click', function () { $('.clear-last-logins').on('click', function () {if (confirm(lang.delete_ays)) {last_logins('reset');}})
if (confirm(lang.delete_ays)) { $(".login-history").on('click', function(e) {e.preventDefault(); last_logins('get', $(this).data('days'));$(this).addClass('active').siblings().removeClass('active');});
last_logins('reset');
}
})
function last_logins(action, lines = 10) { function last_logins(action, days = 7) {
if (action == 'get') { if (action == 'get') {
$('.last-login').html('<i class="bi bi-hourglass"></i>' + lang.waiting);
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: '/api/v1/get/last-login/' + encodeURIComponent(mailcow_cc_username) + '/' + lines, url: '/api/v1/get/last-login/' + encodeURIComponent(mailcow_cc_username) + '/' + days,
jsonp: false, jsonp: false,
error: function () { error: function () {
console.log('error reading last logins'); console.log('error reading last logins');

View File

@ -641,6 +641,7 @@ if (isset($_GET['query'])) {
case "last-login": case "last-login":
if ($object) { if ($object) {
// extra == days
if (isset($extra) && intval($extra) >= 1) { if (isset($extra) && intval($extra) >= 1) {
$data = last_login('get', $object, intval($extra)); $data = last_login('get', $object, intval($extra));
} }

View File

@ -736,6 +736,7 @@
"insert_preset": "Beispiel \"%s\" laden", "insert_preset": "Beispiel \"%s\" laden",
"kind": "Art", "kind": "Art",
"last_mail_login": "Letzter Mail-Login", "last_mail_login": "Letzter Mail-Login",
"last_pw_change": "Letzte Passwortänderung",
"last_run": "Letzte Ausführung", "last_run": "Letzte Ausführung",
"last_run_reset": "Als nächstes ausführen", "last_run_reset": "Als nächstes ausführen",
"mailbox": "Mailbox", "mailbox": "Mailbox",
@ -1052,7 +1053,9 @@
"is_catch_all": "Ist Catch-All-Adresse für Domain(s)", "is_catch_all": "Ist Catch-All-Adresse für Domain(s)",
"last_mail_login": "Letzter Mail-Login", "last_mail_login": "Letzter Mail-Login",
"last_run": "Letzte Ausführung", "last_run": "Letzte Ausführung",
"last_pw_change": "Letzte Passwortänderung",
"last_ui_login": "Letzte UI Anmeldung", "last_ui_login": "Letzte UI Anmeldung",
"login_history": "Login-Historie",
"loading": "Lade...", "loading": "Lade...",
"mailbox_details": "Mailbox-Details", "mailbox_details": "Mailbox-Details",
"messages": "Nachrichten", "messages": "Nachrichten",

View File

@ -734,6 +734,7 @@
"insert_preset": "Insert example preset \"%s\"", "insert_preset": "Insert example preset \"%s\"",
"kind": "Kind", "kind": "Kind",
"last_mail_login": "Last mail login", "last_mail_login": "Last mail login",
"last_pw_change": "Last password change",
"last_run": "Last run", "last_run": "Last run",
"last_run_reset": "Schedule next", "last_run_reset": "Schedule next",
"mailbox": "Mailbox", "mailbox": "Mailbox",
@ -1050,8 +1051,10 @@
"is_catch_all": "Catch-all for domain/s", "is_catch_all": "Catch-all for domain/s",
"last_mail_login": "Last mail login", "last_mail_login": "Last mail login",
"last_run": "Last run", "last_run": "Last run",
"last_pw_change": "Last password change",
"last_ui_login": "Last UI login", "last_ui_login": "Last UI login",
"loading": "Loading...", "loading": "Loading...",
"login_history": "Login history",
"mailbox_details": "Mailbox details", "mailbox_details": "Mailbox details",
"messages": "messages", "messages": "messages",
"month": "month", "month": "month",

View File

@ -174,9 +174,21 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p> <p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p>
<p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p> <p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p>
<hr> <hr>
<h4><?=$lang['user']['recent_successful_connections'];?></h4> <h4 class="recent-login-success pull-left"><?=$lang['user']['recent_successful_connections'];?></h4>
<div class="last-login"><i class="bi bi-hourglass"></i> <?=$lang['user']['waiting'];?></div> <div class="dropdown pull-left">
<span class="clear-last-logins"><?=$lang['user']['clear_recent_successful_connections'];?></span> <button class="btn btn-default btn-xs dropdown-toggle" type="button" id="history_sasl_days" data-toggle="dropdown"><?=$lang['user']['login_history'];?> <span class="caret"></span></button>
<ul class="dropdown-menu">
<li class="login-history" data-days="1"><a href="#">1 <?=$lang['user']['day'];?></a></li>
<li class="login-history" data-days="7"><a href="#">1 <?=$lang['user']['week'];?></a></li>
<li class="login-history active" data-days="14"><a href="#">2 <?=$lang['user']['weeks'];?></a></li>
<li class="login-history" data-days="31"><a href="#">1 <?=$lang['user']['month'];?></a></li>
</ul>
</div>
<div class="clearfix"></div>
<div class="last-login"></div>
<span class="clear-last-logins">
<?=$lang['user']['clear_recent_successful_connections'];?>
</span>
</div> </div>
</div> </div>
<hr> <hr>