public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[Web] Show users last PW change, allow to select n days for last logins
47b57df3
data/web/css/site/mailbox.css | 3 ++-
data/web/css/site/user.css | 4 ++++
data/web/inc/functions.inc.php | 10 +++++-----
data/web/inc/prerequisites.inc.php | 21 ++------------------
data/web/inc/vars.inc.php | 39 ++++++++++++++++++--------------------
data/web/js/site/mailbox.js | 7 +++++++
data/web/js/site/user.js | 12 +++++-------
data/web/json_api.php | 1 +
data/web/lang/lang.de.json | 3 +++
data/web/lang/lang.en.json | 3 +++
data/web/user.php | 18 +++++++++++++++---
11 files changed, 65 insertions(+), 56 deletions(-)
Diff
diff --git a/data/web/css/site/mailbox.css b/data/web/css/site/mailbox.css
index 81e1ff3f..2467ea33 100644
--- a/data/web/css/site/mailbox.css
+++ b/data/web/css/site/mailbox.css
@@ -57,8 +57,9 @@ table tbody tr td input[type="checkbox"] {
font-size: 8pt !important;
}
.label-last-login {
- line-height: 2.5;
+ line-height: 2.2;
color: #4a4a4a!important;
+ padding: .2em .4em .3em !important;
background-color: #ececec!important;
}
diff --git a/data/web/css/site/user.css b/data/web/css/site/user.css
index 246453c1..bfbca47c 100644
--- a/data/web/css/site/user.css
+++ b/data/web/css/site/user.css
@@ -125,4 +125,8 @@ border-bottom-width: 3px;
}
.xmpp-logo-user {
width:64px;
+}
+.recent-login-success {
+ margin-top:2px;
+ margin-right:10px;
}
\ No newline at end of file
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 0beea3b6..7854acd6 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -251,21 +251,21 @@ function password_check($password1, $password2) {
return true;
}
-function last_login($action, $username, $sasl_limit = 10) {
+function last_login($action, $username, $sasl_limit_days = 7) {
global $pdo;
global $redis;
- $sasl_limit = intval($sasl_limit);
+ $sasl_limit_days = intval($sasl_limit_days);
switch ($action) {
case 'get':
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`
LEFT OUTER JOIN `app_passwd` on `sasl_logs`.`app_password` = `app_passwd`.`id`
WHERE `username` = :username
+ AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days
AND `success` = 1
GROUP BY `real_rip`, `service`, `app_password`
- ORDER BY `datetime` DESC
- LIMIT :sasl_limit;');
- $stmt->execute(array(':username' => $username, ':sasl_limit' => $sasl_limit));
+ ORDER BY `datetime` DESC;');
+ $stmt->execute(array(':username' => $username, ':sasl_limit_days' => ($sasl_limit_days * 24)));
$sasl = $stmt->fetchAll(PDO::FETCH_ASSOC);
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)) {
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index 7e9a24f3..5baa81d8 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -172,29 +172,12 @@ function exception_handler($e) {
set_exception_handler('exception_handler');
// TODO: Move function
-function get_remote_ip($anonymize = null) {
- global $ANONYMIZE_IPS;
- if ($anonymize === null) {
- $anonymize = $ANONYMIZE_IPS;
- }
- elseif ($anonymize !== true && $anonymize !== false) {
- $anonymize = true;
- }
+function get_remote_ip() {
$remote = $_SERVER['REMOTE_ADDR'];
if (filter_var($remote, FILTER_VALIDATE_IP) === false) {
return '0.0.0.0';
}
- if ($anonymize) {
- 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;
- }
+ return $remote;
}
// Load core functions first
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
index 1c3a2392..c390317f 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -82,24 +82,24 @@ $DEFAULT_LANG = 'en';
// https://www.iso.org/obp/ui/#search
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
$AVAILABLE_LANGUAGES = array(
- 'cs' => 'Čeština (Czech)',
- 'da' => 'Danish (Dansk)',
- 'de' => 'Deutsch (German)',
- 'en' => 'English',
- 'es' => 'Español (Spanish)',
- 'fi' => 'Suomi (Finish)',
- 'fr' => 'Français (French)',
- 'hu' => 'Magyar (Hungarian)',
- 'it' => 'Italiano (Italian)',
- 'ko' => '한국어 (Korean)',
- 'lv' => 'latviešu (Latvian)',
- 'nl' => 'Nederlands (Dutch)',
- 'pl' => 'Język Polski (Polish)',
- 'pt' => 'Português (Portuguese)',
- 'ro' => 'Română (Romanian)',
- 'ru' => 'Pусский (Russian)',
- 'sk' => 'Slovenčina (Slovak)',
- 'sv' => 'Svenska (Swedish)',
+ 'cs' => 'Čeština (Czech)',
+ 'da' => 'Danish (Dansk)',
+ 'de' => 'Deutsch (German)',
+ 'en' => 'English',
+ 'es' => 'Español (Spanish)',
+ 'fi' => 'Suomi (Finish)',
+ 'fr' => 'Français (French)',
+ 'hu' => 'Magyar (Hungarian)',
+ 'it' => 'Italiano (Italian)',
+ 'ko' => '한국어 (Korean)',
+ 'lv' => 'latviešu (Latvian)',
+ 'nl' => 'Nederlands (Dutch)',
+ 'pl' => 'Język Polski (Polish)',
+ 'pt' => 'Português (Portuguese)',
+ 'ro' => 'Română (Romanian)',
+ 'ru' => 'Pусский (Russian)',
+ 'sk' => 'Slovenčina (Slovak)',
+ 'sv' => 'Svenska (Swedish)',
'zh' => '中文 (Chinese)'
);
@@ -139,9 +139,6 @@ $OTP_LABEL = "mailcow UI";
// How long to wait (in s) for cURL Docker requests
$DOCKER_TIMEOUT = 60;
-// Anonymize IPs logged via UI
-$ANONYMIZE_IPS = true;
-
// Split DKIM key notation (bind format)
$SPLIT_DKIM_255 = false;
diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js
index d008b478..0a6d7ece 100644
--- a/data/web/js/site/mailbox.js
+++ b/data/web/js/site/mailbox.js
@@ -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">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_category","filterable": false,"title":lang.quarantine_category,"breakpoints":"all"},
{"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) + '" />';
+ 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_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>';
diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js
index 3b3956f1..88712d57 100644
--- a/data/web/js/site/user.js
+++ b/data/web/js/site/user.js
@@ -72,17 +72,15 @@ jQuery(function($){
}
acl_data = JSON.parse(acl);
- $('.clear-last-logins').on('click', function () {
- if (confirm(lang.delete_ays)) {
- last_logins('reset');
- }
- })
+ $('.clear-last-logins').on('click', function () {if (confirm(lang.delete_ays)) {last_logins('reset');}})
+ $(".login-history").on('click', function(e) {e.preventDefault(); last_logins('get', $(this).data('days'));$(this).addClass('active').siblings().removeClass('active');});
- function last_logins(action, lines = 10) {
+ function last_logins(action, days = 7) {
if (action == 'get') {
+ $('.last-login').html('<i class="bi bi-hourglass"></i>' + lang.waiting);
$.ajax({
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,
error: function () {
console.log('error reading last logins');
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 3db775a4..ad4d9dbe 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -641,6 +641,7 @@ if (isset($_GET['query'])) {
case "last-login":
if ($object) {
+ // extra == days
if (isset($extra) && intval($extra) >= 1) {
$data = last_login('get', $object, intval($extra));
}
diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json
index a35ecf82..4f7f6065 100644
--- a/data/web/lang/lang.de.json
+++ b/data/web/lang/lang.de.json
@@ -736,6 +736,7 @@
"insert_preset": "Beispiel \"%s\" laden",
"kind": "Art",
"last_mail_login": "Letzter Mail-Login",
+ "last_pw_change": "Letzte Passwortänderung",
"last_run": "Letzte Ausführung",
"last_run_reset": "Als nächstes ausführen",
"mailbox": "Mailbox",
@@ -1052,7 +1053,9 @@
"is_catch_all": "Ist Catch-All-Adresse für Domain(s)",
"last_mail_login": "Letzter Mail-Login",
"last_run": "Letzte Ausführung",
+ "last_pw_change": "Letzte Passwortänderung",
"last_ui_login": "Letzte UI Anmeldung",
+ "login_history": "Login-Historie",
"loading": "Lade...",
"mailbox_details": "Mailbox-Details",
"messages": "Nachrichten",
diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json
index fc015299..a3c6e429 100644
--- a/data/web/lang/lang.en.json
+++ b/data/web/lang/lang.en.json
@@ -734,6 +734,7 @@
"insert_preset": "Insert example preset \"%s\"",
"kind": "Kind",
"last_mail_login": "Last mail login",
+ "last_pw_change": "Last password change",
"last_run": "Last run",
"last_run_reset": "Schedule next",
"mailbox": "Mailbox",
@@ -1050,8 +1051,10 @@
"is_catch_all": "Catch-all for domain/s",
"last_mail_login": "Last mail login",
"last_run": "Last run",
+ "last_pw_change": "Last password change",
"last_ui_login": "Last UI login",
"loading": "Loading...",
+ "login_history": "Login history",
"mailbox_details": "Mailbox details",
"messages": "messages",
"month": "month",
diff --git a/data/web/user.php b/data/web/user.php
index bd7123d5..f9972fe0 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -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 href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p>
<hr>
- <h4><?=$lang['user']['recent_successful_connections'];?></h4>
- <div class="last-login"><i class="bi bi-hourglass"></i> <?=$lang['user']['waiting'];?></div>
- <span class="clear-last-logins"><?=$lang['user']['clear_recent_successful_connections'];?></span>
+ <h4 class="recent-login-success pull-left"><?=$lang['user']['recent_successful_connections'];?></h4>
+ <div class="dropdown pull-left">
+ <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>
<hr>