Software / code / prosody
File
plugins/mod_account_activity.lua @ 13834:61df1404dd7a 13.0
mod_http: Fix IP address normalization (Thanks Boris)
This fixes the problem that an un-bracketed IPv6 address will not match
the first pattern (since it matches brackets) and instead the first
decimal digits will match the pattern meant to strip port numbers from
IPv4 addresses, e.g. 2001:db8::1 --> 2000
This pattern instead matches enough of a regular IPv4 address to make an
IPv6 address fall back to the last case.
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Wed, 09 Apr 2025 15:54:54 +0200 |
| parent | 13682:0055c177a54c |
line wrap: on
line source
local jid = require "prosody.util.jid"; local time = os.time; local store = module:open_store(nil, "keyval+"); module:hook("authentication-success", function(event) local session = event.session; if session.username then store:set_key(session.username, "timestamp", time()); end end); module:hook("resource-unbind", function(event) local session = event.session; if session.username then store:set_key(session.username, "timestamp", time()); end end); local user_sessions = prosody.hosts[module.host].sessions; function get_last_active(username) --luacheck: ignore 131/get_last_active if user_sessions[username] then return os.time(), true; -- Currently connected else local last_activity = store:get(username); if not last_activity then return nil; end return last_activity.timestamp; end end module:add_item("shell-command", { section = "user"; section_desc = "View user activity data"; name = "activity"; desc = "View the last recorded user activity for an account"; args = { { name = "jid"; type = "string" } }; host_selector = "jid"; handler = function(self, userjid) --luacheck: ignore 212/self local username = jid.prepped_split(userjid); local last_timestamp, is_online = get_last_active(username); if not last_timestamp then return true, "No activity"; end return true, ("%s (%s)"):format(os.date("%Y-%m-%d %H:%M:%S", last_timestamp), (is_online and "online" or "offline")); end; }); module:add_item("shell-command", { section = "user"; section_desc = "View user activity data"; name = "list_inactive"; desc = "List inactive user accounts"; args = { { name = "host"; type = "string" }; { name = "duration"; type = "string" }; }; host_selector = "host"; handler = function(self, host, duration) --luacheck: ignore 212/self local um = require "prosody.core.usermanager"; local duration_sec = require "prosody.util.human.io".parse_duration(duration or ""); if not duration_sec then return false, ("Invalid duration %q - try something like \"30d\""):format(duration); end local now = os.time(); local n_inactive, n_unknown = 0, 0; for username in um.users(host) do local last_active = store:get_key(username, "timestamp"); if not last_active then local created_at = um.get_account_info(username, host).created; if created_at and (now - created_at) > duration_sec then self.session.print(username, ""); n_inactive = n_inactive + 1; elseif not created_at then n_unknown = n_unknown + 1; end elseif (now - last_active) > duration_sec then self.session.print(username, os.date("%Y-%m-%dT%T", last_active)); n_inactive = n_inactive + 1; end end if n_unknown > 0 then return true, ("%d accounts inactive since %s (%d unknown)"):format(n_inactive, os.date("%Y-%m-%dT%T", now - duration_sec), n_unknown); end return true, ("%d accounts inactive since %s"):format(n_inactive, os.date("%Y-%m-%dT%T", now - duration_sec)); end; }); module:add_item("shell-command", { section = "migrate"; section_desc = "Perform data migrations"; name = "account_activity_lastlog2"; desc = "Migrate account activity information from mod_lastlog2"; args = { { name = "host"; type = "string" } }; host_selector = "host"; handler = function(self, host) --luacheck: ignore 212/host local lastlog2 = module:open_store("lastlog2", "keyval+"); local n_updated, n_errors, n_skipped = 0, 0, 0; local async = require "prosody.util.async"; local p = require "prosody.util.promise".new(function (resolve) local async_runner = async.runner(function () local n = 0; for username in lastlog2:items() do n = n + 1; if n % 100 == 0 then self.session.print(("Processed %d..."):format(n)); async.sleep(0); end local lastlog2_data = lastlog2:get(username); if lastlog2_data then local current_data, err = store:get(username); if not current_data then if not err then current_data = {}; else n_errors = n_errors + 1; end end if current_data then local imported_timestamp = current_data.timestamp; local latest; for k, v in pairs(lastlog2_data) do if k ~= "registered" and (not latest or v.timestamp > latest) then latest = v.timestamp; end end if latest and (not imported_timestamp or imported_timestamp < latest) then local ok, err = store:set_key(username, "timestamp", latest); if ok then n_updated = n_updated + 1; else self.session.print(("WW: Failed to import %q: %s"):format(username, err)); n_errors = n_errors + 1; end else n_skipped = n_skipped + 1; end end end end return resolve(("%d accounts imported, %d errors, %d skipped"):format(n_updated, n_errors, n_skipped)); end); async_runner:run(true); end); return p; end; });