Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,6 @@ Component "conference.example.com" "muc"
}
```

### Prosody image

The prosody image needs to have the Lua module `http` installed. Install it with LuaRocks:

```
luarocks install http
```

A Dockerfile [also exists](https://github.com/matrix-org/docker-jitsi-meet/releases/tag/stable-4857-ems.1) with this built in.

## License

Apache 2.0
104 changes: 65 additions & 39 deletions mod_auth_matrix_user_verification.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
-- limitations under the License.
--
-- Based on https://github.com/jitsi/jitsi-meet/blob/b765adca752c5bda95b15791e8421852c8ab7000/resources/prosody-plugins/mod_auth_token.lua
-- Code referenced for async portion: https://hg.prosody.im/prosody-modules/file/39156d6f7268/mod_auth_http_async/mod_auth_http_async.lua
-- net.http documentation: https://prosody.im/doc/developers/net/http
-- util.async documentation: https://prosody.im/doc/developers/util/async

local async = require "util.async";
local formdecode = require "util.http".formdecode;
local generate_uuid = require "util.uuid".generate;
local jid = require "util.jid";
Expand All @@ -22,7 +26,7 @@ local new_sasl = require "util.sasl".new;
local sasl = require "util.sasl";
local sessions = prosody.full_sessions;
local basexx = require "basexx";
local http_request = require "http.request";
local http = require "net.http";
local json = require "util.json";

-- Ensure configured
Expand Down Expand Up @@ -99,52 +103,70 @@ end
-- Check room membership from UVS
-- Returns boolean(isMember), boolean(isOwner)
local function verify_room_membership(matrix)
local request = http_request.new_from_uri(string.format("%s/verify/user_in_room", uvsUrl));
request.headers:upsert(":method", "POST");
request.headers:upsert("content-type", "application/json");
-- Set necessary HTTP headers for the request
local options = {};
options.headers = {};
options.headers["Content-Type"] = "application/json";
if uvsAuthToken ~= nil then
module:log("debug", "Setting authentication header with Bearer token");
request.headers:upsert(
"authorization",
string.format("Bearer %s", uvsAuthToken)
)
options.headers["Authorization"] = string.format("Bearer %s", uvsAuthToken)
end
module:log("info", "Found matrix %s %s", matrix.room_id, matrix.server_name);

-- Set the body of the request with details provided by the client
module:log("info", "Found room ID: %s, server_name: %s", matrix.room_id, matrix.server_name);
if matrix.server_name ~= nil then
request:set_body(string.format(
'{"token": "%s", "room_id": "%s", "matrix_server_name": "%s"}',
matrix.token, matrix.room_id, matrix.server_name
));
options.body = json.encode({ token = matrix.token, room_id = matrix.room_id, matrix_server_name = matrix.server_name });
else
request:set_body(string.format('{"token": "%s", "room_id": "%s"}', matrix.token, matrix.room_id));
options.body = json.encode({ token = matrix.token, room_id = matrix.room_id });
end
local headers, stream = assert(request:go());
local body = assert(stream:get_body_as_string());
local status = headers:get(":status");
if status == "200" then
local data = json.decode(body);
if data.results then
if data.results.user == true and data.results.room_membership == true then
if uvsSyncPowerLevels and data.power_levels ~= nil then
-- If the user power in the room is at least "state_detault", we mark them as owner
if data.power_levels.user >= data.power_levels.room.state_default then
return true, true;
end
end
return true, false;
else
if data.results.user == true and data.results.room_membership == false then
module:log("info", "REQUEST_COMPLETE reason:not_in_room")
else
module:log("info", "REQUEST_COMPLETE reason:invalid_token")
end

-- We want to make this HTTP call in an asynchronous manner
-- wait and done allow us to pause execution of the function while we wait for a long-running operation,
-- such as contacting the User Verification Service, to complete.
-- We pause the function with wait, then call done() when the request is complete to unpause where we left off
local wait, done = async.waiter();
local data;

local function cb(response_body, response_code, request, response)
module:log("debug", "Response code: %d", response_code);
module:log("debug", "Response body: %s", response_body);

if response_code == 200 then
-- Deserialise the body
data = json.decode(response_body);
end

done();
end

-- Make the request and pause execution of this function until done is called above
-- Note the request will automatically have a method of POST if a body is included
http.request(string.format("%s/verify/user_in_room", uvsUrl), options, cb);
wait();

if data == nil or not data.results then
module:log("info", "REQUEST_COMPLETE reason:invalid_response")
return false, false;
end

if data.results.user == true and data.results.room_membership == true then
if uvsSyncPowerLevels and data.power_levels ~= nil then
-- If the user power in the room is at least "state_default", we mark them as owner
if data.power_levels.user >= data.power_levels.room.state_default then
return true, true;
end
else
module:log("info", "REQUEST_COMPLETE reason:invalid_response")
end

-- The user is in the room, but they're not considered a moderator
return true, false;
else
module:log("info", "REQUEST_COMPLETE reason:exception")
if data.results.user == true and data.results.room_membership == false then
module:log("info", "REQUEST_COMPLETE reason:not_in_room")
else
module:log("info", "REQUEST_COMPLETE reason:invalid_token")
end
end

return false, false;
end

Expand All @@ -170,12 +192,16 @@ local function process_and_verify_token(session)
return false, "access-denied", "Jitsi room does not match Matrix room"
end

local isMember, isOwner = verify_room_membership(data.context.matrix)
local isMember, isOwner = verify_room_membership(data.context.matrix);

if isMember == false then
if isMember ~= true then
return false, "access-denied", "Token invalid or not in room";
end

if isOwner ~= true then
isOwner = false
end

-- Store the isOwner detail
session.auth_matrix_user_verification_is_owner = isOwner
-- Store some data in the session from the token
Expand Down