Software /
code /
prosody-modules
Changeset
6208:e20901443eae draft
Merge
line wrap: on
line diff
--- a/.luacheckrc Wed Feb 26 19:36:35 2025 +0700 +++ b/.luacheckrc Mon Mar 17 23:42:11 2025 +0700 @@ -29,6 +29,7 @@ "module.hourly", "module.broadcast", "module.context", + "module.could", "module.default_permission", "module.default_permissions", "module.depends", @@ -83,6 +84,7 @@ } globals = { -- Methods that can be set on module API + "module.ready", "module.unload", "module.add_host", "module.load",
--- a/misc/lnav/prosody.json Wed Feb 26 19:36:35 2025 +0700 +++ b/misc/lnav/prosody.json Mon Mar 17 23:42:11 2025 +0700 @@ -33,7 +33,7 @@ "identifier" : true, "kind" : "string" }, - "payload" : { + "message" : { "kind" : "xml" } }
--- a/mod_anti_spam/mod_anti_spam.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_anti_spam/mod_anti_spam.lua Mon Mar 17 23:42:11 2025 +0700 @@ -4,7 +4,7 @@ local set = require "util.set"; local sha256 = require "util.hashes".sha256; local st = require"util.stanza"; -local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; +local rm = require "core.rostermanager"; local full_sessions = prosody.full_sessions; local user_exists = require "core.usermanager".user_exists; @@ -15,10 +15,30 @@ local spam_source_domains = set.new(); local spam_source_ips = trie.new(); local spam_source_jids = set.new(); +local default_spam_action = module:get_option("anti_spam_default_action", "bounce"); +local custom_spam_actions = module:get_option("anti_spam_actions", {}); + +local spam_actions = setmetatable({}, { + __index = function (t, reason) + local action = rawget(custom_spam_actions, reason) or default_spam_action; + rawset(t, reason, action); + return action; + end; +}); local count_spam_blocked = module:metric("counter", "anti_spam_blocked", "stanzas", "Stanzas blocked as spam", {"reason"}); +local hosts = prosody.hosts; + +local reason_messages = { + default = "Rejected as spam"; + ["known-spam-source"] = "Rejected as spam. Your server is listed as a known source of spam. Please contact your server operator."; +}; + function block_spam(event, reason, action) + if not action then + action = spam_actions[reason]; + end event.spam_reason = reason; event.spam_action = action; if module:fire_event("spam-blocked", event) == false then @@ -30,7 +50,7 @@ if action == "bounce" then module:log("debug", "Bouncing likely spam %s from %s (%s)", event.stanza.name, event.stanza.attr.from, reason); - event.origin.send(st.error_reply("cancel", "policy-violation", "Rejected as spam")); + event.origin.send(st.error_reply(event.stanza, "cancel", "policy-violation", reason_messages[reason] or reason_messages.default)); else module:log("debug", "Discarding likely spam %s from %s (%s)", event.stanza.name, event.stanza.attr.from, reason); end @@ -47,13 +67,25 @@ local to_session = full_sessions[stanza.attr.to]; if to_session then return false; end - if not is_contact_subscribed(to_user, to_host, from_jid) then + if not ( + rm.is_contact_subscribed(to_user, to_host, from_jid) or + rm.is_user_subscribed(to_user, to_host, from_jid) or + rm.is_contact_pending_out(to_user, to_host, from_jid) or + rm.is_contact_preapproved(to_user, to_host, from_jid) + ) then + local from_user, from_host = jid_split(from_jid); + -- Allow all messages from your own jid - if from_jid == to_user.."@"..to_host then + if from_user == to_user and from_host == to_host then return false; -- Pass through end if to_resource and stanza.attr.type == "groupchat" then - return false; -- Pass through + return false; -- Pass through group chat messages + end + if rm.is_contact_subscribed(to_user, to_host, from_host) then + -- If you have the sending domain in your roster, + -- allow through (probably a gateway) + return false; end return true; -- Stranger danger end @@ -63,8 +95,11 @@ if spam_source_domains:contains(session.from_host) then return true; end - local origin_ip = ip.new(session.ip); - if spam_source_ips:contains_ip(origin_ip) then + local raw_ip = session.ip; + local parsed_ip = raw_ip and ip.new_ip(session.ip); + -- Not every session has an ip - for example, stanzas sent from a + -- local host session + if parsed_ip and spam_source_ips:contains_ip(parsed_ip) then return true; end end @@ -82,6 +117,8 @@ if not (spammy_strings or spammy_patterns) then return; end local body = stanza:get_child_text("body"); + if not body then return; end + if spammy_strings then for _, s in ipairs(spammy_strings) do if body:find(s, 1, true) then @@ -100,7 +137,7 @@ -- Set up RTBLs -local anti_spam_services = module:get_option_array("anti_spam_services"); +local anti_spam_services = module:get_option_array("anti_spam_services", {}); for _, rtbl_service_jid in ipairs(anti_spam_services) do new_rtbl_subscription(rtbl_service_jid, "spam_source_domains", { @@ -113,10 +150,18 @@ }); new_rtbl_subscription(rtbl_service_jid, "spam_source_ips", { added = function (item) - spam_source_ips:add_subnet(ip.parse_cidr(item)); + local subnet_ip, subnet_bits = ip.parse_cidr(item); + if not subnet_ip then + return; + end + spam_source_ips:add_subnet(subnet_ip, subnet_bits); end; removed = function (item) - spam_source_ips:remove_subnet(ip.parse_cidr(item)); + local subnet_ip, subnet_bits = ip.parse_cidr(item); + if not subnet_ip then + return; + end + spam_source_ips:remove_subnet(subnet_ip, subnet_bits); end; }); new_rtbl_subscription(rtbl_service_jid, "spam_source_jids_sha256", { @@ -130,36 +175,65 @@ end module:hook("message/bare", function (event) - local to_bare = jid_bare(event.stanza.attr.to); + local to_user, to_host = jid_split(event.stanza.attr.to); - if not user_exists(to_bare) then return; end + if not hosts[to_host] then + module:log("warn", "Skipping filtering of message to unknown host <%s>", to_host); + return; + end local from_bare = jid_bare(event.stanza.attr.from); - if not is_from_stranger(from_bare, event) then return; end + if user_exists(to_user, to_host) then + if not is_from_stranger(from_bare, event) then + return; + end + end + + module:log("debug", "Processing message from stranger..."); if is_spammy_server(event.origin) then - return block_spam(event, "known-spam-source", "drop"); + return block_spam(event, "known-spam-source"); end if is_spammy_sender(from_bare) then - return block_spam(event, "known-spam-jid", "drop"); + return block_spam(event, "known-spam-jid"); end if is_spammy_content(event.stanza) then - return block_spam(event, "spam-content", "drop"); + return block_spam(event, "spam-content"); end + + module:log("debug", "Allowing message through"); end, 500); module:hook("presence/bare", function (event) - if event.stanza.type ~= "subscribe" then + if event.stanza.attr.type ~= "subscribe" then return; end - if is_spammy_server(event.origin) then - return block_spam(event, "known-spam-source", "drop"); + + local to_user, to_host = jid_split(event.stanza.attr.to); + local from_bare = jid_bare(event.stanza.attr.from); + + if user_exists(to_user, to_host) then + if not is_from_stranger(from_bare, event) then + return; + end end - if is_spammy_sender(event.stanza) then - return block_spam(event, "known-spam-jid", "drop"); + module:log("debug", "Processing subscription request from stranger..."); + + if is_spammy_server(event.origin) then + return block_spam(event, "known-spam-source"); end + + module:log("debug", "Not from known spam source server"); + + if is_spammy_sender(jid_bare(event.stanza.attr.from)) then + return block_spam(event, "known-spam-jid"); + end + + module:log("debug", "Not from known spam source JID"); + + module:log("debug", "Allowing subscription request through"); end, 500);
--- a/mod_anti_spam/trie.lib.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_anti_spam/trie.lib.lua Mon Mar 17 23:42:11 2025 +0700 @@ -1,4 +1,4 @@ -local bit = require "prosody.util.bitcompat"; +local bit = require "util.bitcompat"; local trie_methods = {}; local trie_mt = { __index = trie_methods }; @@ -120,7 +120,7 @@ end end -function trie_methods:has_ip(item) +function trie_methods:contains_ip(item) item = item.packed; local node = self.root; local len = #item;
--- a/mod_audit/README.md Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_audit/README.md Mon Mar 17 23:42:11 2025 +0700 @@ -49,3 +49,9 @@ ```shell prosodyctl mod_audit user@example.com ``` + +# Compatibilty + +Requires Prosody **trunk** as of 2025-02-11. + +Does not work with Prosody 0.12 or earlier.
--- a/mod_auth_oauth_external/README.md Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_auth_oauth_external/README.md Mon Mar 17 23:42:11 2025 +0700 @@ -11,12 +11,14 @@ # How it works -Clients retrieve tokens somehow, then show them to Prosody, which asks -the Authorization server to validate them, returning info about the user -back to Prosody. +Using OAuth 2.0 in XMPP is explained in [XEP-0493: OAuth Client Login]. +Clients pass tokens from the Authorization Server to Prosody, which +attempts to validate the tokens using the configured validation +endpoint. -Alternatively for legacy clients, Prosody receives the users username -and password and retrieves a token itself, then proceeds as above. +Legacy clients have to use SASL PLAIN, where Prosody receives the users +username and password and attempts to validate this using the OAuth 2 +resource owner password grant. # Configuration
--- a/mod_auth_oauth_external/mod_auth_oauth_external.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_auth_oauth_external/mod_auth_oauth_external.lua Mon Mar 17 23:42:11 2025 +0700 @@ -58,7 +58,7 @@ function provider.get_sasl_handler() local profile = {}; - profile.http_client = http.new({ connection_pooling = true }); -- TODO configurable + profile.http_client = http.default:new({ connection_pooling = true }); -- TODO configurable local extra = { oidc_discovery_url = oidc_discovery_url }; if token_endpoint and allow_plain then local map_username = function (username, _realm) return username; end; --jid.join; -- TODO configurable
--- a/mod_client_certs/mod_client_certs.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_client_certs/mod_client_certs.lua Mon Mar 17 23:42:11 2025 +0700 @@ -10,7 +10,7 @@ local dm_load = require "util.datamanager".load; local dm_store = require "util.datamanager".store; local dm_table = "client_certs"; -local ssl_x509 = require "ssl.x509"; +local ssl = require "ssl"; local util_x509 = require "util.x509"; local id_on_xmppAddr = "1.3.6.1.5.5.7.8.5"; local id_ce_subjectAltName = "2.5.29.17"; @@ -141,7 +141,7 @@ local can_manage = append:get_child("no-cert-management", xmlns_saslcert) ~= nil; x509cert = x509cert:gsub("^%s*(.-)%s*$", "%1"); - local cert = ssl_x509.load(util_x509.der2pem(base64.decode(x509cert))); + local cert = ssl.loadcertificate(util_x509.der2pem(base64.decode(x509cert))); if not cert then origin.send(st.error_reply(stanza, "modify", "not-acceptable", "Could not parse X.509 certificate")); @@ -206,8 +206,8 @@ instructions = "What action do you want to perform?"; { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/certs#subcmd" }; - { name = "subcmd", type = "list-single", label = "Actions", required = true, - value = { {label = "Add certificate", value = "add"}, + { name = "subcmd", type = "list-single", label = "Actions", required = false, + options = { {label = "Add certificate", value = "add"}, {label = "List certificates", value = "list"}, {label = "Disable certificate", value = "disable"}, {label = "Revoke certificate", value = "revoke"}, @@ -292,7 +292,7 @@ local name = fields.name; local x509cert = fields.cert:gsub("^%s*(.-)%s*$", "%1"); - local cert = ssl_x509.load(util_x509.der2pem(base64.decode(x509cert))); + local cert = ssl.loadcertificate(x509cert); if not cert then return { status = "completed", error = { message = "Could not parse X.509 certificate" } }; @@ -327,7 +327,7 @@ end end -local cmd_desc = adhoc_new("Manage certificates", "http://prosody.im/protocol/certs", adhoc_handler, "user"); +local cmd_desc = adhoc_new("Manage certificates", "http://prosody.im/protocol/certs", adhoc_handler, "any"); module:provides("adhoc", cmd_desc); -- Here comes the SASL EXTERNAL stuff
--- a/mod_cloud_notify/mod_cloud_notify.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_cloud_notify/mod_cloud_notify.lua Mon Mar 17 23:42:11 2025 +0700 @@ -4,6 +4,9 @@ -- -- This file is MIT/X11 licensed. +-- This module is only for 0.12, later versions have mod_cloud_notify bundled +--% conflicts: mod_cloud_notify + local os_time = os.time; local st = require"util.stanza"; local jid = require"util.jid";
--- a/mod_cloud_notify_encrypted/README.md Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_cloud_notify_encrypted/README.md Mon Mar 17 23:42:11 2025 +0700 @@ -2,9 +2,6 @@ labels: - 'Stage-Alpha' summary: 'Support for encrypted payloads in push notifications' -rockspec: - dependencies: - - mod_cloud_notify ... Introduction
--- a/mod_cloud_notify_encrypted/mod_cloud_notify_encrypted.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_cloud_notify_encrypted/mod_cloud_notify_encrypted.lua Mon Mar 17 23:42:11 2025 +0700 @@ -144,5 +144,6 @@ module:log("debug", "Encrypted '%s' push notification using %s", push_payload.type, encryption.algorithm); end +module:depends("cloud_notify"); module:hook("cloud_notify/registration", handle_register); module:hook("cloud_notify/push", handle_push, 1);
--- a/mod_cloud_notify_filters/README.md Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_cloud_notify_filters/README.md Mon Mar 17 23:42:11 2025 +0700 @@ -2,9 +2,6 @@ labels: - 'Stage-Alpha' summary: 'Support for push notification filtering rules' -rockspec: - dependencies: - - mod_cloud_notify ... Introduction
--- a/mod_cloud_notify_priority_tag/README.md Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_cloud_notify_priority_tag/README.md Mon Mar 17 23:42:11 2025 +0700 @@ -2,9 +2,6 @@ labels: - 'Stage-Alpha' summary: 'Support for indicating importance to push notification servers' -rockspec: - dependencies: - - mod_cloud_notify ... Introduction
--- a/mod_conversejs/mod_conversejs.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_conversejs/mod_conversejs.lua Mon Mar 17 23:42:11 2025 +0700 @@ -126,7 +126,7 @@ module:provides("http", { title = "Converse.js"; route = { - GET = function (event) + ["GET /"] = function (event) local converse_options = get_converse_options(); event.response.headers.content_type = "text/html"; @@ -175,7 +175,7 @@ sizes = "512x512", }, }), - start_url = module:http_url(), + start_url = module:http_url().."/", background_color = pwa_color, display = "standalone", scope = module:http_url().."/",
--- a/mod_csi_muc_priorities/mod_csi_muc_priorities.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_csi_muc_priorities/mod_csi_muc_priorities.lua Mon Mar 17 23:42:11 2025 +0700 @@ -12,14 +12,6 @@ local username = session.username; local priorities = user_sessions[username].csi_muc_priorities; - if priorities then - local priority = priorities[room_jid]; - if priority ~= nil then - event.reason = "muc priority"; - return priority; - end - end - -- Look for mention local rooms = session.rooms_joined; if rooms then @@ -33,12 +25,33 @@ end -- Your own messages if stanza.attr.from == (room_jid .. "/" .. room_nick) then - event.reason = "muc own message"; + event.reason = "muc own message"; return true; end end end + -- No mentions found, check other logic: + -- deflaultlow=f or nil defaultlow=t + -- in high prio nil nil + -- in low prio false false + -- not in either nil false + -- + -- true means: important (always send immediately) + -- nil means: normal (respect other mods for stuff like grace period/reactions/etc) + -- false means: unimportant (delay sending) + if priorities then + local priority = priorities[room_jid]; + if priority == false then -- low priority + event.reason = "muc priority"; + return false; + end + if priorities[false] and priorities[false]["defaultlow"] and not priority then -- defaultlow is false or nil or not high priority + event.reason = "muc user default low"; + return false; + end + end + -- Standard importance and no mention, leave to other modules to decide for now return nil; end @@ -74,6 +87,12 @@ label = "Lower priority"; desc = "E.g. large noisy public channels"; }; + { + type = "boolean"; + name = "defaultlow"; + label = "Default to lower priority"; + desc = "Mark all channels lower priority as default"; + }; } local store = module:open_store(); @@ -87,20 +106,29 @@ local prioritized_jids = user_sessions[username].csi_muc_priorities or store:get(username); local important = {}; local unimportant = {}; + local defaultlow = false; -- Default to high priority if prioritized_jids then for jid, priority in pairs(prioritized_jids) do - if priority then - table.insert(important, jid); - else - table.insert(unimportant, jid); + if jid then + if priority then + table.insert(important, jid); + else + table.insert(unimportant, jid); + end end end table.sort(important); table.sort(unimportant); + + if prioritized_jids[false] then + defaultlow = prioritized_jids[false]["defaultlow"]; + end end + return { important = important; unimportant = unimportant; + defaultlow = defaultlow }; end, function(fields, form_err, data) if form_err then @@ -108,17 +136,18 @@ end local prioritized_jids = {}; if fields.unimportant then - if fields.unimportant then - for _, jid in ipairs(fields.unimportant) do - prioritized_jids[jid] = false; - end - end - if fields.important then - for _, jid in ipairs(fields.important) do - prioritized_jids[jid] = true; - end + for _, jid in ipairs(fields.unimportant) do + prioritized_jids[jid] = false; end end + if fields.important then + for _, jid in ipairs(fields.important) do + prioritized_jids[jid] = true; + end + end + + local misc_data = {defaultlow = fields.defaultlow}; + prioritized_jids[false] = misc_data; local username = jid_split(data.from); local ok, err = store:set(username, prioritized_jids);
--- a/mod_firewall/conditions.lib.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_firewall/conditions.lib.lua Mon Mar 17 23:42:11 2025 +0700 @@ -123,7 +123,7 @@ end function condition_handlers.SUBSCRIBED() - return "(bare_to == bare_from or to_node and rostermanager.is_contact_subscribed(to_node, to_host, bare_from))", + return "(bare_to == bare_from or to_node and rostermanager.is_user_subscribed(to_node, to_host, bare_from))", { "rostermanager", "split_to", "bare_to", "bare_from" }; end
--- a/mod_groups_internal/mod_groups_internal.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_groups_internal/mod_groups_internal.lua Mon Mar 17 23:42:11 2025 +0700 @@ -11,7 +11,7 @@ local group_members_store = module:open_store("groups"); local group_memberships = module:open_store("groups", "map"); -local muc_host_name = module:get_option("groups_muc_host", "groups."..host); +local muc_host_name = module:get_option("groups_muc_host"); local muc_host = nil; local is_contact_subscribed = rostermanager.is_contact_subscribed; @@ -31,6 +31,7 @@ if group_name then local user_roster = rostermanager.load_roster(user, host); user_roster[contact_jid].groups[group_name] = true; + rostermanager.save_roster(user, host, user_roster, contact_jid); end -- Push updates to both rosters @@ -207,10 +208,18 @@ function delete(group_id) if group_members_store:set(group_id, nil) then local group_info = get_info(group_id); - if group_info and group_info.muc_jid then - local room = muc_host.get_room_from_jid(group_info.muc_jid) - if room then - room:destroy() + if group_info then + if group_info.muc_jid then + local room = muc_host.get_room_from_jid(group_info.muc_jid) + if room then + room:destroy() + end + end + for _, muc_jid in ipairs(group_info.mucs) do + local room = muc_host.get_room_from_jid(muc_jid) + if room then + room:destroy() + end end end return group_info_store:set(group_id, nil);
--- a/mod_http_index/mod_http_index.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_http_index/mod_http_index.lua Mon Mar 17 23:42:11 2025 +0700 @@ -7,8 +7,8 @@ local base_template; do - local template_file = module:get_option_string(module.name .. "_template", module.name .. ".html"); - template_file = assert(module:load_resource(template_file)); + local template_file = module:get_option_path(module.name .. "_template", "html/" .. module.name .. ".html"); + template_file = assert(io.open(template_file)); base_template = template_file:read("*a"); template_file:close(); end
--- a/mod_http_oauth2/html/consent.html Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_http_oauth2/html/consent.html Mon Mar 17 23:42:11 2025 +0700 @@ -45,7 +45,7 @@ select 'Allow'. Otherwise, select 'Deny'. </p> - <input type="hidden" name="user_token" value="{state.user.token}"> + <input type="hidden" name="user_token" value="{state.user.token}" /> <button type="submit" name="consent" value="denied">Deny</button> <button type="submit" name="consent" value="granted">Allow</button> </form>
--- a/mod_http_oauth2/mod_http_oauth2.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_http_oauth2/mod_http_oauth2.lua Mon Mar 17 23:42:11 2025 +0700 @@ -28,6 +28,27 @@ end end +local function strict_url_parse(urlstr) + local url_parts = url.parse(urlstr); + if not url_parts then return url_parts; end + if url_parts.userinfo then return false; end + if url_parts.port then + local port = tonumber(url_parts.port); + if not port then return false; end + if port <= 0 or port > 0xffff then return false; end + if port ~= math.floor(port) then return false; end + end + if url_parts.host then + if encodings.stringprep.nameprep(url_parts.host) ~= url_parts.host then + return false; + end + if not encodings.idna.to_ascii(url_parts.host) then + return false; + end + end + return url_parts; +end + local function strict_formdecode(query) if not query then return nil; @@ -697,8 +718,13 @@ if not request.headers.authorization then return; end local auth_type, auth_data = string.match(request.headers.authorization, "^(%S+)%s(.+)$"); + if not auth_type then return nil; end - if auth_type == "Basic" then + -- As described in Section 2.3 of [RFC5234], the string Bearer is case-insensitive. + -- https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-5.1.1 + auth_type = auth_type:lower(); + + if auth_type == "basic" then local creds = base64.decode(auth_data); if not creds then return; end local username, password = string.match(creds, "^([^:]+):(.*)$"); @@ -708,7 +734,7 @@ username = username; password = password; }; - elseif auth_type == "Bearer" then + elseif auth_type == "bearer" then return { type = "bearer"; bearer_token = auth_data; @@ -1356,7 +1382,7 @@ end local function redirect_uri_allowed(redirect_uri, client_uri, app_type) - local uri = url.parse(redirect_uri); + local uri = strict_url_parse(redirect_uri); if not uri then return false; end @@ -1391,8 +1417,8 @@ }); end - local client_uri = url.parse(client_metadata.client_uri); - if not client_uri or client_uri.scheme ~= "https" or loopbacks:contains(client_uri.host) then + local client_uri = strict_url_parse(client_metadata.client_uri); + if not client_uri or client_uri.scheme ~= "https" or not client_uri.host or loopbacks:contains(client_uri.host) then return nil, oauth_error("invalid_client_metadata", "Missing, invalid or insecure client_uri"); end @@ -1558,6 +1584,7 @@ -- This is the normal 'authorization_code' flow. -- Step 1. Create OAuth client + ["GET /register"] = { headers = { content_type = "application/schema+json" }; body = json.encode(registration_schema) }; ["POST /register"] = handle_register_request; -- Device flow @@ -1569,24 +1596,6 @@ ["POST /authorize"] = handle_authorization_request; ["OPTIONS /authorize"] = { status_code = 403; body = "" }; - -- Step 3. User is redirected to the 'redirect_uri' along with an - -- authorization code. In the insecure 'implicit' flow, the access token - -- is delivered here. - - -- Step 4. Retrieve access token using the code. - ["POST /token"] = handle_token_grant; - - -- Step 4 is later repeated using the refresh token to get new access tokens. - - -- Step 5. Revoke token (access or refresh) - ["POST /revoke"] = handle_revocation_request; - - -- Get info about a token - ["POST /introspect"] = handle_introspection_request; - - -- OpenID - ["GET /userinfo"] = handle_userinfo_request; - -- Optional static content for templates ["GET /style.css"] = templates.css and { headers = { @@ -1601,11 +1610,26 @@ body = templates.js; } or nil; - -- Some convenient fallback handlers - ["GET /register"] = { headers = { content_type = "application/schema+json" }; body = json.encode(registration_schema) }; + -- Step 3. User is redirected to the 'redirect_uri' along with an + -- authorization code. In the insecure 'implicit' flow, the access token + -- is delivered here. + + -- Step 4. Retrieve access token using the code. + ["POST /token"] = handle_token_grant; ["GET /token"] = function() return 405; end; + + -- Step 4 is later repeated using the refresh token to get new access tokens. + + -- Get info about a token + ["POST /introspect"] = handle_introspection_request; + ["GET /introspect"] = function() return 405; end; + + -- Get info about the user, used for OpenID Connect + ["GET /userinfo"] = handle_userinfo_request; + + -- Step 5. Revoke token (access or refresh) + ["POST /revoke"] = handle_revocation_request; ["GET /revoke"] = function() return 405; end; - ["GET /introspect"] = function() return 405; end; }; }); @@ -1652,7 +1676,7 @@ ui_locales_supported = allowed_locales[1] and allowed_locales; -- OpenID - userinfo_endpoint = handle_register_request and module:http_url() .. "/userinfo" or nil; + userinfo_endpoint = handle_userinfo_request and module:http_url() .. "/userinfo" or nil; jwks_uri = nil; -- REQUIRED in OpenID Discovery but not in OAuth 2.0 Metadata id_token_signing_alg_values_supported = { "HS256" }; -- The algorithm RS256 MUST be included, but we use HS256 and client_secret as shared key. }
--- a/mod_invites/mod_invites.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_invites/mod_invites.lua Mon Mar 17 23:42:11 2025 +0700 @@ -191,7 +191,7 @@ type = token_info and token_info.type or "roster"; uri = token_info and token_info.uri or get_uri("roster", username.."@"..module.host, token); additional_data = token_info and token_info.additional_data or nil; - reusable = token_info.reusable; + reusable = token_info and token_info.reusable or false; }, valid_invite_mt); end
--- a/mod_lastlog2/mod_lastlog2.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_lastlog2/mod_lastlog2.lua Mon Mar 17 23:42:11 2025 +0700 @@ -47,7 +47,7 @@ end); end -do +if module.host ~= "*" then local user_sessions = prosody.hosts[module.host].sessions; local kv_store = module:open_store(); function get_last_active(username) --luacheck: ignore 131/get_last_active @@ -67,6 +67,31 @@ end end +module:add_item("shell-command", { + section = "lastlog"; + section_desc = "View and manage user activity data"; + name = "show"; + desc = "View recorded user activity for user"; + args = { { name = "jid"; type = "string" } }; + host_selector = "jid"; + handler = function(self, userjid) + local kv_store = module:open_store(); + local username = jid.prepped_split(userjid); + local lastlog, err = kv_store:get(username); + if err then return false, err; end + if not lastlog then return true, "No record found"; end + local print = self.session.print; + for event, data in pairs(lastlog) do + print(("Last %s: %s"):format(event, + data.timestamp and os.date("%Y-%m-%d %H:%M:%S", data.timestamp) or "<unknown>")); + if data.ip then + print("IP address: "..data.ip); + end + end + return true, "Record shown" + end; +}); + function module.command(arg) if not arg[1] or arg[1] == "--help" then require"util.prosodyctl".show_usage([[mod_lastlog2 <user@host>]], [[Show when user last logged in or out]]);
--- a/mod_log_json/mod_log_json.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_log_json/mod_log_json.lua Mon Mar 17 23:42:11 2025 +0700 @@ -9,7 +9,12 @@ local function sink_maker(config) local send = function () end if config.filename then - local logfile = io.open(config.filename, "a+"); + local logfile; + if config.filename == "/dev/stdout" then + logfile = io.stdout; + else + logfile = io.open(config.filename, "a+"); + end logfile:setvbuf("no"); function send(payload) logfile:write(payload, "\n");
--- a/mod_measure_message_e2ee/mod_measure_message_e2ee.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_measure_message_e2ee/mod_measure_message_e2ee.lua Mon Mar 17 23:42:11 2025 +0700 @@ -3,6 +3,7 @@ local count_openpgp = module:measure("openpgp", "rate"); local count_otr = module:measure("otr", "rate"); local count_ox = module:measure("ox", "rate"); +local count_omemo2 = module:measure("omemo2", "rate"); local count_omemo = module:measure("omemo", "rate"); local count_encrypted = module:measure("encrypted", "rate"); @@ -22,6 +23,11 @@ return; end + if stanza:get_child("encrypted", "urn:xmpp:omemo:2") then + count_omemo2(); + return; + end + if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") then count_omemo(); return;