Diff

mod_invite/mod_invite.lua @ 2058:4b3037c7af62

mod_invite: Allows existing users to generate URLs that can be used to invite new users. Mutual presence subscriptions are automatically created when the creation succeeds.
author Thijs Alkemade <me@thijsalkema.de>
date Wed, 02 Mar 2016 18:12:34 +0100
child 2238:dc4e77824318
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invite/mod_invite.lua	Wed Mar 02 18:12:34 2016 +0100
@@ -0,0 +1,157 @@
+local adhoc_new = module:require "adhoc".new;
+local uuid_new = require "util.uuid".generate;
+local jid_split = require "util.jid".split;
+local jid_join = require "util.jid".join;
+local http_formdecode = require "net.http".formdecode;
+local http_urlencode = require "net.http".urlencode;
+local usermanager = require "core.usermanager";
+local rostermanager = require "core.rostermanager";
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+local tostring = tostring;
+
+local invite_storage = module:open_store();
+local inviter_storage = module:open_store("inviter");
+
+local serve = module:depends"http_files".serve;
+
+module:depends"http";
+
+local entities = {
+	["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;",
+	["'"] = "&apos;", ["\""] = "&quot;", ["\n"] = "<br/>",
+};
+
+local function tohtml(plain)
+	return (plain:gsub("[<>&'\"\n]", entities));
+end
+
+local function apply_template(template, args)
+	return
+		template:gsub("{{([^}]*)}}", function (k)
+			if args[k] then
+				return tohtml(args[k])
+			else
+				return k
+			end
+		end)
+end
+
+function generate_page(event, display_options)
+	local request, response = event.request, event.response;
+
+	local tokens = invite_storage:get() or {};
+
+	local token = request.path:match("^/invite/([^/]*)$");
+
+	response.headers.content_type = "text/html; charset=utf-8";
+
+	if not token or not tokens[token] then
+		local template = assert(module:load_resource("invite/invite_result.html")):read("*a");
+
+		return apply_template(template, { classes = "alert-danger", message = "This invite has expired." })
+	end
+
+	local template = assert(module:load_resource("invite/invite.html")):read("*a");
+
+	return apply_template(template, { user = jid_join(tokens[token], module.host), server = module.host, token = token });
+end
+
+function subscribe(user1, user2)
+	local user1_jid = jid_join(user1, module.host);
+	local user2_jid = jid_join(user2, module.host);
+
+	rostermanager.set_contact_pending_out(user2, module.host, user1_jid);
+	rostermanager.set_contact_pending_in(user1, module.host, user2_jid);
+	rostermanager.subscribed(user1, module.host, user2_jid);
+	rostermanager.process_inbound_subscription_approval(user2, module.host, user1_jid);
+end
+
+function handle_form(event, display_options)
+	local request, response = event.request, event.response;
+	local form_data = http_formdecode(request.body);
+	local user, password, token = form_data["user"], form_data["password"], form_data["token"];
+	local tokens = invite_storage:get() or {};
+
+	local template = assert(module:load_resource("invite/invite_result.html")):read("*a");
+
+	response.headers.content_type = "text/html; charset=utf-8";
+
+	if not user or #user == 0 or not password or #password == 0 or not token then
+		return apply_template(template, { classes = "alert-warning", message = "Please fill in all fields." })
+	end
+
+	if not tokens[token] then
+		return apply_template(template, { classes = "alert-danger", message = "This invite has expired." })
+	end
+
+	-- Shamelessly copied from mod_register_web.
+	local prepped_username = nodeprep(user);
+
+	if not prepped_username or #prepped_username == 0 then
+		return apply_template(template, { classes = "alert-warning", message = "This username contains invalid characters." })
+	end
+
+	if usermanager.user_exists(prepped_username, module.host) then
+		return apply_template(template, { classes = "alert-warning", message = "This username is already in use." })
+	end
+
+	local registering = { username = prepped_username , host = module.host, allowed = true }
+	
+	module:fire_event("user-registering", registering);
+
+	if not registering.allowed then
+		return apply_template(template, { classes = "alert-danger", message = "Registration is not allowed." })
+	end
+
+	local ok, err = usermanager.create_user(prepped_username, password, module.host);
+
+	if ok then
+		subscribe(prepped_username, tokens[token]);
+		subscribe(tokens[token], prepped_username);
+
+		inviter_storage:set(prepped_username, { inviter = tokens[token] });
+
+		rostermanager.roster_push(tokens[token], module.host, jid_join(prepped_username, module.host));
+
+		tokens[token] = nil;
+
+		invite_storage:set(nil, tokens);
+
+		return apply_template(template, { classes = "alert-success", message = "Your account has been created! You can now log in using an XMPP client." })
+	else
+		module:log("debug", "Registration failed: " .. tostring(err));
+
+		return apply_template(template, { classes = "alert-danger", message = "An unknown error has occurred." })
+	end
+end
+
+module:provides("http", {
+	route = {
+		["GET /a_file.txt"] = serve(module:get_directory().."/my_file.txt");
+		["GET /bootstrap.min.css"] = serve(module:get_directory());
+		["GET /*"] = generate_page;
+		POST = handle_form;
+	};
+});
+
+function invite_command_handler(self, data, state)
+	local uuid = uuid_new();
+
+	local user, host = jid_split(data.from);
+
+	if host ~= module.host then
+		return { status = "completed", error = { message = "You are not allowed to invite users to this server." }};
+	end
+
+	local tokens = invite_storage:get() or {};
+
+	tokens[uuid] = user;
+
+	invite_storage:set(nil, tokens);
+
+	return { info = module:http_url() .. "/" .. uuid, status = "completed" };
+end
+
+local adhoc_invite = adhoc_new("Invite user", "invite", invite_command_handler, "user")
+
+module:add_item("adhoc", adhoc_invite);
\ No newline at end of file