Changeset

5943:05125c29fd67

mod_invites_webgen: initial release
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Wed, 26 Feb 2025 17:36:14 +0700
parents 5942:abd1bbe5006e
children 5944:d073ada49a86
files mod_invites_webgen/README.markdown mod_invites_webgen/html/webgen.en.html mod_invites_webgen/html/webgen.html mod_invites_webgen/mod_invites_webgen.lua
diffstat 4 files changed, 395 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invites_webgen/README.markdown	Wed Feb 26 17:36:14 2025 +0700
@@ -0,0 +1,61 @@
+---
+labels:
+- 'Stage-Alpha'
+summary: 'Enable create invitations via the web'
+...
+
+Introduction
+============
+
+Not every client would implement the create invitation feature. Thus, this
+module allows users to create new invites from the server's website.
+
+Details
+=======
+
+The invitation will create account-and-contact invites (the "add to roster"
+kind of invite). Once user have sucessfully enter their credential, the module
+redirect them to the invite page.
+
+It depends on these module and their purpose:
+
+- [mod_http]: for obvious reason :).
+- [mod_invites][doc:modules:mod_invites]: generate the actual invite.
+- [mod_invites_register_web][doc:modules:mod_invites_register]: serve the
+  invite page.
+
+Configuration
+=============
+
+The configuration utilizes the same `allow_user_invites` and
+`allow_contact_invites` as [mod_invites_adhoc]. 
+
+If you specify a `site_name`, it will uses that else it will fallback to the
+`VirtualHost` that it was loaded onto.
+
+You can use your own html templates with `invites_template_html`. Names of the
+files MUST match the default. More over, you can offer multiple (human)
+languages by adding the `&l=` to the URL. Meaning this module will serve
+`register.html` for your default URL:
+```
+
+    https://prosody.example.net/?=aowiefjoaij
+
+``` 
+
+And if you have a `register.en.html` in the directory you have specified in
+your config file, it will be served at:
+```
+
+    https://prosody.example.net/?=aowiefjoaij&l=en
+
+```
+
+So in your `register.html`, you can point to the English version by using an
+`<a>` tag like this:
+```
+
+    <a href="/?={token}&l=en">English</a>
+
+```
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invites_webgen/html/webgen.en.html	Wed Feb 26 17:36:14 2025 +0700
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8;" />
+
+<title>Register</title>
+<meta name="description" content="Register for standard account `@{site_name}`." />
+
+<meta name="author" content="Trần H. Trung" /> 
+<meta name="HandheldFriendly" content="true" />
+<meta name="viewport" content="width=device-width, initial-scale=1" /> 
+
+<style>
+code { 
+    font-family: Monospace; 
+    color: #fff;
+    background: #000;
+    padding: 2px;
+    border-radius: 5px;
+}
+main {
+    width: 960px;
+    margin: auto;
+}
+h1, h2, h3, h4, h5, h6 { text-align: center; }
+table {
+    width: 100%;
+    padding: 5px;
+    text-align: center;
+    background: #000;
+    color: #fff;
+}
+.even { background: #444; }
+.odd { background: #555; }
+table caption { 
+    background: #000;
+    color: #fff;
+    margin: auto;
+    padding: 5px;
+}
+</style>
+</head>
+
+<body>
+
+<div style="height: 2rem;"></div>
+
+<main>
+<hr/>
+
+<h1>Register</h1>
+
+<h4><a href='/webgen?&l=en' title='tiếng Việt' rel='noopener noreferer' >« tiếng Việt »</a></h4>
+
+<p>To register for a <a href='' title='Server - Standard Address' rel='noopener noreferer' >account</a>
+<code>@{site_name}</code>, you need <a href='' title='Server - Invites' rel='noopener noreferer' >an invite.</a></p>
+
+<p>To generate an invite with your account, you can authenticate below:</p>
+
+<form method="post">
+
+<table>
+
+<caption align="bottom">…{message&{message}!}</caption>
+
+<colgroup>
+<col width="52%" />
+<col width="48%" />
+</colgroup>
+
+<tr class="header">
+<th align="center">Username</th>
+<th align="center">Password</th>
+</tr>
+
+<tr class="odd">
+<td align="left"><div style="text-align: center;">
+<p><input required
+       name="username"
+       aria-describedby="usernameHelp"
+       minlength="1"
+       maxlength="32"
+       length="32"><code>@{site_name}</code>
+</input></p></div></td>
+<td align="left"><div style="text-align: center;">
+<p><input required
+       type="password"
+       name="password"
+       aria-describedby="passwordHelp"
+       autocomplete="new-password"
+       minlength="\{password_policy.length\}">
+</input></p></div></td>
+</tr>
+
+<tr class='even'>
+<td colspan="2">
+<h3>
+<button type="submit">
+Generate
+</button>
+</h3>
+</td>
+</tr>
+</table>
+
+</form>
+
+<p>If you're new to XMPP and yet to know anybody, <a href='' title='Trung - Contact' target='_blank' rel='noopener noreferer' >contact me.</a></p>
+
+<h4><a href='#top' title='top' rel='noopener noreferer' >~*~</a></h4>
+
+<hr/>
+</main>
+
+<div style="height: 4rem;"></div>
+
+</body>
+
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invites_webgen/html/webgen.html	Wed Feb 26 17:36:14 2025 +0700
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html lang="vi">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8;" />
+
+<title>Đăng Ký</title>
+<meta name="description" content="Đăng ký tài khoản tiêu chuẩn `@{site_name}`." />
+
+<meta name="author" content="Trần H. Trung" />
+<meta name="HandheldFriendly" content="true" /><!-- mobile device -->
+<meta name="viewport" content="width=device-width, initial-scale=1" /><!-- mobile device --> 
+
+<style>
+code { 
+    font-family: Monospace; 
+    color: #fff;
+    background: #000;
+    padding: 2px;
+    border-radius: 5px;
+}
+main {
+    width: 960px;
+    margin: auto;
+}
+h1, h2, h3, h4, h5, h6 { text-align: center; }
+table {
+    width: 100%;
+    padding: 5px;
+    text-align: center;
+    background: #000;
+    color: #fff;
+}
+.even { background: #444; }
+.odd { background: #555; }
+table caption { 
+    background: #000;
+    color: #fff;
+    margin: auto;
+    padding: 5px;
+}
+</style>
+</head>
+
+<body>
+
+<div style="height: 2rem;"></div>
+
+<main>
+<hr/>
+
+<h1>Đăng Ký</h1>
+
+<h4><a href='/webgen?&l=en' title='English' rel='noopener noreferer' >« English »</a></h4>
+
+<p>Để đăng ký <a href='' title='Tài Liệu - Tài Khoản' rel='noopener noreferer'>tài khoản
+<code>@{site_name}</code></a>, bạn cần có <a href='' title='Tài Liệu - Mời Đăng Ký' rel='noopener noreferer' >lời mời.</a></p>
+
+<p>Để tạo lời mời với tài khoản của mình, bạn có thể xác danh dưới đây:</p>
+
+<form method="post">
+
+<table>
+
+<caption align="bottom">…{message&{message}!}</caption>
+
+<colgroup>
+<col width="52%" />
+<col width="48%" />
+</colgroup>
+
+<tr class="header">
+<th align="center">Tên Đăng Nhập</th>
+<th align="center">Mật Khẩu</th>
+</tr>
+
+<tr class="odd">
+<td align="left"><div style="text-align: center;">
+<p><input required
+       name="username"
+       aria-describedby="usernameHelp"
+       minlength="1"
+       maxlength="32"
+       length="32"><code>@{site_name}</code>
+</input></p></div></td>
+<td align="left"><div style="text-align: center;">
+<p><input required
+       type="password"
+       name="password"
+       aria-describedby="passwordHelp"
+       autocomplete="new-password"
+       minlength="\{password_policy.length\}">
+</input></p></div></td>
+</tr>
+
+<tr class='even'>
+<td colspan='2'>
+<h3>
+<button type="submit">
+Tạo
+</button>
+</h3>
+</td>
+</tr>
+
+</table>
+
+</form>
+
+<p>Nếu XMPP còn mới và bạn chưa quen ai, hãy <a href='' title='Liên Lạc' rel='noopener noreferer' >liên lạc.</a></p>
+
+<h4><a href='#top' title='top' rel='noopener noreferer' >~*~</a></h4>
+
+<hr/>
+</main>
+</div>
+
+<div style="height: 4rem;"></div>
+
+</body>
+
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_invites_webgen/mod_invites_webgen.lua	Wed Feb 26 17:36:14 2025 +0700
@@ -0,0 +1,94 @@
+local http_formdecode = require "net.http".formdecode;
+local usermanager = require "core.usermanager";
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+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 site_name = module:get_option_string("site_name", module.host);
+
+local invites = module:depends("invites");
+--local template_get = module:depends("invites_page").template_get;
+
+local templatePath = module:get_option_string("invites_template_html", "html");
+function template_get(filename, lang)
+	local template = lang and templatePath.."/"..filename.."."..lang..".html" 
+		or templatePath.."/"..filename..".html";
+	return assert(module:load_resource(template):read("*a"));
+end
+
+
+module:depends("http");
+
+local default_path, lang;
+
+local function serve_webgen(event)
+	local query_params = event.request.url.query and http_formdecode(event.request.url.query);
+	lang = query_params and query_params.l;
+
+	local webgen_template = template_get("webgen", lang);
+	event.response.headers["Content-Type"] = "text/html; charset=utf-8";
+
+	local webgen = render_html_template(webgen_template, {
+		site_name = site_name;
+	});
+	return webgen;
+end
+
+module:depends("invites_register_web");
+
+local function handle_webgen(event)
+	local request, response = event.request, event.response;
+	local form_data = http_formdecode(request.body);
+	local username, password = form_data["username"], form_data["password"];
+
+	event.response.headers["Content-Type"] = "text/html; charset=utf-8";
+
+	local webgen_template = template_get("webgen", lang);
+	local htmlPath = module:http_url("webgen", "/webgen");
+
+	if not username or #username == 0 or not password or #password == 0 then
+		return render_html_template(webgen_template, {
+			site_name = site_name;
+			path = htmlPath;
+			msg_class = "alert-warning";
+			message = "Please fill in all fields.";
+		});
+	end
+
+	local prepped_username = nodeprep(username);
+	local exists = usermanager.user_exists(prepped_username, module.host);
+	local pass = usermanager.test_password(prepped_username, module.host, password);
+	local allow_user = module:get_option_boolean("allow_user_invites", false);
+	local allow_contact = module:get_option_boolean("allow_contact_invites", false);
+
+	local invite;
+	if exists and pass and allow_user and allow_contact then
+		invite = invites.create_contact(username, true, { source = "webgen" });
+	else
+		return render_html_template(webgen_template, {
+			site_name = site_name;
+			path = htmlPath;
+			username = username;
+			msg_class = "alert-warning";
+			message = "Invalid input!";
+		});
+	end
+
+	if not invite then
+		return 500;
+	else
+		event.response.headers.Location = invite.landing_page or invite.uri;
+		return 303;
+	end
+end
+
+module:provides("http", {
+	default_path = "webgen";
+	route = {
+		["GET"] = serve_webgen;
+		["POST"] = handle_webgen;
+	};
+});