Software /
code /
prosody-modules
File
mod_invites_api/mod_invites_api.lua @ 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 |
parent | 5143:1cae382e88a1 |
child | 5628:a74b07764d3f |
line wrap: on
line source
local http_formdecode = require "net.http".formdecode; local jid = require "util.jid"; local usermanager = require "core.usermanager"; local datetime = require "util.datetime"; -- 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) local combined_key; local auth_header = request.headers.authorization; if not auth_header then params = params or http_formdecode(request.url.query or "="); 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 if api_user.allowed_methods and not api_user.allowed_methods[event.request.method] then return 405; end 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 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 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("========================================================================"); 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("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 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(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 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 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; 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); end end