Diff

mod_invites_page/mod_invites_page.lua @ 4094:dd00a2b9927c

mod_invites_page: New module to generate landing page for invites
author Matthew Wild <mwild1@gmail.com>
date Fri, 11 Sep 2020 13:52:32 +0100
child 4107:798f284717e7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invites_page/mod_invites_page.lua	Fri Sep 11 13:52:32 2020 +0100
@@ -0,0 +1,144 @@
+local st = require "util.stanza";
+local url_escape = require "util.http".urlencode;
+
+module:depends("http");
+local base_url = module.http_url and module:http_url();
+
+local render_html_template = require"util.interpolation".new("%b{}", st.xml_escape, {
+	urlescape = url_escape;
+	lower = string.lower;
+	classname = function (s) return (s:gsub("%W+", "-")); end;
+	relurl = function (s)
+		if s:match("^%w+://") then
+			return s;
+		end
+		return base_url.."/"..s;
+	end;
+});
+local render_url = require "util.interpolation".new("%b{}", url_escape, {
+	urlescape = url_escape;
+	noscheme = function (url)
+		return (url:gsub("^[^:]+:", ""));
+	end;
+});
+
+module:depends("register_apps");
+module:depends("invites_register_web");
+
+local site_name = module:get_option_string("site_name", module.host);
+local site_apps = module:shared("register_apps/apps");
+
+local http_files;
+
+if prosody.shutdown then
+	module:depends("http");
+	http_files = module:depends("http_files");
+end
+local invites = module:depends("invites");
+
+-- Point at eg https://github.com/ge0rg/easy-xmpp-invitation
+-- This URL must always be absolute, as it is shared standalone
+local invite_url_template = module:get_option_string("invites_page", base_url and (base_url.."?{invite.token}") or nil);
+-- This URL is relative to the invite page, or can be absolute
+local register_url_template = module:get_option_string("invites_registration_page", "register?t={invite.token}&c={app.id}");
+
+local function add_landing_url(invite)
+	if not invite_url_template then return; end
+	-- TODO: we don't currently have a landing page for subscription-only invites,
+	-- so the user will only receive a URI. The client should be able to handle this
+	-- by automatically falling back to a client-specific landing page, per XEP-0401.
+	if not invite.allow_registration then return; end
+	invite.landing_page = render_url(invite_url_template, { host = module.host, invite = invite });
+end
+
+module:hook("invite-created", add_landing_url);
+
+local function render_app_urls(apps, invite_vars)
+	local rendered_apps = {};
+	for _, unrendered_app in ipairs(apps) do
+		local app = setmetatable({}, { __index = unrendered_app });
+		local template_vars = { app = app, invite = invite_vars, base_url = base_url };
+		if app.magic_link_format then
+			-- Magic link generally links directly to third-party
+			app.proceed_url = render_url(app.magic_link_format or app.link or "#", template_vars);
+		elseif app.supports_preauth_uri then
+			-- Proceed to a page that guides the user to download, and then
+			-- click the URI button
+			app.proceed_url = render_url("{base_url!}/setup/{app.id}?{invite.token}", template_vars);
+		else
+			-- Manual means proceed to web registration, but include app id
+			-- so it can show post-registration instructions
+			app.proceed_url = render_url(register_url_template, template_vars);
+		end
+		table.insert(rendered_apps, app);
+	end
+	return rendered_apps;
+end
+
+function serve_invite_page(event)
+	local invite_page_template = assert(module:load_resource("html/invite.html")):read("*a");
+	local invalid_invite_page_template = assert(module:load_resource("html/invite_invalid.html")):read("*a");
+
+	local invite = invites.get(event.request.url.query);
+	if not invite then
+		return render_html_template(invalid_invite_page_template, {
+			site_name = site_name;
+			static = base_url.."/static";
+		});
+	end
+
+	local template_vars = {
+		site_name = site_name;
+		token = invite.token;
+		uri = invite.uri;
+		type = invite.type;
+		jid = invite.jid;
+		inviter = invite.inviter;
+		static = base_url.."/static";
+	};
+	template_vars.apps = render_app_urls(site_apps, template_vars);
+
+	local invite_page = render_html_template(invite_page_template, template_vars);
+	return invite_page;
+end
+
+function serve_setup_page(event, app_id)
+	local invite_page_template = assert(module:load_resource("html/client.html")):read("*a");
+	local invalid_invite_page_template = assert(module:load_resource("html/invite_invalid.html")):read("*a");
+
+	local invite = invites.get(event.request.url.query);
+	if not invite then
+		return render_html_template(invalid_invite_page_template, {
+			site_name = site_name;
+			static = base_url.."/static";
+		});
+	end
+
+	local template_vars = {
+		site_name = site_name;
+		apps = site_apps;
+		token = invite.token;
+		uri = invite.uri;
+		type = invite.type;
+		jid = invite.jid;
+		static = base_url.."/static";
+	};
+	template_vars.app = render_app_urls({ site_apps[app_id] }, template_vars)[1];
+
+	local invite_page = render_html_template(invite_page_template, template_vars);
+	return invite_page;
+end
+
+local mime_map = {
+	png = "image/png";
+	svg = "image/svg+xml";
+	js  = "application/javascript";
+};
+
+module:provides("http", {
+	route = {
+		["GET"] = serve_invite_page;
+		["GET /setup/*"] = serve_setup_page;
+		["GET /static/*"] = http_files and http_files.serve({ path = module:get_directory().."/static", mime_map = mime_map });
+	};
+});