changeset 4343:ee313922b8d1

mod_http_admin_api: HTTP API for managing users and invites
author Matthew Wild <mwild1@gmail.com>
date Sun, 17 Jan 2021 12:49:20 +0000
parents 84e60c3d6e61
children 844cfc8c4039
files mod_http_admin_api/mod_http_admin_api.lua
diffstat 1 files changed, 112 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_admin_api/mod_http_admin_api.lua	Sun Jan 17 12:49:20 2021 +0000
@@ -0,0 +1,112 @@
+local json = require "util.json";
+
+module:depends("http");
+
+local invites = module:depends("invites");
+local tokens = module:depends("tokenauth");
+
+local json_content_type = "application/json";
+
+local www_authenticate_header = ("Bearer realm=%q"):format(module.host.."/"..module.name);
+
+local function check_credentials(request)
+	local auth_type, auth_data = string.match(request.headers.authorization or "", "^(%S+)%s(.+)$");
+	if not (auth_type and auth_data) then
+		return false;
+	end
+
+	if auth_type == "Bearer" then
+		local token_info = tokens.get_token_info(auth_data);
+		if not token_info or not token_info.session then
+			return false;
+		end
+		return token_info.session;
+	end
+	return nil;
+end
+
+function check_auth(routes)
+	local function check_request_auth(event)
+		local session = check_credentials(event.request);
+		if not session then
+			event.response.headers.authorization = www_authenticate_header;
+			return false, 401;
+		elseif session.auth_scope ~= "prosody:scope:admin" then
+			return false, 403;
+		end
+		event.session = session;
+		return true;
+	end
+
+	for route, handler in pairs(routes) do
+		routes[route] = function (event, ...)
+			local permit, code = check_request_auth(event);
+			if not permit then
+				return code;
+			end
+			return handler(event, ...);
+		end;
+	end
+	return routes;
+end
+
+local function token_info_to_invite_info(token_info)
+	return {
+		id = token_info.token;
+		type = token_info.type;
+		inviter = token_info.inviter;
+		jid = token_info.jid;
+		landing_page = token_info.landing_page;
+		created_at = token_info.created_at;
+		expires = token_info.expires;
+	};
+end
+
+function list_invites(event)
+	local invites_list = {};
+	for token, invite in invites.pending_account_invites() do --luacheck: ignore 213/token
+		table.insert(invites_list, token_info_to_invite_info(invite));
+	end
+	table.sort(invites_list, function (a, b)
+		return a.created_at < b.created_at;
+	end);
+
+	event.response.headers["Content-Type"] = json_content_type;
+	return json.encode(invites_list);
+end
+
+function get_invite_by_id(event, invite_id)
+	local invite = invites.get_account_invite_info(invite_id);
+	if not invite then
+		return 404;
+	end
+
+	event.response.headers["Content-Type"] = json_content_type;
+	return json.encode(token_info_to_invite_info(invite));
+end
+
+function create_invite(event)
+	local invite = invites.create_account(nil, { source = "admin_api/"..event.session.username });
+	if not invite then
+		return 500;
+	end
+
+	event.response.headers["Content-Type"] = json_content_type;
+	return json.encode(token_info_to_invite_info(invite));
+end
+
+function delete_invite(event, invite_id) --luacheck: ignore 212/event
+	if not invites.delete_account_invite(invite_id) then
+		return 404;
+	end
+	return 200;
+end
+
+module:provides("http", {
+	route = check_auth {
+		["GET /invites"] = list_invites;
+		["GET /invites/*"] = get_invite_by_id;
+		["PUT /invites"] = create_invite;
+		["DELETE /invites/*"] = delete_invite;
+	};
+});