aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/mod_invites.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mod_invites.lua')
-rw-r--r--plugins/mod_invites.lua324
1 files changed, 220 insertions, 104 deletions
diff --git a/plugins/mod_invites.lua b/plugins/mod_invites.lua
index 1dfc8804..c6a66a8f 100644
--- a/plugins/mod_invites.lua
+++ b/plugins/mod_invites.lua
@@ -6,8 +6,8 @@ local jid_split = require "prosody.util.jid".split;
local argparse = require "prosody.util.argparse";
local human_io = require "prosody.util.human.io";
-local url_escape = require "util.http".urlencode;
-local render_url = require "util.interpolation".new("%b{}", url_escape, {
+local url_escape = require "prosody.util.http".urlencode;
+local render_url = require "prosody.util.interpolation".new("%b{}", url_escape, {
urlescape = url_escape;
noscheme = function (urlstring)
return (urlstring:gsub("^[^:]+:", ""));
@@ -239,19 +239,76 @@ end
module:hook("invite-created", add_landing_url, -1);
--- shell command
+-- COMPAT: Dynamic groups are work in progress as of 13.0, so we'll use the
+-- presence of mod_invites_groups (a community module) to determine whether to
+-- expose our support for invites to groups.
+local have_group_invites = module:get_option_inherited_set("modules_enabled"):contains("invites_groups");
+
module:add_item("shell-command", {
section = "invite";
section_desc = "Create and manage invitations";
name = "create_account";
desc = "Create an invitation to make an account on this server with the specified JID (supply only a hostname to allow any username)";
+ args = {
+ { name = "user_jid", type = "string" };
+ };
+ host_selector = "user_jid";
+ flags = {
+ array_params = { role = true, group = have_group_invites };
+ value_params = { expires_after = true };
+ };
+
+ handler = function (self, user_jid, opts) --luacheck: ignore 212/self
+ local username = jid_split(user_jid);
+ local roles = opts and opts.role or {};
+ local groups = opts and opts.group or {};
+
+ if opts and opts.admin then
+ -- Insert it first since we don't get order out of argparse
+ table.insert(roles, 1, "prosody:admin");
+ end
+
+ local ttl;
+ if opts and opts.expires_after then
+ ttl = human_io.parse_duration(opts.expires_after);
+ if not ttl then
+ return false, "Unable to parse duration: "..opts.expires_after;
+ end
+ end
+
+ local invite = assert(create_account(username, {
+ roles = roles;
+ groups = groups;
+ }, ttl));
+
+ return true, invite.landing_page or invite.uri;
+ end;
+});
+
+module:add_item("shell-command", {
+ section = "invite";
+ section_desc = "Create and manage invitations";
+ name = "create_reset";
+ desc = "Create a password reset link for the specified user";
args = { { name = "user_jid", type = "string" } };
host_selector = "user_jid";
+ flags = {
+ value_params = { expires_after = true };
+ };
- handler = function (self, user_jid) --luacheck: ignore 212/self
+ handler = function (self, user_jid, opts) --luacheck: ignore 212/self
local username = jid_split(user_jid);
- local invite, err = create_account(username);
+ if not username then
+ return nil, "Supply the JID of the account you want to generate a password reset for";
+ end
+ local duration_sec = require "prosody.util.human.io".parse_duration(opts and opts.expires_after or "1d");
+ if not duration_sec then
+ return nil, "Unable to parse duration: "..opts.expires_after;
+ end
+ local invite, err = create_account_reset(username, duration_sec);
if not invite then return nil, err; end
- return true, invite.landing_page or invite.uri;
+ self.session.print(invite.landing_page or invite.uri);
+ return true, ("Password reset link for %s valid until %s"):format(user_jid, os.date("%Y-%m-%d %T", invite.expires));
end;
});
@@ -260,126 +317,185 @@ module:add_item("shell-command", {
section_desc = "Create and manage invitations";
name = "create_contact";
desc = "Create an invitation to become contacts with the specified user";
- args = { { name = "user_jid", type = "string" }, { name = "allow_registration" } };
+ args = { { name = "user_jid", type = "string" } };
host_selector = "user_jid";
+ flags = {
+ value_params = { expires_after = true };
+ kv_params = { allow_registration = true };
+ };
- handler = function (self, user_jid, allow_registration) --luacheck: ignore 212/self
+ handler = function (self, user_jid, opts) --luacheck: ignore 212/self
local username = jid_split(user_jid);
- local invite, err = create_contact(username, allow_registration);
+ if not username then
+ return nil, "Supply the JID of the account you want the recipient to become a contact of";
+ end
+ local ttl;
+ if opts and opts.expires_after then
+ ttl = require "prosody.util.human.io".parse_duration(opts.expires_after);
+ if not ttl then
+ return nil, "Unable to parse duration: "..opts.expires_after;
+ end
+ end
+ local invite, err = create_contact(username, opts and opts.allow_registration, nil, ttl);
if not invite then return nil, err; end
return true, invite.landing_page or invite.uri;
end;
});
-local subcommands = {};
-
---- prosodyctl command
-function module.command(arg)
- local opts = argparse.parse(arg, { short_params = { h = "help"; ["?"] = "help" } });
- local cmd = table.remove(arg, 1); -- pop command
- if opts.help or not cmd or not subcommands[cmd] then
- print("usage: prosodyctl mod_"..module.name.." generate example.com");
- return 2;
- end
- return subcommands[cmd](arg);
-end
-
-function subcommands.generate(arg)
- local function help(short)
- print("usage: prosodyctl mod_" .. module.name .. " generate DOMAIN --reset USERNAME")
- print("usage: prosodyctl mod_" .. module.name .. " generate DOMAIN [--admin] [--role ROLE] [--group GROUPID]...")
- if short then return 2 end
- print()
- print("This command has two modes: password reset and new account.")
- print("If --reset is given, the command operates in password reset mode and in new account mode otherwise.")
- print()
- print("required arguments in password reset mode:")
- print()
- print(" --reset USERNAME Generate a password reset link for the given USERNAME.")
- print()
- print("optional arguments in new account mode:")
- print()
- print(" --admin Make the new user privileged")
- print(" Equivalent to --role prosody:admin")
- print(" --role ROLE Grant the given ROLE to the new user")
- print(" --group GROUPID Add the user to the group with the given ID")
- print(" Can be specified multiple times")
- print(" --expires-after T Time until the invite expires (e.g. '1 week')")
- print()
- print("--group can be specified multiple times; the user will be added to all groups.")
- print()
- print("--reset and the other options cannot be mixed.")
- return 2
- end
+module:add_item("shell-command", {
+ section = "invite";
+ section_desc = "Create and manage invitations";
+ name = "show";
+ desc = "Show details of an account invitation token";
+ args = { { name = "host", type = "string" }, { name = "token", type = "string" } };
+ host_selector = "host";
- local earlyopts = argparse.parse(arg, { short_params = { h = "help"; ["?"] = "help" } });
- if earlyopts.help or not earlyopts[1] then
- return help();
- end
+ handler = function (self, host, token) --luacheck: ignore 212/self 212/host
+ local invite, err = get_account_invite_info(token);
+ if not invite then return nil, err; end
- local sm = require "prosody.core.storagemanager";
- local mm = require "prosody.core.modulemanager";
+ local print = self.session.print;
+
+ if invite.type == "roster" then
+ print("Invitation to register and become a contact of "..invite.jid);
+ elseif invite.type == "register" then
+ local jid_user, jid_host = jid_split(invite.jid);
+ if invite.additional_data and invite.additional_data.allow_reset then
+ print("Password reset for "..invite.additional_data.allow_reset.."@"..jid_host);
+ elseif jid_user then
+ print("Invitation to register on "..jid_host.." with username '"..jid_user.."'");
+ else
+ print("Invitation to register on "..jid_host);
+ end
+ else
+ print("Unknown invitation type");
+ end
- local host = table.remove(arg, 1); -- pop host
- if not host then return help(true) end
- sm.initialize_host(host);
- module.host = host; --luacheck: ignore 122/module
- token_storage = module:open_store("invite_token", "map");
+ if invite.inviter then
+ print("Creator:", invite.inviter);
+ end
- local opts = argparse.parse(arg, {
- short_params = { h = "help"; ["?"] = "help"; g = "group" };
- value_params = { group = true; reset = true; role = true };
- array_params = { group = true; role = true };
- });
+ print("Created:", os.date("%Y-%m-%d %T", invite.created_at));
+ print("Expires:", os.date("%Y-%m-%d %T", invite.expires));
- if opts.help then
- return help();
- end
+ print("");
- -- Load mod_invites
- local invites = module:depends("invites");
- -- Optional community module that if used, needs to be loaded here
- local invites_page_module = module:get_option_string("invites_page_module", "invites_page");
- if mm.get_modules_for_host(host):contains(invites_page_module) then
- module:depends(invites_page_module);
- end
+ if invite.uri then
+ print("XMPP URI:", invite.uri);
+ end
- local allow_reset;
+ if invite.landing_page then
+ print("Web link:", invite.landing_page);
+ end
- if opts.reset then
- local nodeprep = require "prosody.util.encodings".stringprep.nodeprep;
- local username = nodeprep(opts.reset)
- if not username then
- print("Please supply a valid username to generate a reset link for");
- return 2;
+ if invite.additional_data then
+ print("");
+ if invite.additional_data.roles then
+ if invite.additional_data.roles[1] then
+ print("Role:", invite.additional_data.roles[1]);
+ end
+ if invite.additional_data.roles[2] then
+ print("Secondary roles:", table.concat(invite.additional_data.roles, ", ", 2, #invite.additional_data.roles));
+ end
+ end
+ if invite.additional_data.groups then
+ print("Groups:", table.concat(invite.additional_data.groups, ", "));
+ end
+ if invite.additional_data.note then
+ print("Comment:", invite.additional_data.note);
+ end
end
- allow_reset = username;
- end
- local roles = opts.role or {};
- local groups = opts.groups or {};
+ return true, "Invitation valid";
+ end;
+});
- if opts.admin then
- -- Insert it first since we don't get order out of argparse
- table.insert(roles, 1, "prosody:admin");
- end
+module:add_item("shell-command", {
+ section = "invite";
+ section_desc = "Create and manage invitations";
+ name = "delete";
+ desc = "Delete/revoke an invitation token";
+ args = { { name = "host", type = "string" }, { name = "token", type = "string" } };
+ host_selector = "host";
- local invite;
- if allow_reset then
- if roles[1] then
- print("--role/--admin and --reset are mutually exclusive")
- return 2;
- end
- if #groups > 0 then
- print("--group and --reset are mutually exclusive")
+ handler = function (self, host, token) --luacheck: ignore 212/self 212/host
+ local invite, err = delete_account_invite(token);
+ if not invite then return nil, err; end
+ return true, "Invitation deleted";
+ end;
+});
+
+module:add_item("shell-command", {
+ section = "invite";
+ section_desc = "Create and manage invitations";
+ name = "list";
+ desc = "List pending invitations which allow account registration";
+ args = { { name = "host", type = "string" } };
+ host_selector = "host";
+
+ handler = function (self, host) -- luacheck: ignore 212/host
+ local print_row = human_io.table({
+ {
+ title = "Token";
+ key = "invite";
+ width = 24;
+ mapper = function (invite)
+ return invite.token;
+ end;
+ };
+ {
+ title = "Expires";
+ key = "invite";
+ width = 20;
+ mapper = function (invite)
+ return os.date("%Y-%m-%dT%T", invite.expires);
+ end;
+ };
+ {
+ title = "Description";
+ key = "invite";
+ width = "100%";
+ mapper = function (invite)
+ if invite.type == "roster" then
+ return "Contact with "..invite.jid;
+ elseif invite.type == "register" then
+ local jid_user, jid_host = jid_split(invite.jid);
+ if invite.additional_data and invite.additional_data.allow_reset then
+ return "Password reset for "..invite.additional_data.allow_reset.."@"..jid_host;
+ end
+ if jid_user then
+ return "Register on "..jid_host.." with username "..jid_user;
+ end
+ return "Register on "..jid_host;
+ end
+ end;
+ };
+ }, self.session.width);
+
+ self.session.print(print_row());
+ local count = 0;
+ for _, invite in pending_account_invites() do
+ count = count + 1;
+ self.session.print(print_row({ invite = invite }));
end
- invite = assert(invites.create_account_reset(allow_reset));
- else
- invite = assert(invites.create_account(nil, {
- roles = roles,
- groups = groups
- }, opts.expires_after and human_io.parse_duration(opts.expires_after)));
+ return true, ("%d pending invites"):format(count);
+ end;
+});
+
+local subcommands = {};
+
+--- prosodyctl command
+function module.command(arg)
+ local opts = argparse.parse(arg, { short_params = { h = "help"; ["?"] = "help" } });
+ local cmd = table.remove(arg, 1); -- pop command
+ if opts.help or not cmd or not subcommands[cmd] then
+ print("usage: prosodyctl mod_"..module.name.." generate example.com");
+ return 2;
end
+ return subcommands[cmd](arg);
+end
- print(invite.landing_page or invite.uri);
+function subcommands.generate()
+ print("This command is deprecated. Please see 'prosodyctl shell help invite' for available commands.");
+ return 1;
end