File

mod_invites_register_web/mod_invites_register_web.lua @ 6302:06fbbd45ba75

mod_cloud_notify: Readme: fix links and labels that were removed in the last commit diff --git a/mod_cloud_notify/README.md b/mod_cloud_notify/README.md --- a/mod_cloud_notify/README.md +++ b/mod_cloud_notify/README.md @@ -1,3 +1,9 @@ +---- +-labels: +-- 'Stage-Beta' +-summary: 'XEP-0357: Cloud push notifications' +---- + # Introduction This module enables support for sending "push notifications" to clients @@ -32,15 +38,15 @@ notification to your device. When your d it will display it or wake up the app so it can connect to XMPP and receive any pending messages. -This protocol is described for developers in \[XEP-0357: Push -Notifications\]. +This protocol is described for developers in [XEP-0357: Push +Notifications]. -For this module to work reliably, you must have \[mod_smacks\], -\[mod_mam\] and \[mod_carbons\] also enabled on your server. +For this module to work reliably, you must have [mod_smacks], +[mod_mam] and [mod_carbons] also enabled on your server. Some clients, notably Siskin and Snikket iOS need some additional extensions that are not currently defined in a standard XEP. To support -these clients, see \[mod_cloud_notify_extensions\]. +these clients, see [mod_cloud_notify_extensions]. # Configuration @@ -58,18 +64,18 @@ these clients, see \[mod_cloud_notify_ex # Internal design notes App servers are notified about offline messages, messages stored by -\[mod_mam\] or messages waiting in the smacks queue. The business rules +[mod_mam] or messages waiting in the smacks queue. The business rules outlined [here](//mail.jabber.org/pipermail/standards/2016-February/030925.html) are all honored[^2]. -To cooperate with \[mod_smacks\] this module consumes some events: +To cooperate with [mod_smacks] this module consumes some events: `smacks-ack-delayed`, `smacks-hibernation-start` and `smacks-hibernation-end`. These events allow this module to send out notifications for messages received while the session is hibernated by -\[mod_smacks\] or even when smacks acknowledgements for messages are +[mod_smacks] or even when smacks acknowledgements for messages are delayed by a certain amount of seconds configurable with the -\[mod_smacks\] setting `smacks_max_ack_delay`. +[mod_smacks] setting `smacks_max_ack_delay`. The `smacks_max_ack_delay` setting allows to send out notifications to clients which aren't already in smacks hibernation state (because the
author Menel <menel@snikket.de>
date Fri, 13 Jun 2025 10:44:37 +0200
parent 6264:29a646bad096
child 6273:8ceedc336d0d
line wrap: on
line source

local id = require "util.id";
local http_formdecode = require "net.http".formdecode;
local usermanager = require "core.usermanager";
local modulemanager = require "core.modulemanager";
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 render_url = require "util.interpolation".new("%b{}", url_escape, {
	urlescape = url_escape;
	noscheme = function (url)
		return (url:gsub("^[^:]+:", ""));
	end;
});

module:depends("register_apps");
local mod_password_policy = module:depends("password_policy");

local site_name = module:get_option_string("site_name", module.host);
local site_apps = module:shared("register_apps/apps");

local webchat_url = module:get_option_string("webchat_url");

-- If not provided, but mod_conversejs is loaded, default to that
if not webchat_url and modulemanager.get_modules_for_host(module.host):contains("conversejs") then
	local conversejs = module:depends("conversejs");
	webchat_url = conversejs.module:http_url();
end

module:depends("http");

local invites = module:depends("invites");
local invites_page = module:depends("invites_page");

local template_path = module:get_option_path("invites_register_template_path", "html");
function serve_register_page(event)
	local register_page_template = assert(module:load_resource(template_path .. "/register.html")):read("*a");

	local query_params = event.request.url.query and http_formdecode(event.request.url.query);

	local invite = query_params and invites.get(query_params.t);
	if not invite then
		return {
			status_code = 303;
			headers = {
				["Location"] = invites_page.module:http_url().."?"..(event.request.url.query or "");
			};
		};
	end

	event.response.headers["Content-Type"] = "text/html; charset=utf-8";

	local invite_page = render_html_template(register_page_template, {
		site_name = site_name;
		token = invite.token;
		domain = module.host;
		uri = invite.uri;
		type = invite.type;
		jid = invite.jid;
		inviter = invite.inviter;
		app = query_params.c and site_apps[query_params.c];
		password_policy = mod_password_policy.get_policy();
	});
	return invite_page;
end

function handle_register_form(event)
	local request, response = event.request, event.response;
	local form_data = http_formdecode(request.body);
	local user, password, token = form_data["user"], form_data["password"], form_data["token"];
	local app_id = form_data["app_id"];

	local register_page_template = assert(module:load_resource(template_path .. "/register.html")):read("*a");
	local error_template = assert(module:load_resource(template_path .. "/register_error.html")):read("*a");

	local invite = invites.get(token);
	if not invite then
		return {
			status_code = 303;
			headers = {
				["Location"] = invites_page.module:http_url().."?"..event.request.url.query;
			};
		};
	end

	event.response.headers["Content-Type"] = "text/html; charset=utf-8";

	if not user or #user == 0 or not password or #password == 0 or not token then
		return render_html_template(register_page_template, {
			site_name = site_name;
			token = invite.token;
			domain = module.host;
			uri = invite.uri;
			type = invite.type;
			jid = invite.jid;
			password_policy = mod_password_policy.get_policy();

			msg_class = "alert-warning";
			message = "Please fill in all fields.";
		});
	end

	-- Shamelessly copied from mod_register_web.
	local prepped_username = nodeprep(user);

	if not prepped_username or #prepped_username == 0 then
		return render_html_template(register_page_template, {
			site_name = site_name;
			token = invite.token;
			domain = module.host;
			uri = invite.uri;
			type = invite.type;
			jid = invite.jid;
			password_policy = mod_password_policy.get_policy();

			msg_class = "alert-warning";
			message = "This username contains invalid characters.";
		});
	end

	if usermanager.user_exists(prepped_username, module.host) then
		return render_html_template(register_page_template, {
			site_name = site_name;
			token = invite.token;
			domain = module.host;
			uri = invite.uri;
			type = invite.type;
			jid = invite.jid;
			password_policy = mod_password_policy.get_policy();

			msg_class = "alert-warning";
			message = "This username is already in use.";
		});
	end

	local pw_ok, pw_error = mod_password_policy.check_password(password, {
		username = prepped_username;
	});
	if not pw_ok then
		return render_html_template(register_page_template, {
			site_name = site_name;
			token = invite.token;
			domain = module.host;
			uri = invite.uri;
			type = invite.type;
			jid = invite.jid;
			password_policy = mod_password_policy.get_policy();

			msg_class = "alert-warning";
			message = pw_error;
		});
	end

	local registering = {
		validated_invite = invite;
		username = prepped_username;
		host = module.host;
		ip = request.ip;
		allowed = true;
	};

	module:fire_event("user-registering", registering);

	if not registering.allowed then
		return render_html_template(error_template, {
			site_name = site_name;
			msg_class = "alert-danger";
			message = registering.reason or "Registration is not allowed.";
		});
	end

	local ok, err = usermanager.create_user(prepped_username, password, module.host);

	if ok then
		module:fire_event("user-registered", {
			username = prepped_username;
			host = module.host;
			source = "mod_"..module.name;
			validated_invite = invite;
			ip = request.ip;
		});

		local app_info = site_apps[app_id];

		local success_template;
		if app_info then
			if app_info.login_link_format then
				local redirect_url = render_url(app_info.login_link_format, {
					site_name = site_name;
					username = prepped_username;
					domain = module.host;
					password = password;
					app = app_info;
				});
				return {
					status_code = 303;
					headers = {
						["Location"] = redirect_url;
					};
				};
			end
			-- If recognised app, we serve a page that includes setup instructions
			success_template = assert(module:load_resource(template_path .. "/register_success_setup.html")):read("*a");
		else
			success_template = assert(module:load_resource(template_path .. "/register_success.html")):read("*a");
		end

		-- Due to the credentials being served here, ensure that
		-- the browser or any intermediary does not cache the page
		event.response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
		event.response.headers["Pragma"] = "no-cache";
		event.response.headers["Expires"] = "0";

		return render_html_template(success_template, {
			site_name = site_name;
			username = prepped_username;
			domain = module.host;
			password = password;
			app = app_info;
			webchat_url = webchat_url;
		});
	else
		local err_id = id.short();
		module:log("warn", "Registration failed (%s): %s", err_id, tostring(err));
		return render_html_template(error_template, {
			site_name = site_name;
			msg_class = "alert-danger";
			message = ("An unknown error has occurred (%s)"):format(err_id);
		});
	end
end

module:provides("http", {
	default_path = "register";
	route = {
		["GET"] = serve_register_page;
		["POST"] = handle_register_form;
	};
});