Comparison

plugins/mod_register.lua @ 4398:acc37e221940

mod_register: Add support for additional registration fields
author Florian Zeitz <florob@babelmonkeys.de>
date Fri, 12 Aug 2011 00:01:35 +0200
parent 4270:d2d47fde9811
child 5014:b2006c1cfa85
comparison
equal deleted inserted replaced
4397:1378e3c79c34 4398:acc37e221940
8 8
9 9
10 local hosts = _G.hosts; 10 local hosts = _G.hosts;
11 local st = require "util.stanza"; 11 local st = require "util.stanza";
12 local datamanager = require "util.datamanager"; 12 local datamanager = require "util.datamanager";
13 local dataform_new = require "util.dataforms".new;
13 local usermanager_user_exists = require "core.usermanager".user_exists; 14 local usermanager_user_exists = require "core.usermanager".user_exists;
14 local usermanager_create_user = require "core.usermanager".create_user; 15 local usermanager_create_user = require "core.usermanager".create_user;
15 local usermanager_set_password = require "core.usermanager".set_password; 16 local usermanager_set_password = require "core.usermanager".set_password;
16 local usermanager_delete_user = require "core.usermanager".delete_user; 17 local usermanager_delete_user = require "core.usermanager".delete_user;
17 local os_time = os.time; 18 local os_time = os.time;
18 local nodeprep = require "util.encodings".stringprep.nodeprep; 19 local nodeprep = require "util.encodings".stringprep.nodeprep;
19 local jid_bare = require "util.jid".bare; 20 local jid_bare = require "util.jid".bare;
20 21
21 local compat = module:get_option_boolean("registration_compat", true); 22 local compat = module:get_option_boolean("registration_compat", true);
22 local allow_registration = module:get_option_boolean("allow_registration", false); 23 local allow_registration = module:get_option_boolean("allow_registration", false);
24 local additional_fields = module:get_option("additional_registration_fields", {});
25
26 local field_map = {
27 username = { name = "username", type = "text-single", label = "Username", required = true };
28 password = { name = "password", type = "text-private", label = "Password", required = true };
29 nick = { name = "nick", type = "text-single", label = "Nickname" };
30 name = { name = "name", type = "text-single", label = "Full Name" };
31 first = { name = "first", type = "text-single", label = "Given Name" };
32 last = { name = "last", type = "text-single", label = "Family Name" };
33 email = { name = "email", type = "text-single", label = "Email" };
34 address = { name = "address", type = "text-single", label = "Street" };
35 city = { name = "city", type = "text-single", label = "City" };
36 state = { name = "state", type = "text-single", label = "State" };
37 zip = { name = "zip", type = "text-single", label = "Postal code" };
38 phone = { name = "phone", type = "text-single", label = "Telephone number" };
39 url = { name = "url", type = "text-single", label = "Webpage" };
40 date = { name = "date", type = "text-single", label = "Birth date" };
41 };
42
43 local registration_form = dataform_new{
44 title = "Creating a new account";
45 instructions = "Choose a username and password for use with this service.";
46
47 field_map.username;
48 field_map.password;
49 };
50
51 local registration_query = st.stanza("query", {xmlns = "jabber:iq:register"})
52 :tag("instructions"):text("Choose a username and password for use with this service."):up()
53 :tag("username"):up()
54 :tag("password"):up();
55
56 for _, field in ipairs(additional_fields) do
57 if type(field) == "table" then
58 registration_form[#registration_form + 1] = field;
59 else
60 if field:match("%+$") then
61 field = field:sub(1, #field - 1);
62 field_map[field].required = true;
63 end
64
65 registration_form[#registration_form + 1] = field_map[field];
66 registration_query:tag(field):up();
67 end
68 end
69 registration_query:add_child(registration_form:form());
23 70
24 module:add_feature("jabber:iq:register"); 71 module:add_feature("jabber:iq:register");
25 72
26 local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up(); 73 local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up();
27 module:hook("stream-features", function(event) 74 module:hook("stream-features", function(event)
65 session:close({condition = "not-authorized", text = "Account deleted"}); 112 session:close({condition = "not-authorized", text = "Account deleted"});
66 end 113 end
67 -- TODO datamanager should be able to delete all user data itself 114 -- TODO datamanager should be able to delete all user data itself
68 datamanager.store(username, host, "vcard", nil); 115 datamanager.store(username, host, "vcard", nil);
69 datamanager.store(username, host, "private", nil); 116 datamanager.store(username, host, "private", nil);
117 datamanager.store(username, host, "account_details", nil);
70 datamanager.list_store(username, host, "offline", nil); 118 datamanager.list_store(username, host, "offline", nil);
71 local bare = username.."@"..host; 119 local bare = username.."@"..host;
72 for jid, item in pairs(roster) do 120 for jid, item in pairs(roster) do
73 if jid and jid ~= "pending" then 121 if jid and jid ~= "pending" then
74 if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then 122 if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
113 return handle_registration_stanza(event); 161 return handle_registration_stanza(event);
114 end 162 end
115 end); 163 end);
116 end 164 end
117 165
166 local function parse_response(query)
167 local form = query:get_child("x", "jabber:x:data");
168 if form then
169 return registration_form:data(form);
170 else
171 local data = {};
172 local errors = {};
173 for _, field in ipairs(registration_form) do
174 local name, required = field.name, field.required;
175 if field_map[name] then
176 data[name] = query:get_child_text(name);
177 if (not data[name] or #data[name] == 0) and required then
178 errors[name] = "Required value missing";
179 end
180 end
181 end
182 if next(errors) then
183 return data, errors;
184 end
185 return data;
186 end
187 end
188
118 local recent_ips = {}; 189 local recent_ips = {};
119 local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations"); 190 local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations");
120 local whitelist_only = module:get_option("whitelist_registration_only"); 191 local whitelist_only = module:get_option("whitelist_registration_only");
121 local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" }; 192 local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" };
122 local blacklisted_ips = module:get_option("registration_blacklist") or {}; 193 local blacklisted_ips = module:get_option("registration_blacklist") or {};
131 session.send(st.error_reply(stanza, "cancel", "service-unavailable")); 202 session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
132 else 203 else
133 local query = stanza.tags[1]; 204 local query = stanza.tags[1];
134 if stanza.attr.type == "get" then 205 if stanza.attr.type == "get" then
135 local reply = st.reply(stanza); 206 local reply = st.reply(stanza);
136 reply:tag("query", {xmlns = "jabber:iq:register"}) 207 reply:add_child(registration_query);
137 :tag("instructions"):text("Choose a username and password for use with this service."):up()
138 :tag("username"):up()
139 :tag("password"):up();
140 session.send(reply); 208 session.send(reply);
141 elseif stanza.attr.type == "set" then 209 elseif stanza.attr.type == "set" then
142 if query.tags[1] and query.tags[1].name == "remove" then 210 if query.tags[1] and query.tags[1].name == "remove" then
143 session.send(st.error_reply(stanza, "auth", "registration-required")); 211 session.send(st.error_reply(stanza, "auth", "registration-required"));
144 else 212 else
145 local username = query:child_with_name("username"); 213 local data, errors = parse_response(query);
146 local password = query:child_with_name("password"); 214 if errors then
147 if username and password then 215 session.send(st.error_reply(stanza, "modify", "not-acceptable"));
216 else
148 -- Check that the user is not blacklisted or registering too often 217 -- Check that the user is not blacklisted or registering too often
149 if not session.ip then 218 if not session.ip then
150 module:log("debug", "User's IP not known; can't apply blacklist/whitelist"); 219 module:log("debug", "User's IP not known; can't apply blacklist/whitelist");
151 elseif blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then 220 elseif blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then
152 session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account.")); 221 session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account."));
164 return true; 233 return true;
165 end 234 end
166 ip.time = os_time(); 235 ip.time = os_time();
167 end 236 end
168 end 237 end
169 -- FIXME shouldn't use table.concat 238 local username, password = nodeprep(data.username), data.password;
170 username = nodeprep(table.concat(username)); 239 data.username, data.password = nil, nil;
171 password = table.concat(password);
172 local host = module.host; 240 local host = module.host;
173 if not username or username == "" then 241 if not username or username == "" then
174 session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid.")); 242 session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid."));
175 elseif usermanager_user_exists(username, host) then 243 elseif usermanager_user_exists(username, host) then
176 session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); 244 session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists."));
177 else 245 else
246 -- TODO unable to write file, file may be locked, etc, what's the correct error?
247 local error_reply = st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk.");
178 if usermanager_create_user(username, password, host) then 248 if usermanager_create_user(username, password, host) then
249 if next(data) and not datamanager.store(username, host, "account_details", data) then
250 usermanager_delete_user(username, host);
251 session.send(error_reply);
252 return true;
253 end
179 session.send(st.reply(stanza)); -- user created! 254 session.send(st.reply(stanza)); -- user created!
180 module:log("info", "User account created: %s@%s", username, host); 255 module:log("info", "User account created: %s@%s", username, host);
181 module:fire_event("user-registered", { 256 module:fire_event("user-registered", {
182 username = username, host = host, source = "mod_register", 257 username = username, host = host, source = "mod_register",
183 session = session }); 258 session = session });
184 else 259 else
185 -- TODO unable to write file, file may be locked, etc, what's the correct error? 260 session.send(error_reply);
186 session.send(st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk."));
187 end 261 end
188 end 262 end
189 else
190 session.send(st.error_reply(stanza, "modify", "not-acceptable"));
191 end 263 end
192 end 264 end
193 end 265 end
194 end 266 end
195 return true; 267 return true;