diff mod_invites_api/mod_invites_api.lua @ 4115:165ade4ce97b

mod_invites_api: New module to create new invites over HTTP
author Matthew Wild <mwild1@gmail.com>
date Sun, 13 Sep 2020 11:05:19 +0100
parents
children 35b678609b79
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invites_api/mod_invites_api.lua	Sun Sep 13 11:05:19 2020 +0100
@@ -0,0 +1,124 @@
+local http_formdecode = require "net.http".formdecode;
+
+local api_key_store;
+local invites;
+-- COMPAT: workaround to avoid executing inside prosodyctl
+if prosody.shutdown then
+	module:depends("http");
+	api_key_store = module:open_store("invite_api_keys", "map");
+	invites = module:depends("invites");
+end
+
+local function get_api_user(request, params)
+	local combined_key;
+
+	local auth_header = request.headers.authorization;
+
+	if not auth_header then
+		params = params or http_formdecode(request.url.query);
+		combined_key = params.key;
+	else
+		local auth_type, value = auth_header:match("^(%S+)%s(%S+)$");
+		if auth_type ~= "Bearer" then
+			return;
+		end
+		combined_key = value;
+	end
+
+	if not combined_key then
+		return;
+	end
+
+	local key_id, key_token = combined_key:match("^([^/]+)/(.+)$");
+
+	if not key_id then
+		return;
+	end
+
+	local api_user = api_key_store:get(nil, key_id);
+
+	if not api_user or api_user.token ~= key_token then
+		return;
+	end
+
+	-- TODO: key expiry, rate limiting, etc.
+	return api_user;
+end
+
+function handle_request(event)
+	local query_params = http_formdecode(event.request.url.query);
+
+	local api_user = get_api_user(event.request, query_params);
+
+	if not api_user then
+		return 403;
+	end
+
+	local invite = invites.create_account(nil, { source = "api/token/"..api_user.id });
+	if not invite then
+		return 500;
+	end
+
+	event.response.headers.Location = invite.landing_page or invite.uri;
+
+	if query_params.redirect then
+		return 303;
+	end
+	return 201;
+end
+
+if invites then
+	module:provides("http", {
+		route = {
+			["GET"] = handle_request;
+		};
+	});
+end
+
+function module.command(arg)
+	if #arg < 2 then
+		print("Usage:");
+		print("");
+		print(" prosodyctl mod_"..module.name.." create NAME");
+		print(" prosodyctl mod_"..module.name.." delete KEY_ID");
+		print(" prosodyctl mod_"..module.name.." list");
+		print("");
+	end
+
+	local command = table.remove(arg, 1);
+
+	local host = table.remove(arg, 1);
+	if not prosody.hosts[host] then
+		print("Error: please supply a valid host");
+		return 1;
+	end
+	require "core.storagemanager".initialize_host(host);
+	module.host = host; --luacheck: ignore 122/module
+	api_key_store = module:open_store("invite_api_keys", "map");
+
+	if command == "create" then
+		local id = require "util.id".short();
+		local token = require "util.id".long();
+		api_key_store:set(nil, id, {
+			id = id;
+			token = token;
+			name = arg[1];
+			created_at = os.time();
+		});
+		print(id.."/"..token);
+	elseif command == "delete" then
+		local id = table.remove(arg, 1);
+		if not api_key_store:get(nil, id) then
+			print("Error: key not found");
+			return 1;
+		end
+		api_key_store:set(nil, id, nil);
+	elseif command == "list" then
+		local api_key_store_kv = module:open_store("invite_api_keys");
+		for key_id, key_info in pairs(api_key_store_kv:get(nil)) do
+			print(key_id, key_info.name or "<unknown>");
+		end
+	else
+		print("Unknown command - "..command);
+	end
+end