1342
|
1 local dm_load = require "util.datamanager".load; |
|
2 local st = require "util.stanza"; |
|
3 local nodeprep = require "util.encodings".stringprep.nodeprep; |
|
4 local usermanager = require "core.usermanager"; |
|
5 local http = require "net.http"; |
|
6 local vcard = module:require "vcard"; |
|
7 local datetime = require "util.datetime"; |
|
8 local timer = require "util.timer"; |
|
9 local jidutil = require "util.jid"; |
|
10 |
|
11 -- SMTP related params. Readed from config |
|
12 local os_time = os.time; |
|
13 local smtp = require "socket.smtp"; |
|
14 local smtp_server = module:get_option_string("smtp_server", "localhost"); |
|
15 local smtp_port = module:get_option_string("smtp_port", "25"); |
|
16 local smtp_ssl = module:get_option_boolean("smtp_ssl", false); |
|
17 local smtp_user = module:get_option_string("smtp_username"); |
|
18 local smtp_pass = module:get_option_string("smtp_password"); |
|
19 local smtp_address = module:get_option("smtp_from") or ((smtp_user or "no-responder").."@"..(smtp_server or module.host)); |
|
20 local mail_subject = module:get_option_string("msg_subject") |
|
21 local mail_body = module:get_option_string("msg_body"); |
|
22 local url_path = module:get_option_string("url_path", "/resetpass"); |
|
23 |
|
24 |
|
25 -- This table has the tokens submited by the server |
|
26 tokens_mails = {}; |
|
27 tokens_expiration = {}; |
|
28 |
|
29 -- URL |
|
30 local https_host = module:get_option_string("https_host"); |
|
31 local http_host = module:get_option_string("http_host"); |
|
32 local https_port = module:get_option("https_ports", { 443 }); |
|
33 local http_port = module:get_option("http_ports", { 80 }); |
|
34 |
|
35 local timer_repeat = 120; -- repeat after 120 secs |
|
36 |
|
37 function enablessl() |
|
38 local sock = socket.tcp() |
|
39 return setmetatable({ |
|
40 connect = function(_, host, port) |
|
41 local r, e = sock:connect(host, port) |
|
42 if not r then return r, e end |
|
43 sock = ssl.wrap(sock, {mode='client', protocol='tlsv1'}) |
|
44 return sock:dohandshake() |
|
45 end |
|
46 }, { |
|
47 __index = function(t,n) |
|
48 return function(_, ...) |
|
49 return sock[n](sock, ...) |
|
50 end |
|
51 end |
|
52 }) |
|
53 end |
|
54 |
|
55 function template(data) |
|
56 -- Like util.template, but deals with plain text |
|
57 return { apply = function(values) return (data:gsub("{([^}]+)}", values)); end } |
|
58 end |
|
59 |
|
60 local function get_template(name, extension) |
|
61 local fh = assert(module:load_resource("templates/"..name..extension)); |
|
62 local data = assert(fh:read("*a")); |
|
63 fh:close(); |
|
64 return template(data); |
|
65 end |
|
66 |
|
67 local function render(template, data) |
|
68 return tostring(template.apply(data)); |
|
69 end |
|
70 |
|
71 function send_email(address, smtp_address, message_text, subject) |
|
72 local rcpt = "<"..address..">"; |
|
73 |
|
74 local mesgt = { |
|
75 headers = { |
|
76 to = address; |
|
77 subject = subject or ("Jabber password reset "..jid_bare(from_address)); |
|
78 }; |
|
79 body = message_text; |
|
80 }; |
|
81 local ok, err = nil; |
|
82 |
|
83 if not smtp_ssl then |
|
84 ok, err = smtp.send{ from = smtp_address, rcpt = rcpt, source = smtp.message(mesgt), |
|
85 server = smtp_server, user = smtp_user, password = smtp_pass, port = 25 }; |
|
86 else |
|
87 ok, err = smtp.send{ from = smtp_address, rcpt = rcpt, source = smtp.message(mesgt), |
|
88 server = smtp_server, user = smtp_user, password = smtp_pass, port = smtp_port, create = enablessl }; |
|
89 end |
|
90 |
|
91 if not ok then |
|
92 module:log("error", "Failed to deliver to %s: %s", tostring(address), tostring(err)); |
|
93 return; |
|
94 end |
|
95 return true; |
|
96 end |
|
97 |
|
98 local vCard_mt = { |
|
99 __index = function(t, k) |
|
100 if type(k) ~= "string" then return nil end |
|
101 for i=1,#t do |
|
102 local t_i = rawget(t, i); |
|
103 if t_i and t_i.name == k then |
|
104 rawset(t, k, t_i); |
|
105 return t_i; |
|
106 end |
|
107 end |
|
108 end |
|
109 }; |
|
110 |
|
111 local function get_user_vcard(user, host) |
|
112 local vCard = dm_load(user, host or base_host, "vcard"); |
|
113 if vCard then |
|
114 vCard = st.deserialize(vCard); |
|
115 vCard = vcard.from_xep54(vCard); |
|
116 return setmetatable(vCard, vCard_mt); |
|
117 end |
|
118 end |
|
119 |
|
120 local changepass_tpl = get_template("changepass",".html"); |
|
121 local sendmail_success_tpl = get_template("sendmailok",".html"); |
|
122 local reset_success_tpl = get_template("resetok",".html"); |
|
123 local token_tpl = get_template("token",".html"); |
|
124 |
|
125 function generate_page(event, display_options) |
|
126 local request = event.request; |
|
127 |
|
128 return render(changepass_tpl, { |
|
129 path = request.path; hostname = module.host; |
|
130 notice = display_options and display_options.register_error or ""; |
|
131 }) |
|
132 end |
|
133 |
|
134 function generate_token_page(event, display_options) |
|
135 local request = event.request; |
|
136 |
|
137 return render(token_tpl, { |
|
138 path = request.path; hostname = module.host; |
|
139 token = request.url.query; |
|
140 notice = display_options and display_options.register_error or ""; |
|
141 }) |
|
142 end |
|
143 |
|
144 function generateToken(address) |
|
145 math.randomseed(os.time()) |
|
146 length = 16 |
|
147 if length < 1 then return nil end |
|
148 local array = {} |
|
149 for i = 1, length, 2 do |
|
150 array[i] = string.char(math.random(48,57)) |
|
151 array[i+1] = string.char(math.random(97,122)) |
|
152 end |
|
153 local token = table.concat(array); |
|
154 if not tokens_mails[token] then |
|
155 |
|
156 tokens_mails[token] = address; |
|
157 tokens_expiration[token] = os.time(); |
|
158 return token |
|
159 else |
|
160 module:log("error", "Reset password token collision: '%s'", token); |
|
161 return generateToken(address) |
|
162 end |
|
163 end |
|
164 |
|
165 function isExpired(token) |
|
166 if not tokens_expiration[token] then |
|
167 return nil; |
|
168 end |
|
169 if os.difftime(os.time(), tokens_expiration[token]) < 86400 then -- 86400 secs == 24h |
|
170 -- token is valid yet |
|
171 return nil; |
|
172 else |
|
173 -- token invalid, we can create a fresh one. |
|
174 return true; |
|
175 end |
|
176 end |
|
177 |
|
178 -- Expire tokens |
|
179 expireTokens = function() |
|
180 for token,value in pairs(tokens_mails) do |
|
181 if isExpired(token) then |
|
182 module:log("info","Expiring password reset request from user '%s', not used.", tokens_mails[token]); |
|
183 tokens_mails[token] = nil; |
|
184 tokens_expiration[token] = nil; |
|
185 end |
|
186 end |
|
187 return timer_repeat; |
|
188 end |
|
189 |
|
190 -- Check if a user has a active token not used yet. |
|
191 function hasTokenActive(address) |
|
192 for token,value in pairs(tokens_mails) do |
|
193 if address == value and not isExpired(token) then |
|
194 return token; |
|
195 end |
|
196 end |
|
197 return nil; |
|
198 end |
|
199 |
|
200 function generateUrl(token) |
|
201 local url; |
|
202 |
|
203 if https_host then |
|
204 url = "https://" .. https_host; |
|
205 else |
|
206 url = "http://" .. http_host; |
|
207 end |
|
208 |
|
209 if https_port then |
|
210 url = url .. ":" .. https_port[1]; |
|
211 else |
|
212 url = url .. ":" .. http_port[1]; |
|
213 end |
|
214 |
|
215 url = url .. url_path .. "token.html?" .. token; |
|
216 |
|
217 return url; |
|
218 end |
|
219 |
|
220 function sendMessage(jid, subject, message) |
|
221 local msg = st.message({ from = module.host; to = jid; }): |
|
222 tag("subject"):text(subject):up(): |
|
223 tag("body"):text(message); |
|
224 module:send(msg); |
|
225 end |
|
226 |
|
227 function send_token_mail(form, origin) |
|
228 local user, host, resource = jidutil.split(form.username); |
|
229 local prepped_username = nodeprep(user); |
|
230 local prepped_mail = form.email; |
|
231 local jid = prepped_username .. "@" .. host; |
|
232 |
|
233 if not prepped_username then |
|
234 return nil, "El usuario contiene caracteres incorrectos"; |
|
235 end |
|
236 if #prepped_username == 0 then |
|
237 return nil, "El campo usuario está vacio"; |
|
238 end |
|
239 if not usermanager.user_exists(prepped_username, module.host) then |
|
240 return nil, "El usuario NO existe"; |
|
241 end |
|
242 |
|
243 if #prepped_mail == 0 then |
|
244 return nil, "El campo email está vacio"; |
|
245 end |
|
246 |
|
247 local vcarduser = get_user_vcard(prepped_username, module.host); |
|
248 |
|
249 if not vcarduser then |
|
250 return nil, "User has not vCard"; |
|
251 else |
|
252 if not vcarduser.EMAIL then |
|
253 return nil, "Esa cuente no tiene ningĂşn email configurado en su vCard"; |
|
254 end |
|
255 |
|
256 email = string.lower(vcarduser.EMAIL[1]); |
|
257 |
|
258 if email ~= string.lower(prepped_mail) then |
|
259 return nil, "DirecciĂłn eMail incorrecta"; |
|
260 end |
|
261 |
|
262 -- Check if has already a valid token, not used yet. |
|
263 if hasTokenActive(jid) then |
|
264 local valid_until = tokens_expiration[hasTokenActive(jid)] + 86400; |
|
265 return nil, "Ya tienes una petición de restablecimiento de clave válida hasta: " .. datetime.date(valid_until) .. " " .. datetime.time(valid_until); |
|
266 end |
|
267 |
|
268 local url_token = generateToken(jid); |
|
269 local url = generateUrl(url_token); |
|
270 local email_body = render(get_template("sendtoken",".mail"), {jid = jid, url = url} ); |
|
271 |
|
272 module:log("info", "Sending password reset mail to user %s", jid); |
|
273 send_email(email, smtp_address, email_body, mail_subject); |
|
274 return "ok"; |
|
275 end |
|
276 |
|
277 end |
|
278 |
|
279 function reset_password_with_token(form, origin) |
|
280 local token = form.token; |
|
281 local password = form.newpassword; |
|
282 |
|
283 if not token then |
|
284 return nil, "El Token es inválido"; |
|
285 end |
|
286 if not tokens_mails[token] then |
|
287 return nil, "El Token no existe o ya fué usado"; |
|
288 end |
|
289 if not password then |
|
290 return nil, "La campo clave no puede estar vacio"; |
|
291 end |
|
292 if #password < 5 then |
|
293 return nil, "La clave debe tener una longitud de al menos 5 caracteres"; |
|
294 end |
|
295 local jid = tokens_mails[token]; |
|
296 local user, host, resource = jidutil.split(jid); |
|
297 |
|
298 usermanager.set_password(user, password, host); |
|
299 module:log("info", "Password changed with token for user %s", jid); |
|
300 tokens_mails[token] = nil; |
|
301 tokens_expiration[token] = nil; |
|
302 sendMessage(jid, mail_subject, mail_body); |
|
303 return "ok"; |
|
304 end |
|
305 |
|
306 function generate_success(event, form) |
|
307 return render(sendmail_success_tpl, { jid = nodeprep(form.username).."@"..module.host }); |
|
308 end |
|
309 |
|
310 function generate_register_response(event, form, ok, err) |
|
311 local message; |
|
312 if ok then |
|
313 return generate_success(event, form); |
|
314 else |
|
315 return generate_page(event, { register_error = err }); |
|
316 end |
|
317 end |
|
318 |
|
319 function handle_form_token(event) |
|
320 local request, response = event.request, event.response; |
|
321 local form = http.formdecode(request.body); |
|
322 |
|
323 local token_ok, token_err = send_token_mail(form, request); |
|
324 response:send(generate_register_response(event, form, token_ok, token_err)); |
|
325 |
|
326 return true; -- Leave connection open until we respond above |
|
327 end |
|
328 |
|
329 function generate_reset_success(event, form) |
|
330 return render(reset_success_tpl, { }); |
|
331 end |
|
332 |
|
333 function generate_reset_response(event, form, ok, err) |
|
334 local message; |
|
335 if ok then |
|
336 return generate_reset_success(event, form); |
|
337 else |
|
338 return generate_token_page(event, { register_error = err }); |
|
339 end |
|
340 end |
|
341 |
|
342 function handle_form_reset(event) |
|
343 local request, response = event.request, event.response; |
|
344 local form = http.formdecode(request.body); |
|
345 |
|
346 local reset_ok, reset_err = reset_password_with_token(form, request); |
|
347 response:send(generate_reset_response(event, form, reset_ok, reset_err)); |
|
348 |
|
349 return true; -- Leave connection open until we respond above |
|
350 |
|
351 end |
|
352 |
|
353 timer.add_task(timer_repeat, expireTokens); |
|
354 |
|
355 module:provides("http", { |
|
356 default_path = url_path; |
|
357 route = { |
|
358 ["GET /style.css"] = render(get_template("style",".css"), {}); |
|
359 ["GET /token.html"] = generate_token_page; |
|
360 ["GET /"] = generate_page; |
|
361 ["POST /token.html"] = handle_form_reset; |
|
362 ["POST /"] = handle_form_token; |
|
363 }; |
|
364 }); |
|
365 |
|
366 |