Software /
code /
prosody-modules
Diff
mod_invite/mod_invite.lua @ 2058:4b3037c7af62
mod_invite: Allows existing users to generate URLs that can be used to invite new users. Mutual presence subscriptions are automatically created when the creation succeeds.
author | Thijs Alkemade <me@thijsalkema.de> |
---|---|
date | Wed, 02 Mar 2016 18:12:34 +0100 |
child | 2238:dc4e77824318 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invite/mod_invite.lua Wed Mar 02 18:12:34 2016 +0100 @@ -0,0 +1,157 @@ +local adhoc_new = module:require "adhoc".new; +local uuid_new = require "util.uuid".generate; +local jid_split = require "util.jid".split; +local jid_join = require "util.jid".join; +local http_formdecode = require "net.http".formdecode; +local http_urlencode = require "net.http".urlencode; +local usermanager = require "core.usermanager"; +local rostermanager = require "core.rostermanager"; +local nodeprep = require "util.encodings".stringprep.nodeprep; +local tostring = tostring; + +local invite_storage = module:open_store(); +local inviter_storage = module:open_store("inviter"); + +local serve = module:depends"http_files".serve; + +module:depends"http"; + +local entities = { + ["<"] = "<", [">"] = ">", ["&"] = "&", + ["'"] = "'", ["\""] = """, ["\n"] = "<br/>", +}; + +local function tohtml(plain) + return (plain:gsub("[<>&'\"\n]", entities)); +end + +local function apply_template(template, args) + return + template:gsub("{{([^}]*)}}", function (k) + if args[k] then + return tohtml(args[k]) + else + return k + end + end) +end + +function generate_page(event, display_options) + local request, response = event.request, event.response; + + local tokens = invite_storage:get() or {}; + + local token = request.path:match("^/invite/([^/]*)$"); + + response.headers.content_type = "text/html; charset=utf-8"; + + if not token or not tokens[token] then + local template = assert(module:load_resource("invite/invite_result.html")):read("*a"); + + return apply_template(template, { classes = "alert-danger", message = "This invite has expired." }) + end + + local template = assert(module:load_resource("invite/invite.html")):read("*a"); + + return apply_template(template, { user = jid_join(tokens[token], module.host), server = module.host, token = token }); +end + +function subscribe(user1, user2) + local user1_jid = jid_join(user1, module.host); + local user2_jid = jid_join(user2, module.host); + + rostermanager.set_contact_pending_out(user2, module.host, user1_jid); + rostermanager.set_contact_pending_in(user1, module.host, user2_jid); + rostermanager.subscribed(user1, module.host, user2_jid); + rostermanager.process_inbound_subscription_approval(user2, module.host, user1_jid); +end + +function handle_form(event, display_options) + 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 tokens = invite_storage:get() or {}; + + local template = assert(module:load_resource("invite/invite_result.html")):read("*a"); + + 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 apply_template(template, { classes = "alert-warning", message = "Please fill in all fields." }) + end + + if not tokens[token] then + return apply_template(template, { classes = "alert-danger", message = "This invite has expired." }) + end + + -- Shamelessly copied from mod_register_web. + local prepped_username = nodeprep(user); + + if not prepped_username or #prepped_username == 0 then + return apply_template(template, { classes = "alert-warning", message = "This username contains invalid characters." }) + end + + if usermanager.user_exists(prepped_username, module.host) then + return apply_template(template, { classes = "alert-warning", message = "This username is already in use." }) + end + + local registering = { username = prepped_username , host = module.host, allowed = true } + + module:fire_event("user-registering", registering); + + if not registering.allowed then + return apply_template(template, { classes = "alert-danger", message = "Registration is not allowed." }) + end + + local ok, err = usermanager.create_user(prepped_username, password, module.host); + + if ok then + subscribe(prepped_username, tokens[token]); + subscribe(tokens[token], prepped_username); + + inviter_storage:set(prepped_username, { inviter = tokens[token] }); + + rostermanager.roster_push(tokens[token], module.host, jid_join(prepped_username, module.host)); + + tokens[token] = nil; + + invite_storage:set(nil, tokens); + + return apply_template(template, { classes = "alert-success", message = "Your account has been created! You can now log in using an XMPP client." }) + else + module:log("debug", "Registration failed: " .. tostring(err)); + + return apply_template(template, { classes = "alert-danger", message = "An unknown error has occurred." }) + end +end + +module:provides("http", { + route = { + ["GET /a_file.txt"] = serve(module:get_directory().."/my_file.txt"); + ["GET /bootstrap.min.css"] = serve(module:get_directory()); + ["GET /*"] = generate_page; + POST = handle_form; + }; +}); + +function invite_command_handler(self, data, state) + local uuid = uuid_new(); + + local user, host = jid_split(data.from); + + if host ~= module.host then + return { status = "completed", error = { message = "You are not allowed to invite users to this server." }}; + end + + local tokens = invite_storage:get() or {}; + + tokens[uuid] = user; + + invite_storage:set(nil, tokens); + + return { info = module:http_url() .. "/" .. uuid, status = "completed" }; +end + +local adhoc_invite = adhoc_new("Invite user", "invite", invite_command_handler, "user") + +module:add_item("adhoc", adhoc_invite); \ No newline at end of file