Changeset

6210:24316a399978 draft

Merge
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Tue, 18 Mar 2025 00:19:25 +0700
parents 6209:d611ed13df7e
children 6211:750d64c47ec6
files mod_firewall/mod_firewall.lua mod_flags/mod_flags.lua mod_http_altconnect/README.md mod_http_auth_check/README.md mod_http_authentication/README.md mod_http_logging/README.md mod_http_muc_log/README.md mod_http_oauth2/README.md mod_http_upload/README.md mod_idlecompat/README.md mod_ignore_host_chatstates/README.md mod_incidents_handling/README.md
diffstat 12 files changed, 784 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/mod_firewall/mod_firewall.lua	Tue Mar 18 00:16:25 2025 +0700
+++ b/mod_firewall/mod_firewall.lua	Tue Mar 18 00:19:25 2025 +0700
@@ -315,6 +315,10 @@
 	new_long_id = {
 		global_code = [[local new_long_id = require "util.id".long;]];
 	};
+
+	trace = {
+		global_code = [[local trace_init = module:require("trace").init;]];
+	};
 };
 
 local function include_dep(dependency, code)
@@ -371,9 +375,9 @@
 	module:require"marks";
 end
 
-local function new_rule(ruleset, chain)
+local function new_rule(ruleset, chain, line_no)
 	assert(chain, "no chain specified");
-	local rule = { conditions = {}, actions = {}, deps = {} };
+	local rule = { conditions = {}, actions = {}, deps = {}, line_no = line_no };
 	table.insert(ruleset[chain], rule);
 	return rule;
 end
@@ -385,6 +389,7 @@
 		return "Error compiling "..filename.." on line "..line_no..": "..err;
 	end
 
+	local metadata = { debug = {} };
 	local ruleset = {
 		deliver = {};
 	};
@@ -429,6 +434,12 @@
 				return nil, errmsg("Only event chains supported at the moment");
 			end
 			ruleset[chain] = ruleset[chain] or {};
+		elseif not(state) and line:sub(1, 2) == "@@" then
+			local k, v = line:match("^@@%s*([^%s=]+)%s*=%s*(.+)$");
+			if not k then
+				return nil, errmsg("Unable to parse metadata assignment (expected '@@ key = value')");
+			end
+			metadata[k] = v;
 		elseif not(state) and line:sub(1,1) == "%" then -- Definition (zone, limit, etc.)
 			local what, name = line:match("^%%%s*([%w_]+) +([^ :]+)");
 			if not definition_handlers[what] then
@@ -483,12 +494,13 @@
 			end
 		elseif state == "actions" then -- state is actions but action pattern did not match
 			state = nil; -- Awaiting next rule, etc.
-			table.insert(ruleset[chain], rule);
+			table.insert(ruleset[chain], rule); -- FIXME: Is this a bug? Rule should have already been inserted by new_rule()?
 			rule = nil;
 		else
-			if not state then
+			-- Condition
+			if not state then -- Starting a new rule block?
 				state = "rules";
-				rule = new_rule(ruleset, chain);
+				rule = new_rule(ruleset, chain, line_no);
 			end
 			-- Check standard modifiers for the condition (e.g. NOT)
 			local negated;
@@ -514,10 +526,10 @@
 			end
 		end
 	end
-	return ruleset;
+	return ruleset, metadata;
 end
 
-local function process_firewall_rules(ruleset)
+local function process_firewall_rules(ruleset, metadata)
 	-- Compile ruleset and return complete code
 
 	local chain_handlers = {};
@@ -537,8 +549,13 @@
 			end
 		end
 
+		if metadata.trace then
+			include_dep("trace", code);
+			table.insert(code, ("local trace = trace_init(%q, %q);"):format(metadata.filename, chain_name))
+		end
+
 		local condition_cache, n_conditions = {}, 0;
-		for _, rule in ipairs(rules) do
+		for rule_n, rule in ipairs(rules) do
 			for _, dep in ipairs(rule.deps) do
 				include_dep(dep, code);
 			end
@@ -562,6 +579,17 @@
 					else
 						rule.conditions[i] = (negated and "not(" or "(")..condition..")";
 					end
+
+					if metadata.trace then
+						-- Wrap each condition in a tracer
+						rule.conditions[i] = ("trace(%d, %d, %s)"):format(rule_n, i, rule.conditions[i]);
+					end
+				end
+
+				if metadata.trace then
+					-- Trace overall action
+					table.insert(rule.actions, 1, ("trace(%d, nil, true)"):format(rule_n));
+					table.insert(rule.actions, ("else trace(%d, nil, false)"):format(rule_n));
 				end
 
 				rule_code = "if "..table.concat(rule.conditions, " and ").." then\n\t\t\t"
@@ -592,9 +620,9 @@
 end
 
 local function compile_firewall_rules(filename)
-	local ruleset, err = parse_firewall_rules(filename);
-	if not ruleset then return nil, err; end
-	local chain_handlers = process_firewall_rules(ruleset);
+	local ruleset, metadata = parse_firewall_rules(filename);
+	if not ruleset then return nil, metadata; end
+	local chain_handlers = process_firewall_rules(ruleset, metadata);
 	return chain_handlers;
 end
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_flags/mod_flags.lua	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,194 @@
+-- This module is only for 0.12, later versions have mod_flags bundled
+--% conflicts: mod_flags
+
+local flags_map;
+if prosody.process_type ~= "prosodyctl" then
+	flags_map = module:open_store("account_flags", "map");
+end
+
+-- API
+
+function add_flag(username, flag, comment) -- luacheck: ignore 131/add_flag
+	local flag_data = {
+		when = os.time();
+		comment = comment;
+	};
+
+	local ok, err = flags_map:set(username, flag, flag_data);
+	if not ok then
+		return nil, err;
+	end
+
+	module:fire_event("user-flag-added/"..flag, {
+		user = username;
+		flag = flag;
+		data = flag_data;
+	});
+
+	return true;
+end
+
+function remove_flag(username, flag) -- luacheck: ignore 131/remove_flag
+	local ok, err = flags_map:set(username, flag, nil);
+	if not ok then
+		return nil, err;
+	end
+
+	module:fire_event("user-flag-removed/"..flag, {
+		user = username;
+		flag = flag;
+	});
+
+	return true;
+end
+
+function has_flag(username, flag) -- luacheck: ignore 131/has_flag
+	local ok, err = flags_map:get(username, flag);
+	if not ok and err then
+		error("Failed to check flags for user: "..err);
+	end
+	return not not ok;
+end
+
+function get_flag_info(username, flag) -- luacheck: ignore 131/get_flag_info
+	return flags_map:get(username, flag);
+end
+
+
+-- Migration from mod_firewall marks
+
+local function migrate_marks(host)
+	local usermanager = require "core.usermanager";
+
+	local flag_storage = module:open_store("account_flags");
+	local mark_storage = module:open_store("firewall_marks");
+
+	local migration_comment = "Migrated from mod_firewall marks at "..os.date("%Y-%m-%d %R");
+
+	local migrated, empty, errors = 0, 0, 0;
+	for username in usermanager.users(host) do
+		local marks, err = mark_storage:get(username);
+		if marks then
+			local flags = {};
+			for mark_name, mark_timestamp in pairs(marks) do
+				flags[mark_name] = {
+					when = mark_timestamp;
+					comment = migration_comment;
+				};
+			end
+			local saved_ok, saved_err = flag_storage:set(username, flags);
+			if saved_ok then
+				prosody.log("error", "Failed to save flags for %s: %s", username, saved_err);
+				migrated = migrated + 1;
+			else
+				errors = errors + 1;
+			end
+		elseif err then
+			prosody.log("error", "Failed to load marks for %s: %s", username, err);
+			errors = errors + 1;
+		else
+			empty = empty + 1;
+		end
+	end
+
+	print(("Finished - %d migrated, %d users with no marks, %d errors"):format(migrated, empty, errors));
+end
+
+function module.command(arg)
+	local storagemanager = require "core.storagemanager";
+	local usermanager = require "core.usermanager";
+	local jid = require "util.jid";
+	local warn = require"util.prosodyctl".show_warning;
+
+	local command = arg[1];
+	if not command then
+		warn("Valid subcommands: migrate_marks");
+		return 0;
+	end
+	table.remove(arg, 1);
+
+	local node, host = jid.prepped_split(arg[1]);
+	if not host then
+		warn("Please specify a host or JID after the command");
+		return 1;
+	elseif not prosody.hosts[host] then
+		warn("Unknown host: "..host);
+		return 1;
+	end
+
+	table.remove(arg, 1);
+
+	module.host = host; -- luacheck: ignore 122
+	storagemanager.initialize_host(host);
+	usermanager.initialize_host(host);
+
+	flags_map = module:open_store("account_flags", "map");
+
+	if command == "migrate_marks" then
+		migrate_marks(host);
+		return 0;
+	elseif command == "find" then
+		local flag = assert(arg[1], "expected argument: flag");
+		local flags = module:open_store("account_flags", "map");
+		local users_with_flag = flags:get_all(flag);
+
+		local c = 0;
+		for user, flag_data in pairs(users_with_flag) do
+			print(user, os.date("%Y-%m-%d %R", flag_data.when), flag_data.comment or "");
+			c = c + 1;
+		end
+
+		print(("%d accounts listed"):format(c));
+		return 1;
+	elseif command == "add" then
+		local username = assert(node, "expected a user JID, got "..host);
+		local flag = assert(arg[1], "expected argument: flag");
+		local comment = arg[2];
+
+		local ok, err = add_flag(username, flag, comment);
+		if not ok then
+			print("Failed to add flag: "..err);
+			return 1;
+		end
+
+		print("Flag added");
+		return 1;
+	elseif command == "remove" then
+		local username = assert(node, "expected a user JID, got "..host);
+		local flag = assert(arg[1], "expected argument: flag");
+
+		local ok, err = remove_flag(username, flag);
+		if not ok then
+			print("Failed to remove flag: "..err);
+			return 1;
+		end
+
+		print("Flag removed");
+		return 1;
+	elseif command == "list" then
+		local username = assert(node, "expected a user JID, got "..host);
+
+		local c = 0;
+
+		local flags = module:open_store("account_flags");
+		local user_flags, err = flags:get(username);
+
+		if not user_flags and err then
+			print("Unable to list flags: "..err);
+			return 1;
+		end
+
+		if user_flags then
+			for flag_name, flag_data in pairs(user_flags) do
+				print(flag_name, os.date("%Y-%m-%d %R", flag_data.when), flag_data.comment or "");
+				c = c + 1;
+			end
+		end
+
+		print(("%d flags listed"):format(c));
+		return 0;
+	else
+		warn("Unknown command: %s", command);
+		return 1;
+	end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_altconnect/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,3 @@
+This module helps make BOSH and WebSocket connection endpoints
+discoverable via the HTTP method described in
+[XEP-0156](https://xmpp.org/extensions/xep-0156.html#http).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_auth_check/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,31 @@
+---
+labels:
+summary: 'Test account credentials using HTTP'
+...
+
+Introduction
+------------
+
+This module lets you test whether a set of credentials are valid,
+using Prosody's configured authentication mechanism.
+
+This is useful as an easy way to allow other (e.g. non-XMPP) applications
+to authenticate users using their XMPP credentials.
+
+Syntax
+------
+
+To test credentials, issue a simple GET request with HTTP basic auth:
+
+    GET /auth_check HTTP/1.1
+    Authorization: Basic <base64(jid:password)>
+
+Prosody will return a 2xx code on success (user exists and credentials are
+correct), or 401 if the credentials are invalid. Any other code may be returned
+if there is a problem handling the request.
+
+### Example usage
+
+Here follows some example usage using `curl`.
+
+    curl http://prosody.local:5280/auth_check -u user@example.com:secr1t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_authentication/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,29 @@
+---
+labels:
+- 'Stage-Beta'
+summary: Enforces HTTP Basic authentication across all HTTP endpoints served by Prosody
+...
+
+# mod_http_authentication
+
+This module enforces HTTP Basic authentication across all HTTP endpoints served by Prosody.
+
+## Configuration
+
+  Name                               Default                           Description
+  ---------------------------------- --------------------------------- --------------------------------------------------------------------------------------------------------------------------------------
+  http\_credentials                  "minddistrict:secretpassword"     The credentials that HTTP clients must provide to access the HTTP interface. Should be a string with the syntax "username:password".
+  unauthenticated\_http\_endpoints   { "/http-bind", "/http-bind/" }   A list of paths that should be excluded from authentication.
+
+## Usage
+
+This is a global module, so should be added to the global `modules_enabled` option in your config file. It applies to all HTTP virtual hosts.
+
+## Compatibility
+
+The module use a new API in Prosody 0.10 and will not work with older
+versions.
+
+## Details
+
+By Kim Alvefur \<zash@zash.se\>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_logging/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,3 @@
+This module produces more detailed HTTP logs for Prosodys built-in HTTP
+server. The format is similar to that of Apache and go into Prosodys
+normal logs at the `info` level.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_muc_log/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,109 @@
+---
+labels:
+- 'Stage-Beta'
+summary: Provides a web interface to stored chatroom logs
+rockspec:
+  build:
+    copy_directories:
+      - res
+      - static
+...
+
+Introduction
+============
+
+This module provides a built-in web interface to view chatroom logs
+stored by [mod\_muc\_mam].
+
+Installation
+============
+
+Same as any other module, be sure to include the HTML template
+`http_muc_log.html` alongside `mod_http_muc_log.lua`.
+
+Configuration
+=============
+
+For example:
+
+``` lua
+Component "conference.example.com" "muc"
+modules_enabled = {
+    "muc_mam";
+    "http_muc_log";
+}
+storage = {
+    muc_log = "sql"; -- for example
+}
+```
+
+The web interface would then be reachable at the address:
+
+    http://conference.example.com:5280/muc_log/
+
+See [the page about Prosodys HTTP server][doc:http] for info about the
+address.
+
+## Styling
+
+The default HTML template lives in `http_muc_log.html` in the same
+directory as the module, but can be changed by setting
+`http_muc_log_template` to point to a different file. The same template,
+with different input is used for every view.
+
+The module uses [util.interpolation][doc:developers:util:interpolation]
+for rendering templates, with the pattern `"%b{}"` and HTML / XML
+escaping enabled.
+
+## Default view
+
+To link to the latest day instead of calendar from the room listing
+page:
+
+```lua
+http_muc_log_default_view = "latest"
+```
+
+## Inline images
+
+Inline images can optionally be shown. This is disabled by default for
+privacy reasons.
+
+``` {.lua}
+http_muc_log_show_images = true
+```
+
+## Calendar optimization
+
+The calendar view relies on an optional part of the Prosody archive
+storage API that provides a list of every valid date. If this is
+unavailable then the module queries for the first and the last messages
+and assumes that every date between those is valid. This may lead to
+many empty pages in case the logs are sparse.
+
+This optimization can be turned off, to get a more accurate calendar
+view, but it will likely be very slow.
+
+``` {.lua}
+http_muc_log_lazy_calendar = false
+```
+
+## Pinned chatrooms
+
+The room list page is normally sorted by address, rooms having a
+description before those that don't. To override this, or pin certain
+rooms to the top:
+
+``` lua
+http_muc_log_list_order = {
+    "general@channels.example.com",
+    "support@channels.example.com",
+}
+```
+
+Compatibility
+=============
+
+Requires Prosody 0.11 or later and a storage backend with support for
+stanza archives. See [mod\_storage\_muc\_log] for using legacy data from
+[mod\_muc\_log].
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_oauth2/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,289 @@
+---
+labels:
+- Stage-Alpha
+rockspec:
+  build:
+    copy_directories:
+    - html
+summary: OAuth 2.0 Authorization Server API
+---
+
+## Introduction
+
+This module implements an [OAuth2](https://oauth.net/2/)/[OpenID Connect
+(OIDC)](https://openid.net/connect/) Authorization Server on top of
+Prosody's usual internal authentication backend.
+
+OAuth and OIDC are web standards that allow you to provide clients and
+third-party applications limited access to your account, without sharing your
+password with them.
+
+With this module deployed, software that supports OAuth can obtain
+"access tokens" from Prosody which can then be used to connect to XMPP
+accounts using the [OAUTHBEARER SASL mechanism][rfc7628] or via non-XMPP
+interfaces such as [mod_rest].
+
+Although this module has been around for some time, it has recently been
+significantly extended and largely rewritten to support OAuth/OIDC more fully.
+
+As of April 2023, it should be considered **alpha** stage. It works, we have
+tested it, but it has not yet seen wider review, testing and deployment. At
+this stage we recommend it for experimental and test deployments only. For
+specific information, see the [deployment notes section](#deployment-notes)
+below.
+
+Known client implementations:
+
+-   [example shell script for mod_rest](https://hg.prosody.im/prosody-modules/file/tip/mod_rest/example/rest.sh)
+-   *(we need you!)*
+
+Support for [OAUTHBEARER][rfc7628] has been added to the Lua XMPP
+library, [verse](https://code.matthewwild.co.uk/verse).  If you know of
+additional implementations, or are motivated to work on one, please let
+us know! We'd be happy to help (e.g. by providing a test server).
+
+## Standards support
+
+Notable supported standards:
+
+- [RFC 6749: The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749)
+- [RFC 7009: OAuth 2.0 Token Revocation](https://www.rfc-editor.org/rfc/rfc7009)
+- [RFC 7591: OAuth 2.0 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591.html)
+- [RFC 7628: A Set of Simple Authentication and Security Layer (SASL) Mechanisms for OAuth](https://www.rfc-editor.org/rfc/rfc7628)
+- [RFC 7636: Proof Key for Code Exchange by OAuth Public Clients](https://www.rfc-editor.org/rfc/rfc7636)
+- [RFC 7662: OAuth 2.0 Token Introspection](https://www.rfc-editor.org/rfc/rfc7662)
+- [RFC 8628: OAuth 2.0 Device Authorization Grant](https://www.rfc-editor.org/rfc/rfc8628)
+- [RFC 9207: OAuth 2.0 Authorization Server Issuer Identification](https://www.rfc-editor.org/rfc/rfc9207.html)
+- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
+- [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) (_partial, e.g. missing JWKS_)
+- [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html)
+
+## Configuration
+
+### Interface
+
+The module presents a web page to users to allow them to authenticate when
+a client requests access. Built-in pages are provided, but you may also theme
+or entirely override them.
+
+This module honours the `site_name` configuration option that is also used by
+a number of other modules:
+
+```lua
+site_name = "My XMPP Server"
+```
+
+To provide custom templates, specify the path to the template directory:
+
+```lua
+oauth2_template_path = "/etc/prosody/custom-oauth2-templates"
+```
+
+If you know what features your templates use use you can adjust the
+`Content-Security-Policy` header to only allow what is needed:
+
+```lua
+oauth2_security_policy = "default-src 'self'" -- this is the default
+```
+
+### Token parameters
+
+The following options configure the lifetime of tokens issued by the module.
+The defaults are recommended.
+
+```lua
+oauth2_access_token_ttl = 3600 -- one hour
+oauth2_refresh_token_ttl = 604800 -- one week
+```
+
+### Dynamic client registration
+
+To allow users to connect any compatible software, you should enable dynamic
+client registration.
+
+Dynamic client registration can be enabled by configuring a JWT key. Algorithm
+defaults to *HS256*, lifetime defaults to forever.
+
+```lua
+oauth2_registration_key = "securely generated JWT key here"
+oauth2_registration_algorithm = "HS256"
+oauth2_registration_ttl = nil -- unlimited by default
+```
+
+Registering a client is described in
+[RFC7591](https://www.rfc-editor.org/rfc/rfc7591.html).
+
+In addition to the requirements in the RFC, the following requirements
+are enforced:
+
+`client_name`
+:   **MUST** be present, is shown to users in consent screen.
+
+`client_uri`
+:   **MUST** be present and **MUST** be a `https://` URL.
+
+`redirect_uris`
+
+:   **MUST** contain at least one valid URI. Different rules apply
+    depending on the value of `application_type`, see below.
+
+`application_type`
+
+:   Optional, defaults to `web`. Determines further restrictions for
+    `redirect_uris`. The following values are supported:
+
+    `web` *(default)*
+    :   For web clients. With this, `redirect_uris` **MUST** be
+        `https://` URIs and **MUST** use the same hostname part as the
+        `client_uri`.
+
+    `native`
+    :   For native e.g. desktop clients etc. `redirect_uris` **MUST**
+        match one of:
+
+        -   Loopback HTTP URI, e.g. `http://127.0.0.1/` or
+            `http://[::1]`
+        -   Application-specific scheme, e.g. `com.example.app:/`
+        -   The special OOB URI `urn:ietf:wg:oauth:2.0:oob`
+
+`tos_uri`, `policy_uri`
+:   Informative URLs pointing to Terms of Service and Service Policy
+    document **MUST** use the same scheme (i.e. `https://`) and hostname
+    as the `client_uri`.
+
+#### Registration Examples
+
+In short registration works by POST-ing a JSON structure describing your
+client to an endpoint:
+
+``` bash
+curl -sSf https://xmpp.example.net/oauth2/register \
+    -H Content-Type:application/json \
+    -H Accept:application/json \
+    --data '
+{
+   "client_name" : "My Application",
+   "client_uri" : "https://app.example.com/",
+   "redirect_uris" : [
+      "https://app.example.com/redirect"
+   ]
+}
+'
+```
+
+Another example with more fields:
+
+``` bash
+curl -sSf https://xmpp.example.net/oauth2/register \
+    -H Content-Type:application/json \
+    -H Accept:application/json \
+    --data '
+{
+   "application_type" : "native",
+   "client_name" : "Desktop Chat App",
+   "client_uri" : "https://app.example.org/",
+   "contacts" : [
+      "support@example.org"
+   ],
+   "policy_uri" : "https://app.example.org/about/privacy",
+   "redirect_uris" : [
+      "http://localhost:8080/redirect",
+      "org.example.app:/redirect"
+   ],
+   "scope" : "xmpp",
+   "software_id" : "32a0a8f3-4016-5478-905a-c373156eca73",
+   "software_version" : "3.4.1",
+   "tos_uri" : "https://app.example.org/about/terms"
+}
+'
+```
+
+### Supported flows
+
+-   Authorization Code grant, optionally with Proof Key for Code Exchange
+-   Device Authorization Grant
+-   Resource owner password grant *(disabled by default)*
+-   Implicit flow *(disabled by default)*
+-   Refresh Token grants
+
+Various flows can be disabled and enabled with
+`allowed_oauth2_grant_types` and `allowed_oauth2_response_types`:
+
+```lua
+-- These examples reflect the defaults
+allowed_oauth2_grant_types = {
+	"authorization_code"; -- authorization code grant
+	"device_code";
+	-- "password"; -- resource owner password grant disabled by default
+}
+
+allowed_oauth2_response_types = {
+	"code"; -- authorization code flow
+    -- "token"; -- implicit flow disabled by default
+}
+```
+
+The [Proof Key for Code Exchange][RFC 7636] mitigation method is
+required by default but can be made optional:
+
+```lua
+oauth2_require_code_challenge = false -- default is true
+```
+
+Further, individual challenge methods can be enabled or disabled:
+
+```lua
+-- These reflects the default
+allowed_oauth2_code_challenge_methods = {
+    -- "plain"; -- insecure but backwards-compatible
+    "S256";
+}
+```
+
+### Policy documents
+
+Links to Terms of Service and Service Policy documents can be advertised
+for use by OAuth clients:
+
+```lua
+oauth2_terms_url = "https://example.com/terms-of-service.html"
+oauth2_policy_url = "https://example.com/service-policy.pdf"
+-- These are unset by default
+```
+
+## Deployment notes
+
+### Access management
+
+This module does not provide an interface for users to manage what they have
+granted access to their account! (e.g. to view and revoke clients they have
+previously authorized). It is recommended to join this module with
+[mod_client_management] to provide such access. However, at the time of writing,
+no XMPP clients currently support the protocol used by that module. We plan to
+work on additional interfaces in the future.
+
+### Scopes
+
+OAuth supports "scopes" as a way to grant clients limited access.
+
+There are currently no standard scopes defined for XMPP. This is
+something that we intend to change, e.g. by definitions provided in a
+future XEP. This means that clients you authorize currently have to
+choose between unrestricted access to your account (including the
+ability to change your password and lock you out!) and zero access. So,
+for now, while using OAuth clients can prevent leaking your password to
+them, it is not currently suitable for connecting untrusted clients to
+your account.
+
+As a first step, the `xmpp` scope is supported, and corresponds to
+whatever permissions the user would have when logged in over XMPP.
+
+Further, known Prosody roles can be used as scopes.
+
+OpenID scopes such as `openid` and `profile` can be used for "Login
+with XMPP" without granting access to more than limited profile details.
+
+## Compatibility
+
+Requires Prosody trunk (April 2023), **not** compatible with Prosody 0.12 or
+earlier.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_upload/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,13 @@
+---
+description: HTTP File Upload
+superseded_by: mod_http_file_share
+labels:
+- Stage-Obsolete
+---
+
+This module is obsolete and deleted.
+
+In Prosody 0.12 and later, consider switching to [mod_http_file_share](https://prosody.im/doc/modules/mod_http_file_share)
+which is distributed with Prosody. You can migrate existing files using
+[mod_migrate_http_upload](https://modules.prosody.im/mod_migrate_http_upload.html).
+You will be redirected shortly.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_idlecompat/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,24 @@
+---
+labels:
+- 'Stage-Beta'
+summary: 'XEP-0319 compatibility module'
+...
+
+Introduction
+============
+
+This module adds [XEP-0319](http://xmpp.org/extensions/xep-0319.html)
+idle tags to presence stanzas containing [XEP-0012: Last
+Activity](http://xmpp.org/extensions/xep-0012.html) tags for idle
+indication (e.g. supported by libpurple clients). It works on outgoing
+and incoming presence stanzas.
+
+Install and enable it like any other module. It has no configuration.
+
+Compatibility
+=============
+
+  ------- -------
+  trunk   Works
+  0.9     Works
+  ------- -------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_ignore_host_chatstates/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,3 @@
+This module discards typing notifications sent to a bare host JID,
+preventing error replies to be sent. These errors are harmless but can
+be annoying sometimes if your client shows them prominently.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_incidents_handling/README.md	Tue Mar 18 00:19:25 2025 +0700
@@ -0,0 +1,47 @@
+---
+labels:
+- 'Stage-Beta'
+summary: Incidents Handling plugin
+...
+
+Introduction
+============
+
+This module implements
+[XEP-268](http://xmpp.org/extensions/xep-0268.html).
+
+Details
+=======
+
+It will let you manage reports, inquiries, requests and responses
+through an Adhoc interface. The following new adhoc admin commands will
+be available:
+
+-   List Incidents -- List all available incidents and let's you reply
+    requests.
+-   Send Incident Inquiry -- Inquiry a remote server about an incident.
+-   Send Incident Report -- Send an incident report to a remote server.
+-   Send Incident Request -- Send an incident request to a remote
+    server.
+
+Each Adhoc form provides syntax instructions through `<desc/>` elements
+(they may currently be stripped by Prosody), although it's encouraged to
+read the [IODEF specifications](https://tools.ietf.org/html/rfc5070).
+
+Usage
+=====
+
+Copy the module folder into your prosody modules directory. Place the
+module between your enabled modules either into the global or a vhost
+section.
+
+Optional configuration directives:
+
+``` {.lua}
+incidents_expire_time = 86400 -- Expiral of "closed" incidents in seconds.
+```
+
+Info
+====
+
+-   to be 0.9, works.