Comparison

plugins/mod_account_activity.lua @ 13665:30a91d819913 13.0

mod_account_activity: Record an account's last activity timestamp This is similar to mod_lastlog/mod_lastlog2. Some functionality was dropped, compared to mod_lastlog2. These features (recording the IP address, or tracking the timestamp of multiple events) are handled better by the mod_audit family of modules. For example, those correctly handle multiple logins, IP address truncation, and data retention policies. The "registered" timestamp from mod_lastlog2 was also dropped, as this has been stored in account_details by Prosody itself since at least 0.12 already.
author Matthew Wild <mwild1@gmail.com>
date Wed, 12 Feb 2025 12:33:45 +0000
child 13667:3052eae50c98
comparison
equal deleted inserted replaced
13664:5528bc5ab019 13665:30a91d819913
1 local jid = require "util.jid";
2 local time = os.time;
3
4 local store = module:open_store(nil, "keyval+");
5
6 module:hook("authentication-success", function(event)
7 local session = event.session;
8 if session.username then
9 store:set_key(session.username, "timestamp", time());
10 end
11 end);
12
13 module:hook("resource-unbind", function(event)
14 local session = event.session;
15 if session.username then
16 store:set_key(session.username, "timestamp", time());
17 end
18 end);
19
20 local user_sessions = prosody.hosts[module.host].sessions;
21 function get_last_active(username) --luacheck: ignore 131/get_last_active
22 if user_sessions[username] then
23 return os.time(), true; -- Currently connected
24 else
25 local last_activity = store:get(username);
26 if not last_activity then return nil; end
27 return last_activity.timestamp;
28 end
29 end
30
31 module:add_item("shell-command", {
32 section = "user";
33 section_desc = "View user activity data";
34 name = "activity";
35 desc = "View the last recorded user activity for an account";
36 args = { { name = "jid"; type = "string" } };
37 host_selector = "jid";
38 handler = function(self, userjid) --luacheck: ignore 212/self
39 local username = jid.prepped_split(userjid);
40 local last_timestamp, is_online = get_last_active(username);
41 if not last_timestamp then
42 return true, "No activity";
43 end
44
45 return true, ("%s (%s)"):format(os.date("%Y-%m-%d %H:%M:%S", last_timestamp), (is_online and "online" or "offline"));
46 end;
47 });
48
49 module:add_item("shell-command", {
50 section = "migrate";
51 section_desc = "Perform data migrations";
52 name = "account_activity_lastlog2";
53 desc = "Migrate account activity information from mod_lastlog2";
54 args = { { name = "host"; type = "string" } };
55 host_selector = "host";
56 handler = function(self, host) --luacheck: ignore 212/host
57 local lastlog2 = module:open_store("lastlog2", "keyval+");
58 local n_updated, n_errors, n_skipped = 0, 0, 0;
59
60 local async = require "util.async";
61
62 local p = require "util.promise".new(function (resolve)
63 local async_runner = async.runner(function ()
64 local n = 0;
65 for username in lastlog2:items() do
66 n = n + 1;
67 if n % 100 == 0 then
68 self.session.print(("Processed %d..."):format(n));
69 async.sleep(0);
70 end
71 local lastlog2_data = lastlog2:get(username);
72 if lastlog2_data then
73 local current_data, err = store:get(username);
74 if not current_data then
75 if not err then
76 current_data = {};
77 else
78 n_errors = n_errors + 1;
79 end
80 end
81 if current_data then
82 local imported_timestamp = current_data.timestamp;
83 local latest;
84 for k, v in pairs(lastlog2_data) do
85 if k ~= "registered" and (not latest or v.timestamp > latest) then
86 latest = v.timestamp;
87 end
88 end
89 if latest and (not imported_timestamp or imported_timestamp < latest) then
90 local ok, err = store:set_key(username, "timestamp", latest);
91 if ok then
92 n_updated = n_updated + 1;
93 else
94 self.session.print(("WW: Failed to import %q: %s"):format(username, err));
95 n_errors = n_errors + 1;
96 end
97 else
98 n_skipped = n_skipped + 1;
99 end
100 end
101 end
102 end
103 return resolve(("%d accounts imported, %d errors, %d skipped"):format(n_updated, n_errors, n_skipped));
104 end);
105 async_runner:run(true);
106 end);
107 return p;
108 end;
109 });