# HG changeset patch # User Seve Ferrer # Date 1628594836 -7200 # Node ID 9fc52ccfb445dbf63ecf4f3079981f61bb79c48b # Parent 64fafbeba14d581c359cea05588f86235dc1ef10 mod_http_muc_kick: Publish module diff -r 64fafbeba14d -r 9fc52ccfb445 mod_http_muc_kick/README.md --- /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. + diff -r 64fafbeba14d -r 9fc52ccfb445 mod_http_muc_kick/mod_http_muc_kick.lua --- /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; + }; +});