view mod_invites/mod_invites.lua @ 3965:2b10e51d85a6

mod_muc_limits: Add config option to limit to join stanzas only This is a bit more limited in pre-0.11 MUC modules, because it just detects stanzas sent to full JIDs (which would include all presence and nick changes). This option is useful for setups where users are typically unaffiliated, but trusted (e.g. if access to the room is gated through some other means such as password/token auth).
author Matthew Wild <mwild1@gmail.com>
date Fri, 03 Apr 2020 12:26:56 +0100
parents 80830d97da81
children f85ea76447dd
line wrap: on
line source

local id = require "util.id";
local url = require "socket.url";
local jid_node = require "util.jid".node;

local invite_ttl = module:get_option_number("invite_expiry", 86400 * 7);

local token_storage = module:open_store("invite_token", "map");

local function get_uri(action, jid, token, params) --> string
	return url.build({
			scheme = "xmpp",
			path = jid,
			query = action..";preauth="..token..(params and (";"..params) or ""),
		});
end

local function create_invite(invite_action, invite_jid, allow_registration)
	local token = id.medium();

	local created_at = os.time();
	local expires = created_at + invite_ttl;

	local invite_params = (invite_action == "roster" and allow_registration) and "ibr=y" or nil;

	local invite = {
		type = invite_action;
		jid = invite_jid;

		token = token;
		allow_registration = allow_registration;

		uri = get_uri(invite_action, invite_jid, token, invite_params);

		created_at = created_at;
		expires = expires;
	};

	module:fire_event("invite-created", invite);

	if allow_registration then
		local ok, err = token_storage:set(nil, token, invite);
		if not ok then
			module:log("warn", "Failed to store account invite: %s", err);
			return nil, "internal-server-error";
		end
	end

	if invite_action == "roster" then
		local username = jid_node(invite_jid);
		local ok, err = token_storage:set(username, token, expires);
		if not ok then
			module:log("warn", "Failed to store subscription invite: %s", err);
			return nil, "internal-server-error";
		end
	end

	return invite;
end

-- Create invitation to register an account (optionally restricted to the specified username)
function create_account(account_username) --luacheck: ignore 131/create_account
	local jid = account_username and (account_username.."@"..module.host) or module.host;
	return create_invite("register", jid, true);
end

-- Create invitation to become a contact of a local user
function create_contact(username, allow_registration) --luacheck: ignore 131/create_contact
	return create_invite("roster", username.."@"..module.host, allow_registration);
end

local valid_invite_methods = {};
local valid_invite_mt = { __index = valid_invite_methods };

function valid_invite_methods:use()
	if self.username then
		-- Also remove the contact invite if present, on the
		-- assumption that they now have a mutual subscription
		token_storage:set(self.username, self.token, nil);
	end
	token_storage:set(nil, self.token, nil);
	return true;
end

-- Get a validated invite (or nil, err). Must call :use() on the
-- returned invite after it is actually successfully used
-- For "roster" invites, the username of the local user (who issued
-- the invite) must be passed.
-- If no username is passed, but the registration is a roster invite
-- from a local user, the "inviter" field of the returned invite will
-- be set to their username.
function get(token, username)
	if not token then
		return nil, "no-token";
	end

	local valid_until, inviter;

	if username then -- token being used for subscription
		-- Fetch from user store (subscription invite)
		valid_until = token_storage:get(username, token);
	else -- token being used for account creation
		-- Fetch from host store (account invite)
		local token_info = token_storage:get(nil, token);
		valid_until = token_info and token_info.expires;
		if token_info.type == "roster" then
			username = jid_node(token_info.jid);
			inviter = username;
		end
	end

	if not valid_until then
		module:log("debug", "Got unknown token: %s", token);
		return nil, "token-invalid";
	elseif os.time() > valid_until then
		module:log("debug", "Got expired token: %s", token);
		return nil, "token-expired";
	end

	return setmetatable({
		token = token;
		username = username;
		inviter = inviter;
	}, valid_invite_mt);
end

function use(token) --luacheck: ignore 131/use
	local invite = get(token);
	return invite and invite:use();
end