mod_welcome_page: New module to provide a friendly entrypoint to invite-based setups
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_welcome_page/README.markdown Fri Oct 09 12:19:46 2020 +0100
@@ -0,0 +1,47 @@
+---
+labels:
+- 'Stage-Beta'
+summary: 'Serve a welcome page to users'
+rockspec:
+ dependencies:
+ - mod_invites
+ build:
+ copy_directories:
+ - html
+...
+
+Introduction
+============
+
+This module serves a welcome page to users, and allows them to create an
+account invite via the web on invite-only servers.
+
+The page template and policy of when to allow account creation are both
+possible to override.
+
+This module is part of the suite of modules that implement invite-based
+account registration for Prosody. The other modules are:
+
+- mod_invites
+- mod_invites_adhoc
+- mod_invites_page
+- mod_invites_register
+- mod_invites_register_web
+- mod_register_apps
+
+For details and a full overview, start with the mod_invites documentation.
+
+Configuration
+=======
+
+`welcome_page_template_path`
+: The path to a directory containing the page templates and assets. See
+ the module source for the example template.
+
+`welcome_page_variables`
+: Optional variables to pass to the template, available as `{var.name}`
+
+`welcome_page_open_registration`
+: Whether to allow account creation in the absence of any other plugin
+ overriding the policy. Defaults to `false` unless `registration_invite_only`
+ is set to `false`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_welcome_page/html/index.html Fri Oct 09 12:19:46 2020 +0100
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>{site_name}</title>
+ <link rel="stylesheet" href="/share/bootstrap4/css/bootstrap.min.css">
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
+ <link rel="manifest" href="/site.webmanifest">
+ <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
+ <meta name="msapplication-TileColor" content="#fbd308">
+ <meta name="theme-color" content="#fbd308">
+ <style>
+ #background {
+ z-index: -1;
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: -webkit-linear-gradient(to left,#0cd0f3,#dfd18e);
+ background: linear-gradient(to left,#0cd0f3,#dfd18e);
+ opacity: 0.8;
+ }
+ .jumbotron {
+ opacity: 0.8;
+ }
+ </style>
+</head>
+<body>
+ <div id="background" class="fixed-top overflow-hidden"></div>
+
+ <div class="jumbotron m-md-3">
+ <h1 class="display-4">{site_name}</h1>
+ <p class="lead">Welcome to our chat service</p>
+ <hr class="my-4">
+ <p>{site_name} is an XMPP chat service.</p>
+
+ {message&<div class="alert {message.class?alert-info}" role="alert">
+ {message.text}
+ </div>}
+
+ <form method="POST" action="/" class="d-inline">
+ <button class="btn btn-primary btn-lg mb-2" type="submit">Create account</button>
+ </form>
+
+ {var.links&{var.links#
+ <a class="btn btn-{item.class?secondary} btn-lg mb-2" href="{item.href}" role="button">{item.text}</a>
+ }}
+
+ {var.webchat&<p class="pt-2">Already have an account here? <a href="{var.webchat}">Log in via the web chat</a></p>}
+ <div>
+
+ <script src="/share/jquery/jquery.min.js"></script>
+ <script src="/share/bootstrap4/js/bootstrap.min.js"></script>
+</body>
+</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_welcome_page/mod_welcome_page.lua Fri Oct 09 12:19:46 2020 +0100
@@ -0,0 +1,71 @@
+local st = require "util.stanza";
+local url_escape = require "util.http".urlencode;
+local render_html_template = require"util.interpolation".new("%b{}", st.xml_escape, {
+ urlescape = url_escape;
+});
+
+local template_path = module:get_option_string("welcome_page_template_path", module:get_directory().."/html");
+local user_vars = module:get_option("welcome_page_variables", {});
+local site_name = module:get_option("site_name", module.host);
+local invite_only = module:get_option_boolean("registration_invite_only", true);
+local open_registration = module:get_option_boolean("welcome_page_open_registration", not invite_only);
+
+module:depends("http");
+local invites = module:depends("invites");
+
+local function load_template(path)
+ local template_file, err = io.open(path);
+ if not template_file then
+ error("Unable to load template file: "..tostring(err));
+ end
+ local template = template_file:read("*a");
+ template_file:close();
+ return template;
+end
+
+local template = load_template(template_path.."/index.html");
+
+local function serve_page(event)
+ event.response.headers["Content-Type"] = "text/html; charset=utf-8";
+ return render_html_template(template, {
+ site_name = site_name;
+ request = event.request;
+ var = user_vars;
+ });
+end
+
+local function handle_submit(event)
+ local submission = { allowed = open_registration, request = event.request };
+ module:fire_event("mod_welcome_page/submission", submission);
+ if not submission.allowed then
+ event.response.headers["Content-Type"] = "text/html; charset=utf-8";
+ return render_html_template(template, {
+ site_name = site_name;
+ request = event.request;
+ var = user_vars;
+ message = {
+ class = "alert-danger";
+ text = submission.reason or "Account creation is not possible at this time";
+ };
+ });
+ end
+
+ local invite = invites.create_account(nil, { source = module.name });
+ if not invite then
+ return 500;
+ end
+
+ event.response.headers.Location = invite.landing_page or invite.uri;
+
+ return 303;
+end
+
+local http_files = module:depends("http_files");
+
+module:provides("http", {
+ route = {
+ ["GET"] = serve_page;
+ ["GET /*"] = http_files.serve({ path = template_path });
+ ["POST"] = handle_submit;
+ };
+});