Changeset

5595:f7410850941f

mod_invites_api: Change and add new commands for `module.command` to improve UX.
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Wed, 26 Jul 2023 18:43:45 +0700
parents 5594:14480ca9576e
children 5627:89b6d0e09b86
files mod_invites_api/mod_invites_api.lua
diffstat 1 files changed, 559 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/mod_invites_api/mod_invites_api.lua	Mon Jul 10 19:19:36 2023 +0700
+++ b/mod_invites_api/mod_invites_api.lua	Wed Jul 26 18:43:45 2023 +0700
@@ -1,12 +1,22 @@
 local http_formdecode = require "net.http".formdecode;
+local jid = require "util.jid";
+local usermanager = require "core.usermanager";
+local datetime = require "util.datetime";
 
-local api_key_store;
-local invites;
+-- Whether local users can invite other users to create an account on this server
+local allow_user_invites = module:get_option_boolean("allow_user_invites", false);
+-- Who can see and use the contact invite command. It is strongly recommened to
+-- keep this available to all local users. To allow/disallow invite-registration
+-- on the server, use the option above instead.
+local allow_contact_invites = module:get_option_boolean("allow_contact_invites", true);
+
+local api_key_store, api_key_store_kv, invites, date, config, path;
 -- 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");
+	api_key_store_kv = module:open_store("invite_api_keys");
 end
 
 local function get_api_user(request, params)
@@ -58,7 +68,18 @@
 		return 405;
 	end
 
-	local invite = invites.create_account(nil, { source = "api/token/"..api_user.id });
+	local invite;
+	local username, domain = jid.prepped_split(api_user.jid);
+	if username and usermanager.user_exists(username, domain) then
+		local ttl = module:get_option_number("invite_expiry", 86400 * 7);
+		invite = invites.create_contact(username,
+			allow_user_invites, -- allow_registration
+			{ source = "api/token/"..api_user.id }, -- additional_data
+			ttl
+			);
+	else
+		invite = invites.create_account(nil, { source = "api/token/"..api_user.id });
+	end
 	if not invite then
 		return 500;
 	end
@@ -79,49 +100,557 @@
 	});
 end
 
+function get_value(callback)
+	for key_id, key_info in pairs(api_key_store_kv:get(nil) or {}) do
+		date = datetime.datetime(key_info.created_at);
+		callback(key_id, key_info);
+	end
+end
+
+local function get_url(id, token)
+	local config, path = module:get_option("http_paths");
+	if config then
+		for k, v in pairs(config) do
+			if k == module.name then 
+				path = v;
+				break;
+			else 
+				path = "/"..module.name;
+			end
+		end
+	else path = "/"..module.name ; end
+
+	local url_base = module:get_option_string("http_external_link", module.host);
+	return url_base..path.."?key="..id.."/"..token.."&redirect=true";
+end
+
 function module.command(arg)
 	if #arg < 2 then
-		print("Usage:");
+		print("========================================================================");
+		print("");
+		print("Create:");
+		print("");
+		print("> prosodyctl mod_"..module.name.." create JID NAME");
+		print("");
+		print("Query:");
+		print("");
+		print("> prosodyctl mod_"..module.name.." get JID NAME");
+		print("> prosodyctl mod_"..module.name.." date JID NAME");
+		print("> prosodyctl mod_"..module.name.." id JID NAME");
+		print("> prosodyctl mod_"..module.name.." key JID NAME");
+		print("> prosodyctl mod_"..module.name.." url JID NAME");
 		print("");
-		print(" prosodyctl mod_"..module.name.." create NAME");
-		print(" prosodyctl mod_"..module.name.." delete KEY_ID");
-		print(" prosodyctl mod_"..module.name.." list");
+		print("Revoke:");
+		print("");
+		print("> prosodyctl mod_"..module.name.." delete JID NAME");
+		print("> prosodyctl mod_"..module.name.." delete-id HOST ID");
+		print("");
+		print("Renew:");
 		print("");
+		print("> prosodyctl mod_"..module.name.." renew JID NAME");
+		print("");
+		print("Help:");
+		print("");
+		print("> prosodyctl mod_"..module.name.." help COMMAND");
+		print("");
+		print("========================================================================");
+		return;
 	end
 
 	local command = table.remove(arg, 1);
+	if command == "help" then
+		help_command = table.remove(arg, 1);
+		if help_command == "create" then
+			print("========================================================================");
+			print("");
+			print("Create a key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." create JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" `JID` can either be a user's account or a host.");
+			print(" When `JID` is a host, you need to supply a `NAME`.");
+			print("");
+			print(" Each user's account can only have 1 API key but hosts are unlimited.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "renew" then
+			print("========================================================================");
+			print("");
+			print("Re-new a key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." create JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" `JID` can either be a user's account or a host.");
+			print(" When `JID` is a host, you need to supply a `NAME`.");
+			print("");
+			print(" The old `ID` will be kept and a new token will be generated for the API");
+			print(" key you specified.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "get" then
+			print("========================================================================");
+			print("");
+			print("Get info of a key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." get JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" When `JID` is a domain, it will list all the keys under that host.");
+			print(" When `JID` is a user's account, it fetches the key for that user.");
+			print(" If you supply both a host and a `NAME`, it fetches the key with `NAME`");
+			print(" under that host.")
+			print("");
+			print(" Output for a host is: DATE, ID, JID, NAME.");
+			print("");
+			print(" Output for a user's account is: DATE, ID, URL.");
+			print("");
+			print(" Output for a host with a valid `NAME` is: DATE, ID, URL.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "date" then
+			print("========================================================================");
+			print("");
+			print("Get the time stamp of a key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." date JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" Same as the `get` command but print only the birthday of the key.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "id" then
+			print("========================================================================");
+			print("");
+			print("Print the ID of a key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." id JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" Same as the `get` command but print only the ID of the key.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "key" then
+			print("========================================================================");
+			print("");
+			print("Print the API key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." key JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" Same as the `get` command but print only the key.");
+			print("");
+			print(" The key has the format: ID/TOKEN");
+			print("");
+			print(" The `renew` command will generate a new token and revoke the old one.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "url" then
+			print("========================================================================");
+			print("");
+			print("Print the URL of a key:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." url JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" Same as the `get` command but print only the URL of the key.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "delete" then
+			print("========================================================================");
+			print("");
+			print("Delete a key by JID and NAME:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." delete JID NAME");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" Same as `create` command but delete the key specified.");
+			print("");
+			print("========================================================================");
+		elseif help_command == "delete-id" then
+			print("========================================================================");
+			print("");
+			print("Delete API key by ID:");
+			print("");
+			print("> prosodyctl mod_"..module.name.." delete HOST ID");
+			print("");
+			print("------------------------------------------------------------------------");
+			print("");
+			print("Usage:");
+			print("");
+			print(" Input must be a valid host - cannot be a user's account.");
+			print("");
+			print(" To get the ID of a key, use the `id` command.");
+			print("");
+			print("========================================================================");
+		end
+		return;
+	end
 
-	local host = table.remove(arg, 1);
-	if not prosody.hosts[host] then
-		print("Error: please supply a valid host");
+	local username, domain = jid.prepped_split(table.remove(arg, 1));
+	if not prosody.hosts[domain] then
+		print("Error: please supply a valid host.");
 		return 1;
 	end
-	require "core.storagemanager".initialize_host(host);
-	module.host = host; --luacheck: ignore 122/module
+	require "core.storagemanager".initialize_host(domain);
+	module.host = domain; --luacheck: ignore 122/module
+	usermanager.initialize_host(module.host);
 	api_key_store = module:open_store("invite_api_keys", "map");
+	api_key_store_kv = module:open_store("invite_api_keys");
 
 	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();
-			allowed_methods = { GET = true, POST = true };
-		});
-		print(id.."/"..token);
+		local util_id = require "util.id".short();
+		local util_token = require "util.id".long();
+		local found = false;
+		local os_time = os.time();
+		if username then
+			if usermanager.user_exists(username, module.host) then
+				get_value(function (id, info)
+					if username.."@"..module.host == info.jid then
+						date = datetime.datetime(info.created_at);
+						print("Found:");
+						print(date, id, info.jid);
+						util_id = id;
+						util_token = info.token;
+						found = true;
+					end
+				end);
+				if found == false then
+					api_key_store:set(nil, util_id, {
+						id = util_id;
+						token = util_token;
+						name = nil;
+						jid = username.."@"..domain;
+						created_at = os_time;
+						allowed_methods = { GET = true, POST = true };
+					});
+					date = datetime.datetime(os_time);
+					print("Created:");
+					print(date, util_id.."/"..util_token, username.."@"..domain);
+				end
+				return;
+			else
+				print("Error: "..username.."@"..domain.." does not exists.");
+				return 1;
+			end
+		elseif domain then
+			local arg_name = table.remove(arg, 1);
+			if not arg_name or arg_name == "" then
+				print("Error: key for host needs a `NAME`.");
+				return;
+			end
+			found = false;
+			get_value(function (id, info)
+				if domain == info.jid and arg_name == info.name then
+					date = datetime.datetime(info.created_at);
+					print("Found:");
+					print(date, id, info.jid, info.name);
+					util_id = id;
+					util_token = info.token;
+					found = true;
+				end
+			end);
+			if found == false then
+				api_key_store:set(nil, util_id, {
+					id = util_id;
+					token = util_token;
+					name = arg_name;
+					jid = domain;
+					created_at = os_time;
+					allowed_methods = { GET = true, POST = true };
+				});
+				date = datetime.datetime(os_time);
+				print("Created:");
+				print(date, util_id.."/"..util_token, domain, arg_name);
+			end
+			return;
+		end
+	elseif command == "renew" then
+		local util_token = require "util.id".long();
+		local os_time = os.time();
+		if username then
+			local found;
+			if usermanager.user_exists(username, module.host) then
+				get_value(function (id, info)
+					if username.."@"..module.host == info.jid then
+						api_key_store:set(nil, id, {
+							id = id;
+							token = util_token;
+							name = arg[1];
+							jid = username.."@"..domain;
+							created_at = os_time;
+							allowed_methods = { GET = true, POST = true };
+						});
+						found = true;
+						date = datetime.datetime(os_time);
+						print("Re-newed:");
+						print(date, id.."/"..util_token, info.jid);
+					end
+				end);
+				if not found then
+					print("Error: Could not find the key for "..username.."@"..domain);
+					print("");
+					print("To make this API key, run:");
+					print("prosodyctl "..module.name.." create "..username.."@"..domain);
+					return;
+				end
+				return;
+			else
+				print("Error: "..username.."@"..domain.." does not exists.");
+				return 1;
+			end
+		elseif domain then
+			local arg_name = table.remove(arg, 1);
+			if not arg_name or arg_name == "" then
+				print("Error: key for host needs a `NAME`.");
+				return 1;
+			end
+			found = false;
+			get_value(function (id, info)
+				if domain == info.jid and arg_name == info.name then
+					api_key_store:set(nil, id, {
+						id = id;
+						token = util_token;
+						name = arg_name;
+						jid = domain;
+						created_at = os_time;
+						allowed_methods = { GET = true, POST = true };
+					});
+					date = datetime.datetime(os_time);
+					print("Re-newed:");
+					print(date, id.."/"..util_token, info.jid, info.name);
+					found = true;
+				end
+			end);
+			if not found then
+				date = datetime.datetime(os_time);
+				print("Error: Could not find "..arg_name.." in "..domain);
+				print("");
+				print("To make this API key, run:");
+				print("prosodyctl "..module.name.." create "..domain.." "..arg_name);
+				return;
+			end
+			return;
+		end
+	elseif command == "get" then
+		local name = table.remove(arg, 1);
+		local found = false;
+		if name and not username then
+			get_value(function (id, info)
+				if info.name == name then
+					print(date, id, get_url(id, info.token));
+					found = true;
+				end
+			end);
+			if found == false then
+				print("Error: could not find "..name.." in "..domain);
+				print("");
+				print("You can create it with:");
+				print("");
+				print("> prosodyctl "..module.name.." create "..domain.." "..name);
+			end
+			return;
+		elseif username then
+			get_value(function (id, info)
+				local j = jid.prepped_split(info.jid);
+				if j == username then
+					print(date, id, get_url(id, info.token));
+					found = true;
+				end
+			end);
+			if found == false then
+				print("Error: could not find the key for "..username.."@"..domain);
+				print("");
+				print("You can create it with:");
+				print("");
+				print("> prosodyctl "..module.name.." create "..username.."@"..domain);
+			end
+			return;
+		else
+			get_value(function (id, info)
+				if info.jid == module.host then
+					print(date, id, info.jid, info.name or "<unknown>");
+				else
+					print(date, id, info.jid, info.name or "");
+				end
+			end);
+			return;
+		end
+	elseif command == "date" then
+		local name = table.remove(arg, 1);
+		if name and not username then
+			get_value(function (id, info)
+				if info.name == name then
+					print(date);
+				end
+			end);
+			return;
+		elseif username then
+			get_value(function (id, info)
+				local j = jid.prepped_split(info.jid);
+				if j == username then
+					print(date);
+				end
+			end);
+			return;
+		else
+			get_value(function (id, info)
+				if info.jid == module.host then
+					print(date, info.jid, info.name or "<unknown>");
+				else
+					print(date, info.jid);
+				end
+			end);
+			return;
+		end
+	elseif command == "id" then
+		local name = table.remove(arg, 1);
+		if name and not username then
+			get_value(function (id, info)
+				if info.name == name then
+					print(id);
+				end
+			end);
+			return;
+		elseif username then
+			get_value(function (id, info)
+				local j = jid.prepped_split(info.jid);
+				if j == username then
+					print(id);
+				end
+			end);
+			return;
+		else
+			get_value(function (id, info)
+				if info.jid == module.host then
+					print(id, info.jid, info.name or "<unknown>");
+				else
+					print(id, info.jid);
+				end
+			end);
+			return;
+		end
+	elseif command == "key" then
+		local name = table.remove(arg, 1);
+		if name and not username then
+			get_value(function (id, info)
+				if info.name == name then
+					print(id.."/"..info.token);
+				end
+			end);
+			return;
+		elseif username then
+			get_value(function (id, info)
+				local j = jid.prepped_split(info.jid);
+				if j == username then
+					print(id.."/"..info.token);
+				end
+			end);
+			return;
+		else
+			get_value(function (id, info)
+				if info.jid == module.host then
+					print(id.."/"..info.token, info.jid, info.name or "<unknown>");
+				else
+					print(id.."/"..info.token, info.jid);
+				end
+			end);
+			return;
+		end
+	elseif command == "url" then
+		local name = table.remove(arg, 1);
+		if name and not username then
+			get_value(function (id, info)
+				if info.name == name then
+					print(get_url(id, info.token));
+				end
+			end);
+			return;
+		elseif username then
+			get_value(function (id, info)
+				local j = jid.prepped_split(info.jid);
+				if j == username then
+					print(get_url(id, info.token));
+				end
+			end);
+			return;
+		else
+			get_value(function (id, info)
+				if info.jid == module.host then
+					print(get_url(id, info.token), info.jid, info.name or "<unknown>");
+				else
+					print(get_url(id, info.token), info.jid);
+				end
+			end);
+			return;
+		end
 	elseif command == "delete" then
-		local id = table.remove(arg, 1);
-		if not api_key_store:get(nil, id) then
+		local name = table.remove(arg, 1);
+		if name and not username then
+			get_value(function (id, info)
+				if info.name == name then
+					api_key_store:set(nil, id, nil);
+					print("Deleted:");
+					print(date, id.."/"..info.token, info.jid, info.name or "");
+				end
+			end);
+			return;
+		elseif username then
+			get_value(function (id, info)
+				local j = jid.prepped_split(info.jid);
+				if j == username then
+					api_key_store:set(nil, id, nil);
+					print("Deleted:");
+					print(date, id.."/"..info.token, info.jid, info.name or "");
+				end
+			end);
+			return;
+		else
+			print("Error: Needs a valid `JID`. Or a host and a `NAME`.");
+		end
+	elseif command == "delete-id" then
+		if username then
+			print("Error: Input must be a valid host - cannot be a user's account.");
+			return 1;
+		end
+		local arg_id = table.remove(arg, 1);
+		if not api_key_store:get(nil, arg_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) or {}) do
-			print(key_id, key_info.name or "<unknown>");
+		else
+			get_value(function (id, info)
+				if arg_id == id then
+					api_key_store:set(nil, id, nil);
+					print("Deleted:");
+					print(date, id, info.jid, info.name or "");
+				end
+			end);
+			return;
 		end
 	else
 		print("Unknown command - "..command);