From 3aeca398e219d6c14f0ea9e67996771765b63833 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Mon, 27 Apr 2026 10:48:12 -0600 Subject: [PATCH] last-login: Add abstracted way to update custom fields --- data/settings.js | 14 ++++++++++- data/updates.js | 1 + docs/core/plugins/last_login.md | 42 ++++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/data/settings.js b/data/settings.js index 1304a03c3..6aab18c3f 100644 --- a/data/settings.js +++ b/data/settings.js @@ -2600,7 +2600,7 @@ last_login { plugin: 'last-login', values: setting_types.STRING, text: ` -The key that is updated in the dictionary with the last login information.` +The key that is updated in the dictionary with the last login timestamp.` }, last_login_precision: { @@ -2611,6 +2611,18 @@ The key that is updated in the dictionary with the last login information.` text: `Precision for last login timestamp.` }, + last_login_dict_fields: { + default: '', + added: { + settings_last_login_dict_fields_added: false + }, + plugin: 'last-login', + values: setting_types.STRING, + text: ` +Space-separated list of additional \`key=value\` pairs to be updated in the +dictionary. Both keys and values support variable expansion.` + }, + /* lazy-expunge plugin */ lazy_expunge_mailbox: { diff --git a/data/updates.js b/data/updates.js index a4b0ca604..77e31669a 100644 --- a/data/updates.js +++ b/data/updates.js @@ -155,6 +155,7 @@ export const updates = { settings_imapc_ssl_verify_removed: '2.4.0', settings_inet_listener_type_added: '2.4.0', settings_lazy_expunge_only_last_instance_changed: '2.4.3', + settings_last_login_dict_fields_added: '2.4.X', settings_login_socket_path_added: '2.4.0', settings_lmtp_user_concurrency_limit_changed: '2.4.1', settings_mail_access_groups_changed: '2.4.3', diff --git a/docs/core/plugins/last_login.md b/docs/core/plugins/last_login.md index 46566bccc..2541b8e77 100644 --- a/docs/core/plugins/last_login.md +++ b/docs/core/plugins/last_login.md @@ -60,7 +60,9 @@ to include `%{service}`. ### MySQL Example -This includes the service and remote IP address as well. +This includes the service and remote IP address as well. Using +`last_login_dict_fields` ensures that both the timestamp and the IP address are +updated if a login record for that user/service already exists. ::: code-group @@ -69,7 +71,8 @@ last_login { dict proxy { name = sql } - key = last-login/%{service}/%{user}/%{remote_ip} + key = last-login/%{service}/%{user}/access + last_login_dict_fields = last-login/%{service}/%{user}/ip=%{remote_ip} precision = ms } @@ -82,7 +85,7 @@ dict_server { password = pass } - dict_map shared/last-login/$service/$user/$remote_ip { + dict_map shared/last-login/$service/$user/access { sql_table = last_login value_field last_access { type = uint @@ -94,8 +97,18 @@ dict_server { key_field service { value = $service } - key_field last_ip { - value = $remote_ip + } + + dict_map shared/last-login/$service/$user/ip { + sql_table = last_login + value_field last_ip { + } + + key_field userid { + value = $user + } + key_field service { + value = $service } } } @@ -125,7 +138,8 @@ last_login { name = cassandra socket_path = dict-async } - key = last-login/%{service}/%{user}/%{remote_ip} + key = last-login/%{service}/%{user}/access + last_login_dict_fields = last-login/%{service}/%{user}/ip=%{remote_ip} precision = ms } @@ -140,7 +154,7 @@ dict_server { password = pass } - dict_map shared/last-login/$service/$user/$remote_ip { + dict_map shared/last-login/$service/$user/access { sql_table = last_login value_field last_access { type = uint @@ -152,8 +166,18 @@ dict_server { key_field service { value = $service } - key_field last_ip { - value = $remote_ip + } + + dict_map shared/last-login/$service/$user/ip { + sql_table = last_login + value_field last_ip { + } + + key_field userid { + value = $user + } + key_field service { + value = $service } } }