Software /
code /
prosody-modules
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 }); + }; +});