comparison 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
parents
children dc4e77824318
comparison
equal deleted inserted replaced
2057:1c126c49f5c1 2058:4b3037c7af62
1 local adhoc_new = module:require "adhoc".new;
2 local uuid_new = require "util.uuid".generate;
3 local jid_split = require "util.jid".split;
4 local jid_join = require "util.jid".join;
5 local http_formdecode = require "net.http".formdecode;
6 local http_urlencode = require "net.http".urlencode;
7 local usermanager = require "core.usermanager";
8 local rostermanager = require "core.rostermanager";
9 local nodeprep = require "util.encodings".stringprep.nodeprep;
10 local tostring = tostring;
11
12 local invite_storage = module:open_store();
13 local inviter_storage = module:open_store("inviter");
14
15 local serve = module:depends"http_files".serve;
16
17 module:depends"http";
18
19 local entities = {
20 ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;",
21 ["'"] = "&apos;", ["\""] = "&quot;", ["\n"] = "<br/>",
22 };
23
24 local function tohtml(plain)
25 return (plain:gsub("[<>&'\"\n]", entities));
26 end
27
28 local function apply_template(template, args)
29 return
30 template:gsub("{{([^}]*)}}", function (k)
31 if args[k] then
32 return tohtml(args[k])
33 else
34 return k
35 end
36 end)
37 end
38
39 function generate_page(event, display_options)
40 local request, response = event.request, event.response;
41
42 local tokens = invite_storage:get() or {};
43
44 local token = request.path:match("^/invite/([^/]*)$");
45
46 response.headers.content_type = "text/html; charset=utf-8";
47
48 if not token or not tokens[token] then
49 local template = assert(module:load_resource("invite/invite_result.html")):read("*a");
50
51 return apply_template(template, { classes = "alert-danger", message = "This invite has expired." })
52 end
53
54 local template = assert(module:load_resource("invite/invite.html")):read("*a");
55
56 return apply_template(template, { user = jid_join(tokens[token], module.host), server = module.host, token = token });
57 end
58
59 function subscribe(user1, user2)
60 local user1_jid = jid_join(user1, module.host);
61 local user2_jid = jid_join(user2, module.host);
62
63 rostermanager.set_contact_pending_out(user2, module.host, user1_jid);
64 rostermanager.set_contact_pending_in(user1, module.host, user2_jid);
65 rostermanager.subscribed(user1, module.host, user2_jid);
66 rostermanager.process_inbound_subscription_approval(user2, module.host, user1_jid);
67 end
68
69 function handle_form(event, display_options)
70 local request, response = event.request, event.response;
71 local form_data = http_formdecode(request.body);
72 local user, password, token = form_data["user"], form_data["password"], form_data["token"];
73 local tokens = invite_storage:get() or {};
74
75 local template = assert(module:load_resource("invite/invite_result.html")):read("*a");
76
77 response.headers.content_type = "text/html; charset=utf-8";
78
79 if not user or #user == 0 or not password or #password == 0 or not token then
80 return apply_template(template, { classes = "alert-warning", message = "Please fill in all fields." })
81 end
82
83 if not tokens[token] then
84 return apply_template(template, { classes = "alert-danger", message = "This invite has expired." })
85 end
86
87 -- Shamelessly copied from mod_register_web.
88 local prepped_username = nodeprep(user);
89
90 if not prepped_username or #prepped_username == 0 then
91 return apply_template(template, { classes = "alert-warning", message = "This username contains invalid characters." })
92 end
93
94 if usermanager.user_exists(prepped_username, module.host) then
95 return apply_template(template, { classes = "alert-warning", message = "This username is already in use." })
96 end
97
98 local registering = { username = prepped_username , host = module.host, allowed = true }
99
100 module:fire_event("user-registering", registering);
101
102 if not registering.allowed then
103 return apply_template(template, { classes = "alert-danger", message = "Registration is not allowed." })
104 end
105
106 local ok, err = usermanager.create_user(prepped_username, password, module.host);
107
108 if ok then
109 subscribe(prepped_username, tokens[token]);
110 subscribe(tokens[token], prepped_username);
111
112 inviter_storage:set(prepped_username, { inviter = tokens[token] });
113
114 rostermanager.roster_push(tokens[token], module.host, jid_join(prepped_username, module.host));
115
116 tokens[token] = nil;
117
118 invite_storage:set(nil, tokens);
119
120 return apply_template(template, { classes = "alert-success", message = "Your account has been created! You can now log in using an XMPP client." })
121 else
122 module:log("debug", "Registration failed: " .. tostring(err));
123
124 return apply_template(template, { classes = "alert-danger", message = "An unknown error has occurred." })
125 end
126 end
127
128 module:provides("http", {
129 route = {
130 ["GET /a_file.txt"] = serve(module:get_directory().."/my_file.txt");
131 ["GET /bootstrap.min.css"] = serve(module:get_directory());
132 ["GET /*"] = generate_page;
133 POST = handle_form;
134 };
135 });
136
137 function invite_command_handler(self, data, state)
138 local uuid = uuid_new();
139
140 local user, host = jid_split(data.from);
141
142 if host ~= module.host then
143 return { status = "completed", error = { message = "You are not allowed to invite users to this server." }};
144 end
145
146 local tokens = invite_storage:get() or {};
147
148 tokens[uuid] = user;
149
150 invite_storage:set(nil, tokens);
151
152 return { info = module:http_url() .. "/" .. uuid, status = "completed" };
153 end
154
155 local adhoc_invite = adhoc_new("Invite user", "invite", invite_command_handler, "user")
156
157 module:add_item("adhoc", adhoc_invite);