Changeset

4184:9127fa98ee1e

mod_welcome_page: New module to provide a friendly entrypoint to invite-based setups
author Matthew Wild <mwild1@gmail.com>
date Fri, 09 Oct 2020 12:19:46 +0100
parents 4183:ad9ce6750880
children 4185:06a9ac93e2f1
files mod_welcome_page/README.markdown mod_welcome_page/html/index.html mod_welcome_page/mod_welcome_page.lua
diffstat 3 files changed, 175 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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;
+	};
+});