changeset 4642:9fc52ccfb445

mod_http_muc_kick: Publish module
author Seve Ferrer <seve@delape.net>
date Tue, 10 Aug 2021 13:27:16 +0200
parents 64fafbeba14d
children df09f9ce0b1b
files mod_http_muc_kick/README.md mod_http_muc_kick/mod_http_muc_kick.lua
diffstat 2 files changed, 152 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_muc_kick/README.md	Tue Aug 10 13:27:16 2021 +0200
@@ -0,0 +1,64 @@
+# Introduction
+
+
+This module allows kicking users out of MUCs via HTTP.  
+It can be used in combination with [mod_muc_http_auth](https://modules.prosody.im/mod_muc_http_auth.html) as a complement to externalize MUC access.
+
+This module expects a JSON payload with the following keys:
+* `nickname` Mandatory. The nickname of the user to be kicked.
+* `muc` Mandatory. The JID of the muc to kick the user from.
+* `reason` Optional. A comment explaining the reason of the kick (More details https://xmpp.org/extensions/xep-0045.html#example-91).
+
+Example:
+```
+{
+    nickname: "Bob",
+    muc: "snuggery@chat.example.org",
+}
+```
+If the user was kicked successfuly, the module will return a 200 status code.  
+Otherwise, the according status code will be returned in the response, as well as a JSON payload providing an error message.
+```
+{
+    error: "Missing nickname and/or MUC"
+}
+```
+
+The path this module listens on is `/muc_kick`.  
+Example of a request to kick `Bob` from the `snuggery@chat.example.org` MUC using cURL:
+
+```
+curl --header "Content-Type: application/json" \
+  --request POST \
+  -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \
+  --data '{"nickname":"Bob","muc":"snuggery@chat.example.org"}' \
+  http://chat.example.org:5280/muc_kick
+```
+
+
+
+# Configuring
+
+## Enabling
+
+``` {.lua}
+Component "chat.example.org" "muc"
+
+modules_enabled = {
+    "http_muc_kick";
+}
+
+http_muc_kick_authorization_header = "Basic YWxhZGRpbjpvcGVuc2VzYW1l" -- Check the Settings section below
+
+```
+
+
+## Settings
+
+|Name |Description |Default |
+|-----|------------|--------|
+|http_muc_kick_authorization_header| Value of the Authorization header expected by every request when trying to kick a user. Example: `Basic dXNlcm5hbWU6cGFzc3dvcmQ=`| nil |
+
+Even though there is no check on whether the Authorization header provided is a valid one,
+please be aware that if `http_muc_kick_authorization_header` is nil, the module will not load as a reminder that some authorization should be enforced for this module.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_muc_kick/mod_http_muc_kick.lua	Tue Aug 10 13:27:16 2021 +0200
@@ -0,0 +1,88 @@
+local jid_split = require "util.jid".prepped_split;
+local json = require "util.json";
+
+module:depends("http");
+
+local authorization = assert(
+    module:get_option_string("http_muc_kick_authorization_header", nil),
+    "http_muc_kick_authorization_header setting is missing, please add it to the Prosody config before using mod_http_muc_kick"
+);
+
+local function is_authorized(request)
+    return request.headers.authorization == authorization;
+end
+
+local function check_muc(jid)
+	local muc_node, host = jid_split(jid);
+
+	if not hosts[host] then
+		return nil, nil, "No such host: "..host;
+	elseif not hosts[host].modules.muc then
+		return nil, nil, "Host '"..host.."' is not a MUC service";
+	end
+
+	return muc_node, host;
+end
+
+local function get_muc(muc_jid)
+    local muc_node, host, err = check_muc(muc_jid);
+    if not muc_node then
+        return nil, host, err;
+    end
+
+    muc = prosody.hosts[host].modules.muc.get_room_from_jid(muc_jid);
+    if not muc then
+        return nil, host, "No MUC '"..muc_node.."' found for host: "..host;
+    end
+    
+    return muc;
+end
+
+local function handle_error(response, status_code, error)
+    response.headers.content_type = "application/json";
+    response.status_code = status_code;
+    response:send(json.encode({error = error}));
+
+    -- return true to keep the connection open, and prevent other handlers from executing.
+    -- https://prosody.im/doc/developers/http#return_value
+    return true;
+end
+
+module:provides("http", {
+    route = {
+        ["POST"] = function (event)
+            local request, response = event.request, event.response;
+
+            if not is_authorized(request) then
+                return handle_error(response, 401, "Authorization failed");
+            end
+
+            local body = json.decode(request.body or "") or {};
+            if not body then
+                return handle_error(response, 400, "JSON body not found");
+            end
+
+            local nickname, muc_jid, reason = body.nickname, body.muc, body.reason or "";
+            if not nickname or not muc_jid then
+                return handle_error(response, 400, "Missing nickname and/or MUC");
+            end
+
+        	local muc, _, err = get_muc(muc_jid);
+            if not muc then
+                return handle_error(response, 404, "MUC not found: " .. err);
+            end
+
+            local occupant_jid = muc.jid .. "/" .. nickname;
+
+            -- Kick user by giving them the "none" role
+            -- https://xmpp.org/extensions/xep-0045.html#kick
+            local success, error, condition = muc:set_role(true, occupant_jid, nil, reason);
+            if not success then
+                return handle_error(response, 400, "Coudln't kick user: ".. error .. ": " .. condition);
+            end
+            
+            -- Kick was successful
+        	return 200;
+        end;
+    };
+});