import files from my cvs repository

This commit is contained in:
edshot99 2024-03-12 22:58:50 -05:00
parent ac47a4133b
commit 555cc7d254
23 changed files with 1317 additions and 822 deletions

54
mod_email/README.markdown Normal file
View File

@ -0,0 +1,54 @@
---
labels:
- 'Stage-Stable'
...
Introduction
------------
Gives server owners and module developers the ability to send emails.
Usage
-----
For users and developers, add the following to your configuration file and
edit depending on your SMTP setup:
smtp = {
origin = "username@hostname.domainname";
-- Use sendmail and have sendmail handle SMTP
exec = "/usr/sbin/sendmail";
-- Use SMTP directly
--user = "username";
--password = "password";
--server = "localhost";
--port = 25;
--domain = "hostname.domainname";
}
For developers you can do something like this to send emails from your module:
local moduleapi = require "core.moduleapi";
module:depends("email");
local ok, err = moduleapi:send_email({to = mail_address, subject = mail_subject, body = mail_body});
if not ok then
module:log("error", "Failed to deliver to %s: %s", tostring(mail_address), tostring(err));
end
Todo
----
- Loading socket.smtp causes a stack trace on Prosody start up.
Everything still works fine, but this should probably be fixed.
- No SSL/STARTTLS support. This will require implementing something like LuaSec.
If needed, I would recommend to just set up OpenSMTPD and use sendmail.
Compatibility
-------------
----- --------------
0.12 Works
----- --------------

View File

@ -6,44 +6,42 @@ labels:
Introduction
============
This module aims to help in the procedure of user password restoration.
This module aims to help user password restoration.
To start the restoration, the user must go to an URL provided by this
module, fill the JID and email and submit the request.
The module will generate a token valid for 24h and send an email with a
specially crafted url to the vCard email address. If the user goes to
this url, will be able to change his password.
The module will generate a token valid for 1h and send an email with a
specially crafted URL to the email address set in account details (not vCard).
If the user goes to this URL, the user will be able to change his password.
Usage
=====
Simply add "email\_pass" to your modules\_enabled list and copy files
"**mod\_email\_pass.lua**" and "**vcard.lib.lua**" to prosody modules
folder. This module need to that **https\_host** or **http\_host** must
be configured. This parameter is necessary to construct the URL that has
been sended to the user.
Add "email\_pass" to your modules\_enabled list and copy the mod\_email\_pass
folder to the Prosody modules folder.
This module only send emails to the vCard user email address, then the
user must set this address in order to be capable of do the restoration.
This module also requires the "email" module which you can find here:
https://modules.prosody.im/mod\_email.html
This module only sends emails to the user email address set in Prosody account
details.
The user must set this email address in order to be capable of do the
restoration by going to: /email\_pass/changemail.html
Configuration
=============
--------------- ------------------------------------------------------------
smtp\_server The Host/ip of your SMTP server
smtp\_port Port used by your SMTP server. Default 25
smtp\_ssl Use of SMTP SSL legacy (No STARTTLS)
smtp\_user Username used to do SMTP auth
smtp\_pass Password used to do SMTP auth
smtp\_address EMail address that will be apears as From in mails
msg\_subject Subject used for messages/mails
msg\_body Message send when password has been changed
url\_path Path where the module will be visible. Default /resetpass/
--------------- ------------------------------------------------------------
--------------- ------------------------------------------------------------ ---------------------
token\_expire Time in seconds before token expires 3600
attempt\_limit Maximum attempts for 'update email'/'reset with token' page 10
attempt\_wait Time in seconds to block IP from going beyond limit 1800
email\_pass\_url URL prefix used for sending the token https://hostname:5281
--------------- ------------------------------------------------------------ ---------------------
Compatibility
=============
----- -------
0.12 Works
0.9 Works
----- -------

View File

@ -3,23 +3,13 @@ local st = require "util.stanza";
local nodeprep = require "util.encodings".stringprep.nodeprep;
local usermanager = require "core.usermanager";
local http = require "net.http";
local vcard = module:require "vcard";
local datetime = require "util.datetime";
local timer = require "util.timer";
local jidutil = require "util.jid";
local moduleapi = require "core.moduleapi";
-- SMTP related params. Readed from config
local os_time = os.time;
local smtp = require "socket.smtp";
local smtp_server = module:get_option_string("smtp_server", "localhost");
local smtp_port = module:get_option_string("smtp_port", "25");
local smtp_ssl = module:get_option_boolean("smtp_ssl", false);
local smtp_user = module:get_option_string("smtp_username");
local smtp_pass = module:get_option_string("smtp_password");
local smtp_address = module:get_option("smtp_from") or ((smtp_user or "no-responder").."@"..(smtp_server or module.host));
local mail_subject = module:get_option_string("msg_subject")
local mail_body = module:get_option_string("msg_body");
local url_path = module:get_option_string("url_path", "/resetpass");
local token_expire = module:get_option_number("token_expire", 3600);
-- This table has the tokens submited by the server
@ -27,30 +17,18 @@ tokens_mails = {};
tokens_expiration = {};
-- URL
local https_host = module:get_option_string("https_host");
local http_host = module:get_option_string("http_host");
local https_port = module:get_option("https_ports", { 443 });
local http_port = module:get_option("http_ports", { 80 });
local email_pass_url = module:get_option_string("email_pass_url", "https://" .. module.host .. ":5281");
local timer_repeat = 120; -- repeat after 120 secs
function enablessl()
local sock = socket.tcp()
return setmetatable({
connect = function(_, host, port)
local r, e = sock:connect(host, port)
if not r then return r, e end
sock = ssl.wrap(sock, {mode='client', protocol='tlsv1'})
return sock:dohandshake()
end
}, {
__index = function(t,n)
return function(_, ...)
return sock[n](sock, ...)
end
end
})
end
local account_details = module:open_store("account_details");
local attempt_limit = module:get_option_number("attempt_limit", 10);
local attempt_wait = module:get_option_number("attempt_wait", 1800);
attempt_wait_list = {};
attempt_wait_list_time = {};
module:depends("email");
function template(data)
-- Like util.template, but deals with plain text
@ -68,91 +46,139 @@ local function render(template, data)
return tostring(template.apply(data));
end
function send_email(address, smtp_address, message_text, subject)
local rcpt = "<"..address..">";
local mesgt = {
headers = {
to = address;
subject = subject or ("Jabber password reset "..jid_bare(from_address));
};
body = message_text;
};
local ok, err = nil;
if not smtp_ssl then
ok, err = smtp.send{ from = smtp_address, rcpt = rcpt, source = smtp.message(mesgt),
server = smtp_server, user = smtp_user, password = smtp_pass, port = 25 };
else
ok, err = smtp.send{ from = smtp_address, rcpt = rcpt, source = smtp.message(mesgt),
server = smtp_server, user = smtp_user, password = smtp_pass, port = smtp_port, create = enablessl };
end
if not ok then
module:log("error", "Failed to deliver to %s: %s", tostring(address), tostring(err));
return;
end
return true;
end
local vCard_mt = {
__index = function(t, k)
if type(k) ~= "string" then return nil end
for i=1,#t do
local t_i = rawget(t, i);
if t_i and t_i.name == k then
rawset(t, k, t_i);
return t_i;
end
end
end
};
local function get_user_vcard(user, host)
local vCard = dm_load(user, host or base_host, "vcard");
if vCard then
vCard = st.deserialize(vCard);
vCard = vcard.from_xep54(vCard);
return setmetatable(vCard, vCard_mt);
end
end
local changemail_tpl = get_template("changemail",".html");
local changepass_tpl = get_template("changepass",".html");
local sendmail_success_tpl = get_template("sendmailok",".html");
local reset_success_tpl = get_template("resetok",".html");
local update_success_tpl = get_template("updateok",".html");
local token_tpl = get_template("token",".html");
function generate_page(event, display_options)
function generate_page(event, lang, display_options)
local request = event.request;
-- begin translation
if lang == "Español" then
s_title = "Reseteo de la contraseña de tu cuenta Jabber";
s_username = "Nombre de Usuario";
s_usernamemessage = "Introduce tu nombre de usuario";
s_email = "Email";
s_emailmessage = "Introduce tu email";
s_send = "¡Enviar!";
s_text = "Al pulsar sobre el botón, se enviará a la dirección de correo que figura en tu cuenta un enlace en el que deberás entrar.";
else
s_title = "Reset your Jabber account password";
s_username = "Username";
s_usernamemessage = "Enter your username";
s_email = "Email";
s_emailmessage = "Enter your email";
s_send = "Send!";
s_text = "When you click the button, a link will be sent to the email address in your account.";
end
-- end translation
return render(changepass_tpl, {
path = request.path; hostname = module.host;
path = request.path;
hostname = module.host;
notice = display_options and display_options.register_error or "";
})
s_title = s_title;
s_username = s_username;
s_usernamemessage = s_usernamemessage;
s_email = s_email;
s_emailmessage = s_emailmessage;
s_send = s_send;
s_text = s_text;
s_lang = lang;
});
end
function generate_token_page(event, display_options)
function generate_token_page(event, lang, display_options)
local request = event.request;
-- begin translation
if lang == "Español" then
s_title = "Reseto de la contraseña de tu cuenta Jabber";
s_token = "Token";
s_password = "Contraseña";
s_passwordconfirm = "Contraseña (Confirmació)";
s_change = "¡Cambiar!";
else
s_title = "Reset the password for your Jabber account";
s_token = "Token";
s_password = "Password";
s_passwordconfirm = "Password (Confirm)";
s_change = "Change!";
end
-- end translation
return render(token_tpl, {
path = request.path; hostname = module.host;
path = request.path;
hostname = module.host;
token = request.url.query;
notice = display_options and display_options.register_error or "";
})
s_title = s_title;
s_token = s_token;
s_password = s_password;
s_passwordconfirm = s_passwordconfirm;
s_change = s_change;
s_lang = lang;
});
end
function generate_mail_page(event, lang, display_options)
local request = event.request;
-- begin translation
if lang == "Español" then
s_title = "Cambiar la eMail de tu cuenta Jabber";
s_username = "Nombre de Usuario";
s_usernamemessage = "Introduce tu nombre de usuario";
s_password = "Contraseña";
s_passwordmessage = "Introduce tu contraseña";
s_email = "Email";
s_emailmessage = "Introduce tu email";
s_change = "¡Cambiar!";
else
s_title = "Change the eMail of your Jabber account";
s_username = "Username";
s_usernamemessage = "Enter your username";
s_password = "Password";
s_passwordmessage = "Enter your password";
s_email = "Email";
s_emailmessage = "Enter your email";
s_change = "Change!";
end
-- end translation
return render(changemail_tpl, {
path = request.path;
hostname = module.host;
notice = display_options and display_options.register_error or "";
s_title = s_title;
s_username = s_username;
s_usernamemessage = s_usernamemessage;
s_password = s_password;
s_passwordmessage = s_passwordmessage;
s_email = s_email;
s_emailmessage = s_emailmessage;
s_change = s_change;
s_lang = lang;
});
end
function generateToken(address)
math.randomseed(os.time())
length = 16
if length < 1 then return nil end
local array = {}
for i = 1, length, 2 do
array[i] = string.char(math.random(48,57))
array[i+1] = string.char(math.random(97,122))
end
local token = table.concat(array);
if not tokens_mails[token] then
tokens_mails[token] = address;
tokens_expiration[token] = os.time();
return token
@ -166,7 +192,7 @@ function isExpired(token)
if not tokens_expiration[token] then
return nil;
end
if os.difftime(os.time(), tokens_expiration[token]) < 86400 then -- 86400 secs == 24h
if os.difftime(os.time(), tokens_expiration[token]) < token_expire then
-- token is valid yet
return nil;
else
@ -197,22 +223,10 @@ function hasTokenActive(address)
return nil;
end
function generateUrl(token)
function generateUrl(token, path)
local url;
if https_host then
url = "https://" .. https_host;
else
url = "http://" .. http_host;
end
if https_port then
url = url .. ":" .. https_port[1];
else
url = url .. ":" .. http_port[1];
end
url = url .. url_path .. "token.html?" .. token;
url = email_pass_url .. path .. "token.html?" .. token;
return url;
end
@ -225,93 +239,411 @@ function sendMessage(jid, subject, message)
end
function send_token_mail(form, origin)
local prepped_username = nodeprep(form.username);
if form.langchange == "true" then
return nil;
end
local lang = form.lang;
local prepped_username = nodeprep(form.username, true);
local prepped_mail = form.email;
local jid = prepped_username .. "@" .. module.host;
if not prepped_username then
return nil, "El usuario contiene caracteres incorrectos";
-- begin translation
if lang == "Español" then
return nil, "El nombre de usuario contiene caracteres incorrectos";
else
return nil, "The username contains invalid characters";
end
-- end translation
end
if #prepped_username == 0 then
return nil, "El campo usuario está vacio";
-- begin translation
if lang == "Español" then
return nil, "El campo texto de nombre de usuario está vacío";
else
return nil, "The username text field is empty";
end
-- end translation
end
if not usermanager.user_exists(prepped_username, module.host) then
return nil, "El usuario NO existe";
-- begin translation
if lang == "Español" then
return nil, "El usuario no existe";
else
return nil, "The user does not exist";
end
-- end translation
end
if #prepped_mail == 0 then
return nil, "El campo email está vacio";
end
local vcarduser = get_user_vcard(prepped_username, module.host);
if not vcarduser then
return nil, "User has not vCard";
-- begin translation
if lang == "Español" then
return nil, "El campo texto de email está vacío";
else
if not vcarduser.EMAIL then
return nil, "Esa cuente no tiene ningún email configurado en su vCard";
return nil, "The email text field is empty";
end
-- end translation
end
email = string.lower(vcarduser.EMAIL[1]);
local account_detail_table = account_details:get(prepped_username);
if account_detail_table == nil then
account_detail_table = {["email"] = ""};
end
local account_detail_email = account_detail_table["email"];
if not account_detail_email then
-- begin translation
if lang == "Español" then
return nil, "El cuente no tiene ningún email configurado";
else
return nil, "The account has no email configured";
end
-- end translation
end
email = string.lower( account_detail_email );
if #email == 0 then
-- begin translation
if lang == "Español" then
return nil, "El cuente no tiene ningún email configurado";
else
return nil, "The account has no email configured";
end
-- end translation
end
if email ~= string.lower(prepped_mail) then
return nil, "Dirección eMail incorrecta";
-- begin translation
if lang == "Español" then
return nil, "eMail incorrecta";
else
return nil, "Incorrect eMail";
end
-- end translation
end
-- Check if has already a valid token, not used yet.
if hasTokenActive(jid) then
local valid_until = tokens_expiration[hasTokenActive(jid)] + 86400;
return nil, "Ya tienes una petición de restablecimiento de clave válida hasta: " .. datetime.date(valid_until) .. " " .. datetime.time(valid_until);
local valid_until = tokens_expiration[hasTokenActive(jid)] + token_expire;
-- begin translation
if lang == "Español" then
return nil, "Ya tienes una petición de reseteada de contraseña, válida hasta: " .. datetime.date(valid_until) .. " " .. datetime.time(valid_until);
else
return nil, "You already have a password reset request, valid until: " .. datetime.date(valid_until) .. " " .. datetime.time(valid_until);
end
-- end translation
end
local url_path = origin.path;
local url_token = generateToken(jid);
local url = generateUrl(url_token);
local email_body = render(get_template("sendtoken",".mail"), {jid = jid, url = url} );
local url = generateUrl(url_token, url_path);
local mail_subject = nil;
local mail_body = nil;
-- begin translation
if lang == "Español" then
mail_subject = "Jabber/XMPP - Reseto de la Contraseña";
mail_body = render( get_template("sendtoken", ".mail"), {jid = jid, url = url, time = ((token_expire / 60) / 60), host = module.host} );
else
mail_subject = "Jabber/XMPP - Password Reset";
mail_body = render( get_template("sendtoken-en", ".mail"), {jid = jid, url = url, time = ((token_expire / 60) / 60), host = module.host} );
end
-- end translation
module:log("info", "Sending password reset mail to user %s", jid);
send_email(email, smtp_address, email_body, mail_subject);
local ok, err = moduleapi:send_email({to = email, subject = mail_subject, body = mail_body});
if not ok then
module:log("error", "Failed to deliver to %s: %s", tostring(address), tostring(err));
end
-- begin translation
if lang == "Español" then
mail_subject = "Gestión de Cuentas";
mail_body = "Token para reseto su contraseña ha sido enviado a su email por el sistema de reseto de contraseña.";
else
mail_subject = "Account Management";
mail_body = "Token to reset your password has been sent to your email by password reset system.";
end
-- end translation
sendMessage(jid, mail_subject, mail_body);
return "ok";
end
function reset_password_with_token(form, origin)
local lang = form.lang;
if form.langchange == "true" then
return nil;
end
if attempt_wait_list[origin.ip] == nil then attempt_wait_list[origin.ip] = 0; end
if attempt_wait_list[origin.ip] >= attempt_limit then
if os.difftime(os.time(), attempt_wait_list_time[origin.ip]) > attempt_wait then
attempt_wait_list[origin.ip] = 0;
attempt_wait_list_time[origin.ip] = 0;
else
module:log("info", "Too many attempts at guessing token from IP %s", origin.ip);
-- begin translation
if lang == "Español" then
return nil, "Demasiados intentos. Inténtalo otra vez en "..(attempt_wait / 60).."m.";
else
return nil, "Too many attempts. Try again in "..(attempt_wait / 60).."m.";
end
-- end translation
end
end
function reset_password_with_token(form, origin)
local token = form.token;
local password = form.newpassword;
local passwordconfirmation = form.newpasswordconfirmation;
form.newpassword, form.newpasswordconfirmation = nil, nil;
if not token then
-- begin translation
if lang == "Español" then
return nil, "El Token es inválido";
else
return nil, "The token is invalid";
end
-- end translation
end
if not tokens_mails[token] then
attempt_wait_list[origin.ip] = attempt_wait_list[origin.ip]+1;
attempt_wait_list_time[origin.ip] = os.time();
-- begin translation
if lang == "Español" then
return nil, "El Token no existe o ya fué usado";
else
return nil, "The token does not exist or has already been used";
end
-- end translation
end
if not password then
return nil, "La campo clave no puede estar vacio";
-- begin translation
if lang == "Español" then
return nil, "El campo texto de contraseña está vacío";
else
return nil, "The password text field is empty";
end
-- end translation
end
if #password < 5 then
return nil, "La clave debe tener una longitud de al menos 5 caracteres";
-- begin translation
if lang == "Español" then
return nil, "El contraseña debe tener una longitud de al menos cinco caracteres";
else
return nil, "The password must be at least five characters long";
end
-- end translation
end
if password ~= passwordconfirmation then
-- begin translation
if lang == "Español" then
return nil, "El confirmació de contraseña es inválido";
else
return nil, "The password confirmation is invalid";
end
-- end translation
end
local jid = tokens_mails[token];
local user, host, resource = jidutil.split(jid);
local mail_subject = nil;
local mail_body = nil;
-- begin translation
if lang == "Español" then
mail_subject = "Gestión de Cuentas";
mail_body = "La contraseña se ha cambiado con el sistema de reseto de contraseña.";
else
mail_subject = "Account Management";
mail_body = "Password has been changed with password reset system.";
end
-- end translation
usermanager.set_password(user, password, host);
module:log("info", "Password changed with token for user %s", jid);
tokens_mails[token] = nil;
tokens_expiration[token] = nil;
sendMessage(jid, mail_subject, mail_body);
-- begin translation
if lang == "Español" then
mail_subject = "Jabber/XMPP - Contraseña Cambiado";
mail_body = render( get_template("updatepass", ".mail"), {jid = jid, host = module.host} );
else
mail_subject = "Jabber/XMPP - Password Changed";
mail_body = render( get_template("updatepass-en", ".mail"), {jid = jid, host = module.host} );
end
-- end translation
module:log("info", "Sending password update mail to user %s", jid);
local ok, err = moduleapi:send_email({to = email, subject = mail_subject, body = mail_body});
if not ok then
module:log("error", "Failed to deliver to %s: %s", tostring(address), tostring(err));
end
return "ok";
end
function generate_success(event, form)
return render(sendmail_success_tpl, { jid = nodeprep(form.username).."@"..module.host });
function change_mail_with_password(form, origin)
local lang = form.lang;
if form.langchange == "true" then
return nil;
end
if attempt_wait_list[origin.ip] == nil then attempt_wait_list[origin.ip] = 0; end
if attempt_wait_list[origin.ip] >= attempt_limit then
if os.difftime(os.time(), attempt_wait_list_time[origin.ip]) > attempt_wait then
attempt_wait_list[origin.ip] = 0;
attempt_wait_list_time[origin.ip] = 0;
else
module:log("info", "Too many attempts at guessing password from IP %s", origin.ip);
-- begin translation
if lang == "Español" then
return nil, "Demasiados intentos. Inténtalo otra vez en "..(attempt_wait / 60).."m";
else
return nil, "Too many attempts. Try again in "..(attempt_wait / 60).."m";
end
-- end translation
end
end
local prepped_username = nodeprep(form.username, true);
local prepped_mail = form.email;
local password = form.password;
local jid = prepped_username .. "@" .. module.host;
form.password = nil;
if not prepped_username then
-- begin translation
if lang == "Español" then
return nil, "El nombre de usuario contiene caracteres incorrectos";
else
return nil, "The username contains invalid characters";
end
-- end translation
end
if #prepped_username == 0 then
-- begin translation
if lang == "Español" then
return nil, "El campo texto de nombre de usuario está vacio";
else
return nil, "The username text field is empty";
end
-- end translation
end
if not usermanager.user_exists(prepped_username, module.host) then
-- begin translation
if lang == "Español" then
return nil, "El usuario no existe";
else
return nil, "The user does not exist";
end
-- end translation
end
if not password then
-- begin translation
if lang == "Español" then
return nil, "El campo texto de contraseña está vacío";
else
return nil, "The password text field is empty";
end
-- end translation
end
if usermanager.test_password(prepped_username, module.host, password) then
local account_detail_table = account_details:get(prepped_username);
if account_detail_table == nil then
account_detail_table = {["email"] = ""};
end
local remove_email;
if prepped_mail == "" then
remove_email = true;
else
remove_email = false;
end
email = string.lower(prepped_mail);
account_detail_table["email"] = email;
account_details:set(prepped_username, account_detail_table);
module:log("info", "Email changed with password for user %s", jid);
local mail_subject = nil;
local mail_body = nil;
-- begin translation
if lang == "Español" then
mail_subject = "Gestión de Cuentas";
mail_body = "El email se ha actualizado a <"..email.."> con el sistema de reseto de contraseña.";
else
mail_subject = "Account Management";
mail_body = "Email has been updated to <"..email.."> with password reset system.";
end
-- end translation
sendMessage(jid, mail_subject, mail_body);
if not remove_email then
-- begin translation
if lang == "Español" then
mail_subject = "Jabber/XMPP - Email Cambiado";
mail_body = render( get_template("updatemail", ".mail"), {jid = jid, email = email, host = module.host} );
else
mail_subject = "Jabber/XMPP - Email Changed";
mail_body = render( get_template("updatemail-en", ".mail"), {jid = jid, email = email, host = module.host} );
end
-- end translation
module:log("info", "Sending email update mail to user %s", jid);
local ok, err = moduleapi:send_email({to = email, subject = mail_subject, body = mail_body});
if not ok then
module:log("error", "Failed to deliver to %s: %s", tostring(address), tostring(err));
end
end
return "ok";
else
attempt_wait_list[origin.ip] = attempt_wait_list[origin.ip]+1;
attempt_wait_list_time[origin.ip] = os.time();
-- begin translation
if lang == "Español" then
return nil, "Contraseña incorrecta";
else
return nil, "Incorrect password";
end
-- end translation
end
end
function generate_success(event, lang, username)
-- begin translation
if lang == "Español" then
s_title = "¡Enlace enviado!";
s_text = "Acabamos de enviarte un email con un enlace que tendrás que visitar.";
else
s_title = "Link sent!";
s_text = "We just sent you an email with a link youll need to visit.";
end
-- end translation
return render(sendmail_success_tpl, {
jid = nodeprep(username, true).."@"..module.host;
s_title = s_title;
s_text = s_text;
});
end
function generate_register_response(event, form, ok, err)
local message;
if ok then
return generate_success(event, form);
return generate_success(event, form.lang, form.username);
else
return generate_page(event, { register_error = err });
return generate_page(event, form.lang, { register_error = err });
end
end
@ -325,16 +657,61 @@ function handle_form_token(event)
return true; -- Leave connection open until we respond above
end
function generate_reset_success(event, form)
return render(reset_success_tpl, { });
function generate_reset_success(event, lang)
-- begin translation
if lang == "Español" then
s_title = "¡Contraseña reseteada!";
s_p1 = "Tu contraseña ha sido cambiada.";
s_p2 = "Ya puedes iniciar sesión de Jabber.";
else
s_title = "Password reset!";
s_p1 = "Your password has been changed.";
s_p2 = "You can now log into Jabber.";
end
-- end translation
return render(reset_success_tpl, {
s_title = s_title;
s_p1 = s_p1;
s_p2 = s_p2;
});
end
function generate_update_success(event, lang, email)
-- begin translation
if lang == "Español" then
s_title = "¡Email cambiada!";
s_p1 = "Tu email ha sido cambiada.";
s_p2 = "Nuevo email";
else
s_title = "Email changed!";
s_p1 = "Your email has been changed.";
s_p2 = "New email";
end
-- end translation
return render(update_success_tpl, {
s_title = s_title;
s_p1 = s_p1;
s_p2 = s_p2;
email = email;
});
end
function generate_reset_response(event, form, ok, err)
local message;
if ok then
return generate_reset_success(event, form);
return generate_reset_success(event, form.lang);
else
return generate_token_page(event, { register_error = err });
return generate_token_page(event, form.lang, { register_error = err });
end
end
function generate_update_response(event, form, ok, err)
local message;
if ok then
return generate_update_success(event, form.lang, form.email);
else
return generate_mail_page(event, form.lang, { register_error = err });
end
end
@ -346,17 +723,29 @@ function handle_form_reset(event)
response:send(generate_reset_response(event, form, reset_ok, reset_err));
return true; -- Leave connection open until we respond above
end
function handle_form_mailchange(event)
local request, response = event.request, event.response;
local form = http.formdecode(request.body);
local change_ok, change_err = change_mail_with_password(form, request);
response:send(generate_update_response(event, form, change_ok, change_err));
return true; -- Leave connection open until we respond above
end
timer.add_task(timer_repeat, expireTokens);
module:provides("http", {
default_path = url_path;
route = {
["GET /style.css"] = render(get_template("style",".css"), {});
["GET /changepass.html"] = generate_page;
["GET /changemail.html"] = generate_mail_page;
["GET /token.html"] = generate_token_page;
["GET /"] = generate_page;
["POST /changepass.html"] = handle_form_token;
["POST /changemail.html"] = handle_form_mailchange;
["POST /token.html"] = handle_form_reset;
["POST /"] = handle_form_token;
};

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<div id="estilo3" class="formulario">
<h1>{s_title}</h1>
</div>
<div id="estilo2" class="formulario2">
<div>
<form action="{path}" method="POST">
<input name="lang" type="submit" value="English">
<input name="lang" type="submit" value="Español">
<input name="langchange" type="hidden" value="true">
</form>
</div>
</div>
<div id="estilo" class="formulario">
<form action="{path}" method="POST">
<p class="error">{notice}</p>
<label>
{s_username}:
<span class="small">{s_usernamemessage}</span>
</label>
<input type="text" name="username" required>@{hostname}
<div class="spacer"></div>
<label>
{s_password}:
<span class="small">{s_passwordmessage}</span>
</label>
<input type="password" name="password" required>
<div class="spacer"></div>
<label>
{s_email}:
<span class="small">{s_emailmessage}</span>
</label>
<input type="text" name="email">
<div class="spacer"></div>
<input name="lang" type="hidden" value="{s_lang}">
<input name="langchange" type="hidden" value="false">
<input id="button" class="button" type="submit" value="{s_change}">
<div class="spacer"></div>
</form>
</div>
</body>
</html>

View File

@ -3,33 +3,47 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css" />
<title>Reseteo de la clave de tu cuenta Jabber</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<div id="estilo3" class="formulario">
<h1>{s_title}</h1>
</div>
<div id="estilo2" class="formulario2">
<div>
<form action="{path}" method="POST">
<input name="lang" type="submit" value="English">
<input name="lang" type="submit" value="Español">
<input name="langchange" type="hidden" value="true">
</form>
</div>
</div>
<div id="estilo" class="formulario">
<h1>Reseteo de la clave de tu cuenta Jabber</h1>
<form action="{path}" method="POST">
<p class="error">{notice}</p>
<label>
Usuario:
<span class="small">Introduce tu usuario</span>
{s_username}:
<span class="small">{s_usernamemessage}</span>
</label>
<input type="text" name="username" required>@{hostname}
<div class="spacer"></div>
<label>
Email:
<span class="small">Introduce tu email</span>
{s_email}:
<span class="small">{s_emailmessage}</span>
</label>
<input type="text" name="email" required>
<div class="spacer"></div>
<input id="button" class="button" type="submit" value="Enviar!">
<input name="lang" type="hidden" value="{s_lang}">
<input name="langchange" type="hidden" value="false">
<input id="button" class="button" type="submit" value="{s_send}">
<div class="spacer"></div>
</form>
<p>
Al pulsar sobre el bot&oacute;n, se enviar&aacute; a la direcci&oacute;n de correo que figura
en tu vCard un enlace en el que deber&aacute;s entrar.<br />
{s_text}
</p>
<br />
</div>
</body>
</html>

View File

@ -1,14 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8">
<title>Clave reseteada!</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<div id="estilo" class="formulario">
<h1>Tu clave ha sido cambiada correctamente. Ya puedes iniciar sesi&oacute;n con ella.</h1>
<h1>{s_p1}</h1>
<h1>{s_p2}</h1>
</div>
</body>
</html>

View File

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8">
<title>Enlace enviado!</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<div id="estilo" class="formulario">
<h1>Acabamos de enviarte un email con un enlace que tendr&aacute;s que visitar.</h1>
<h1>{s_text}</h1>
</div>
</body>
</html>

View File

@ -0,0 +1,16 @@
Hello
You have received this email because you have requested the
reset of your Jabber/XMPP account password.
To proceed with the password change, use the following link:
Account ID: {jid}
Reset Link: {url}
If you have not requested to reset your password, ignore this message.
The link expires in {time}h.
Sincerely, the team of {host}

View File

@ -1,14 +1,16 @@
Hola:
Hola
Si has recibido este email es porque has solicitado el reseteo de la
clave de tu cuenta Jabber/XMPP {jid}
Tú tienen recibido este email porque has solicitado el reseteo de la
contraseña de tu cuenta Jabber/XMPP.
Para proceder con el cambio de clave, haz click en el siguiente enlace:
Para proceder con el cambio de contraseña, usar el siguiente enlace:
{url}
ID de Cuenta: {jid}
Enlace de Reseteo: {url}
Si no has solicitado resetear tu clave, ignora este mensaje.
Si no has solicitado resetear tu contraseña, ignora este mensaje.
El enlace expira en {time}h.
Atentamente, el equipo de mijabber.es
Atentamente, el equipo de {host}

View File

@ -3,8 +3,20 @@ body{
font-size:12px;
}
p, h1, form, button{border:0; margin:0; padding:0;}
.spacer{clear:both; height:1px;}
h1 {
text-align:center;
}
p, h1, form, button {
border:0;
margin:0;
padding:0;
}
.spacer {
clear:both;
height:1px;
}
/* ----------- My Form ----------- */
.formulario {
@ -12,12 +24,30 @@ p, h1, form, button{border:0; margin:0; padding:0;}
width:500px;
padding:14px;
}
.formulario2 {
margin:0 auto;
width:500px;
padding:14px;
}
/* ----------- stylized ----------- */
#estilo {
border:solid 2px #b7ddf2;
background:#ebf4fb;
}
#estilo2 {
border-left:solid 2px #b7ddf2;
border-right:solid 2px #b7ddf2;
background:#ebf4fb;
}
#estilo3 {
border:solid 2px #b7ddf2;
background:#ebf4fb;
}
#estilo h1 {
font-size:14px;
font-weight:bold;
@ -39,6 +69,7 @@ p, h1, form, button{border:0; margin:0; padding:0;}
margin-bottom:20px;
border-bottom:solid 1px #b7ddf2;
padding-bottom:10px;
text-align:center;
}
#estilo label {
@ -59,7 +90,7 @@ p, h1, form, button{border:0; margin:0; padding:0;}
}
#estilo input {
float:left;
/* float:left; */
font-size:12px;
padding:4px 2px;
border:solid 1px #aacfe4;
@ -67,6 +98,24 @@ p, h1, form, button{border:0; margin:0; padding:0;}
margin:2px 0 20px 10px;
}
#estilo2 div {
text-align:center;
}
#estilo2 input {
padding:5px;
}
#estilo3 h1 {
font-size:14px;
font-weight:bold;
margin-bottom:8px;
}
#estilo .button {
margin:2px 150px 20px 150px;
}
.button {
-moz-box-shadow:inset 0px 1px 0px 0px #cae3fc;
-webkit-box-shadow:inset 0px 1px 0px 0px #cae3fc;
@ -99,9 +148,11 @@ p, h1, form, button{border:0; margin:0; padding:0;}
text-align:center;
text-shadow:1px 1px 0px #287ace;
}
.button:hover {
background-color:#4197ee;
}
.button:active {
position:relative;
top:1px;

View File

@ -1,28 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8">
<title>Reseto de la clave de tu cuenta Jabber</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<div id="estilo3" class="formulario">
<h1>{s_title}</h1>
</div>
<div id="estilo2" class="formulario2">
<div>
<form action="{path}?{token}" method="POST">
<input name="lang" type="submit" value="English">
<input name="lang" type="submit" value="Español">
<input name="langchange" type="hidden" value="true">
</form>
</div>
</div>
<div id="estilo" class="formulario">
<h1>Reseteo de la clave de tu cuenta Jabber</h1>
<form action="{path}?{token}" method="POST">
<p class="error">{notice}</p>
<label>
Token:
{s_token}:
</label>
<input name="token" value="{token}" required readonly>
<div class="spacer"></div>
<label>
Contrase&ntilde;a:
{s_password}:
</label>
<input name="newpassword" type="password" required size="35">
<div class="spacer"></div>
<input id="button" class="button" type="submit" value="Cambiar!">
<label>
{s_passwordconfirm}:
</label>
<input name="newpasswordconfirmation" type="password" required size="35">
<div class="spacer"></div>
<input id="button" class="button" type="submit" value="{s_change}">
<div class="spacer"></div>
<input name="lang" type="hidden" value="{s_lang}">
<input name="langchange" type="hidden" value="false">
</form>
</div>
</body>

View File

@ -0,0 +1,11 @@
Hello
You have received this email because your Jabber/XMPP
account email has been changed.
Account ID: {jid}
Account Email: {email}
Sincerely, the team of {host}

View File

@ -0,0 +1,11 @@
Hola
Tú tienen recibido este email porque has cambiado el email de tu
cuenta de Jabber/XMPP.
ID de Cuenta: {jid}
Email de Cuenta: {email}
Atentamente, el equipo de {host}

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<div id="estilo" class="formulario">
<h1>{s_p1}</h1>
<h1>{s_p2}:</h1>
<h1><i>{email}</i></h1>
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
Hello
You have received this email because your Jabber/XMPP
account password has been reset.
Account ID: {jid}
Sincerely, the team of {host}

View File

@ -0,0 +1,10 @@
Hola
Tú tienen recibido este email porque has reseteo la contraseña de tu
cuenta de Jabber/XMPP.
ID de Cuenta: {jid}
Atentamente, el equipo de {host}

View File

@ -1,464 +0,0 @@
-- Copyright (C) 2011-2012 Kim Alvefur
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- TODO
-- Fix folding.
local st = require "util.stanza";
local t_insert, t_concat = table.insert, table.concat;
local type = type;
local next, pairs, ipairs = next, pairs, ipairs;
local from_text, to_text, from_xep54, to_xep54;
local line_sep = "\n";
local vCard_dtd; -- See end of file
local function fold_line()
error "Not implemented" --TODO
end
local function unfold_line()
error "Not implemented"
-- gsub("\r?\n[ \t]([^\r\n])", "%1");
end
local function vCard_esc(s)
return s:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n");
end
local function vCard_unesc(s)
return s:gsub("\\?[\\nt:;,]", {
["\\\\"] = "\\",
["\\n"] = "\n",
["\\r"] = "\r",
["\\t"] = "\t",
["\\:"] = ":", -- FIXME Shouldn't need to espace : in values, just params
["\\;"] = ";",
["\\,"] = ",",
[":"] = "\29",
[";"] = "\30",
[","] = "\31",
});
end
local function item_to_xep54(item)
local t = st.stanza(item.name, { xmlns = "vcard-temp" });
local prop_def = vCard_dtd[item.name];
if prop_def == "text" then
t:text(item[1]);
elseif type(prop_def) == "table" then
if prop_def.types and item.TYPE then
if type(item.TYPE) == "table" then
for _,v in pairs(prop_def.types) do
for _,typ in pairs(item.TYPE) do
if typ:upper() == v then
t:tag(v):up();
break;
end
end
end
else
t:tag(item.TYPE:upper()):up();
end
end
if prop_def.props then
for _,v in pairs(prop_def.props) do
if item[v] then
t:tag(v):up();
end
end
end
if prop_def.value then
t:tag(prop_def.value):text(item[1]):up();
elseif prop_def.values then
local prop_def_values = prop_def.values;
local repeat_last = prop_def_values.behaviour == "repeat-last" and prop_def_values[#prop_def_values];
for i=1,#item do
t:tag(prop_def.values[i] or repeat_last):text(item[i]):up();
end
end
end
return t;
end
local function vcard_to_xep54(vCard)
local t = st.stanza("vCard", { xmlns = "vcard-temp" });
for i=1,#vCard do
t:add_child(item_to_xep54(vCard[i]));
end
return t;
end
function to_xep54(vCards)
if not vCards[1] or vCards[1].name then
return vcard_to_xep54(vCards)
else
local t = st.stanza("xCard", { xmlns = "vcard-temp" });
for i=1,#vCards do
t:add_child(vcard_to_xep54(vCards[i]));
end
return t;
end
end
function from_text(data)
data = data -- unfold and remove empty lines
:gsub("\r\n","\n")
:gsub("\n ", "")
:gsub("\n\n+","\n");
local vCards = {};
local c; -- current item
for line in data:gmatch("[^\n]+") do
local line = vCard_unesc(line);
local name, params, value = line:match("^([-%a]+)(\30?[^\29]*)\29(.*)$");
value = value:gsub("\29",":");
if #params > 0 then
local _params = {};
for k,isval,v in params:gmatch("\30([^=]+)(=?)([^\30]*)") do
k = k:upper();
local _vt = {};
for _p in v:gmatch("[^\31]+") do
_vt[#_vt+1]=_p
_vt[_p]=true;
end
if isval == "=" then
_params[k]=_vt;
else
_params[k]=true;
end
end
params = _params;
end
if name == "BEGIN" and value == "VCARD" then
c = {};
vCards[#vCards+1] = c;
elseif name == "END" and value == "VCARD" then
c = nil;
elseif vCard_dtd[name] then
local dtd = vCard_dtd[name];
local p = { name = name };
c[#c+1]=p;
--c[name]=p;
local up = c;
c = p;
if dtd.types then
for _, t in ipairs(dtd.types) do
local t = t:lower();
if ( params.TYPE and params.TYPE[t] == true)
or params[t] == true then
c.TYPE=t;
end
end
end
if dtd.props then
for _, p in ipairs(dtd.props) do
if params[p] then
if params[p] == true then
c[p]=true;
else
for _, prop in ipairs(params[p]) do
c[p]=prop;
end
end
end
end
end
if dtd == "text" or dtd.value then
t_insert(c, value);
elseif dtd.values then
local value = "\30"..value;
for p in value:gmatch("\30([^\30]*)") do
t_insert(c, p);
end
end
c = up;
end
end
return vCards;
end
local function item_to_text(item)
local value = {};
for i=1,#item do
value[i] = vCard_esc(item[i]);
end
value = t_concat(value, ";");
local params = "";
for k,v in pairs(item) do
if type(k) == "string" and k ~= "name" then
params = params .. (";%s=%s"):format(k, type(v) == "table" and t_concat(v,",") or v);
end
end
return ("%s%s:%s"):format(item.name, params, value)
end
local function vcard_to_text(vcard)
local t={};
t_insert(t, "BEGIN:VCARD")
for i=1,#vcard do
t_insert(t, item_to_text(vcard[i]));
end
t_insert(t, "END:VCARD")
return t_concat(t, line_sep);
end
function to_text(vCards)
if vCards[1] and vCards[1].name then
return vcard_to_text(vCards)
else
local t = {};
for i=1,#vCards do
t[i]=vcard_to_text(vCards[i]);
end
return t_concat(t, line_sep);
end
end
local function from_xep54_item(item)
local prop_name = item.name;
local prop_def = vCard_dtd[prop_name];
local prop = { name = prop_name };
if prop_def == "text" then
prop[1] = item:get_text();
elseif type(prop_def) == "table" then
if prop_def.value then --single item
prop[1] = item:get_child_text(prop_def.value) or "";
elseif prop_def.values then --array
local value_names = prop_def.values;
if value_names.behaviour == "repeat-last" then
for i=1,#item.tags do
t_insert(prop, item.tags[i]:get_text() or "");
end
else
for i=1,#value_names do
t_insert(prop, item:get_child_text(value_names[i]) or "");
end
end
elseif prop_def.names then
local names = prop_def.names;
for i=1,#names do
if item:get_child(names[i]) then
prop[1] = names[i];
break;
end
end
end
if prop_def.props_verbatim then
for k,v in pairs(prop_def.props_verbatim) do
prop[k] = v;
end
end
if prop_def.types then
local types = prop_def.types;
prop.TYPE = {};
for i=1,#types do
if item:get_child(types[i]) then
t_insert(prop.TYPE, types[i]:lower());
end
end
if #prop.TYPE == 0 then
prop.TYPE = nil;
end
end
-- A key-value pair, within a key-value pair?
if prop_def.props then
local params = prop_def.props;
for i=1,#params do
local name = params[i]
local data = item:get_child_text(name);
if data then
prop[name] = prop[name] or {};
t_insert(prop[name], data);
end
end
end
else
return nil
end
return prop;
end
local function from_xep54_vCard(vCard)
local tags = vCard.tags;
local t = {};
for i=1,#tags do
t_insert(t, from_xep54_item(tags[i]));
end
return t
end
function from_xep54(vCard)
if vCard.attr.xmlns ~= "vcard-temp" then
return nil, "wrong-xmlns";
end
if vCard.name == "xCard" then -- A collection of vCards
local t = {};
local vCards = vCard.tags;
for i=1,#vCards do
t[i] = from_xep54_vCard(vCards[i]);
end
return t
elseif vCard.name == "vCard" then -- A single vCard
return from_xep54_vCard(vCard)
end
end
-- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd
vCard_dtd = {
VERSION = "text", --MUST be 3.0, so parsing is redundant
FN = "text",
N = {
values = {
"FAMILY",
"GIVEN",
"MIDDLE",
"PREFIX",
"SUFFIX",
},
},
NICKNAME = "text",
PHOTO = {
props_verbatim = { ENCODING = { "b" } },
props = { "TYPE" },
value = "BINVAL", --{ "EXTVAL", },
},
BDAY = "text",
ADR = {
types = {
"HOME",
"WORK",
"POSTAL",
"PARCEL",
"DOM",
"INTL",
"PREF",
},
values = {
"POBOX",
"EXTADD",
"STREET",
"LOCALITY",
"REGION",
"PCODE",
"CTRY",
}
},
LABEL = {
types = {
"HOME",
"WORK",
"POSTAL",
"PARCEL",
"DOM",
"INTL",
"PREF",
},
value = "LINE",
},
TEL = {
types = {
"HOME",
"WORK",
"VOICE",
"FAX",
"PAGER",
"MSG",
"CELL",
"VIDEO",
"BBS",
"MODEM",
"ISDN",
"PCS",
"PREF",
},
value = "NUMBER",
},
EMAIL = {
types = {
"HOME",
"WORK",
"INTERNET",
"PREF",
"X400",
},
value = "USERID",
},
JABBERID = "text",
MAILER = "text",
TZ = "text",
GEO = {
values = {
"LAT",
"LON",
},
},
TITLE = "text",
ROLE = "text",
LOGO = "copy of PHOTO",
AGENT = "text",
ORG = {
values = {
behaviour = "repeat-last",
"ORGNAME",
"ORGUNIT",
}
},
CATEGORIES = {
values = "KEYWORD",
},
NOTE = "text",
PRODID = "text",
REV = "text",
SORTSTRING = "text",
SOUND = "copy of PHOTO",
UID = "text",
URL = "text",
CLASS = {
names = { -- The item.name is the value if it's one of these.
"PUBLIC",
"PRIVATE",
"CONFIDENTIAL",
},
},
KEY = {
props = { "TYPE" },
value = "CRED",
},
DESC = "text",
};
vCard_dtd.LOGO = vCard_dtd.PHOTO;
vCard_dtd.SOUND = vCard_dtd.PHOTO;
return {
from_text = from_text;
to_text = to_text;
from_xep54 = from_xep54;
to_xep54 = to_xep54;
-- COMPAT:
lua_to_text = to_text;
lua_to_xep54 = to_xep54;
text_to_lua = from_text;
text_to_xep54 = function (...) return to_xep54(from_text(...)); end;
xep54_to_lua = from_xep54;
xep54_to_text = function (...) return to_text(from_xep54(...)) end;
};

View File

@ -19,27 +19,35 @@ Details
-------
mod\_register\_web has Prosody serve a web page where users can sign up
for an account. It implements reCAPTCHA to prevent automated sign-ups
(from bots, etc.).
for an account. It implements a CAPTCHA system to prevent automated
sign-ups (from bots, etc.).
Configuration
-------------
The module is served on Prosody's default HTTP ports at the path
`/register_web`. More details on configuring HTTP modules in Prosody can
be found in our [HTTP documentation](http://prosody.im/doc/http).
be found in our [HTTP documentation](https://prosody.im/doc/http).
To configure the CAPTCHA you need to supply a 'captcha\_options' option:
captcha_options = {
recaptcha_private_key = "12345";
recaptcha_public_key = "78901";
-- provider = "recaptcha";
-- recaptcha_private_key = "12345";
-- recaptcha_public_key = "78901";
-- provider = "hcaptcha";
-- hcaptcha_private_key = "12345";
-- hcaptcha_public_key = " 78901";
---- Default if no reCaptcha or hCaptcha options are set
-- provider = "simplecaptcha";
}
The keys for reCAPTCHA are available in your reCAPTCHA account, visit
[reCAPTCHA](https://developers.google.com/recaptcha/) for more info.
To only allow registration through this module, enter in the following
to your configuration:
If no reCaptcha options are set, a simple built in captcha is used.
registration_web_only = true
Customization
-------------
@ -52,6 +60,7 @@ Compatibility
-------------
----- --------------
0.12 Works
0.10 Works
0.9 Works
0.8 Doesn't work

View File

@ -25,8 +25,8 @@ function template(data)
return { apply = function(values) return (data:gsub("{([^}]+)}", values)); end }
end
local function get_template(name)
local fh = assert(module:load_resource(template_path..path_sep..name..".html"));
local function get_template(name, extension)
local fh = assert(module:load_resource(template_path..path_sep..name..extension));
local data = assert(fh:read("*a"));
fh:close();
return template(data);
@ -36,15 +36,30 @@ local function render(template, data)
return tostring(template.apply(data));
end
local register_tpl = get_template "register";
local success_tpl = get_template "success";
local register_tpl = get_template("register", ".html");
local success_tpl = get_template("success", ".html");
local web_verified;
local web_only = module:get_option_boolean("registration_web_only", false);
if web_only then
-- from mod_invites_register.lua
module:hook("user-registering", function (event)
local web_verified = event.web_verified;
if not web_verified then
event.allowed = false;
event.reason = "Registration on this server is through website only";
return;
end
end);
end
-- COMPAT `or request.conn:ip()`
if next(captcha_options) ~= nil then
local provider = captcha_options.provider;
if provider == nil or provider == "recaptcha" then
local recaptcha_tpl = get_template "recaptcha";
local recaptcha_tpl = get_template("recaptcha", ".html");
function generate_captcha(display_options)
return recaptcha_tpl.apply(setmetatable({
@ -77,7 +92,7 @@ if next(captcha_options) ~= nil then
end);
end
elseif provider == "hcaptcha" then
local captcha_tpl = get_template "hcaptcha";
local captcha_tpl = get_template("hcaptcha", ".html");
function generate_captcha(display_options)
return captcha_tpl.apply(setmetatable({
@ -93,7 +108,7 @@ if next(captcha_options) ~= nil then
function verify_captcha(request, form, callback)
http.request("https://hcaptcha.com/siteverify", {
body = http.formencode {
secret = captcha_options.captcha_private_key;
secret = captcha_options.hcaptcha_private_key;
remoteip = request.ip or request.conn:ip();
response = form["h-captcha-response"];
};
@ -116,8 +131,16 @@ else
local hmac_sha1 = require "util.hashes".hmac_sha1;
local secret = require "util.uuid".generate()
local ops = { '+', '-' };
local captcha_tpl = get_template "simplecaptcha";
function generate_captcha()
local captcha_tpl = get_template("simplecaptcha", ".html");
function generate_captcha(display_options, lang)
-- begin translation
if lang == "Español" then
s_question = "¿Qué es";
else
s_question = "What is";
end
-- end translation
local op = ops[random(1, #ops)];
local x, y = random(1, 9)
repeat
@ -135,54 +158,114 @@ else
end
local challenge = hmac_sha1(secret, answer, true);
return captcha_tpl.apply {
op = op, x = x, y = y, challenge = challenge;
op = op, x = x, y = y, challenge = challenge, s_question = s_question;
};
end
function verify_captcha(request, form, callback)
if hmac_sha1(secret, form.captcha_reply or "", true) == form.captcha_challenge then
callback(true);
else
-- begin translation
if form.lang == "Español" then
callback(false, "Verificación de Captcha fallida");
else
callback(false, "Captcha verification failed");
end
-- end translation
end
end
end
function generate_page(event, display_options)
function generate_page(event, lang, display_options)
local request, response = event.request, event.response;
-- begin translation
if lang == "Español" then
s_title = "Registro de cuenta XMPP";
s_username = "Nombre de Usuario";
s_password = "Contraseña";
s_passwordconfirm = "Contraseña Confirmación";
s_register = "¡Registro!";
else
s_title = "XMPP Account Registration";
s_username = "Username";
s_password = "Password";
s_passwordconfirm = "Confirm Password";
s_register = "Register!";
end
-- end translation
response.headers.content_type = "text/html; charset=utf-8";
return render(register_tpl, {
path = request.path; hostname = module.host;
notice = display_options and display_options.register_error or "";
captcha = generate_captcha(display_options);
})
captcha = generate_captcha(display_options, lang);
s_title = s_title;
s_username = s_username;
s_password = s_password;
s_passwordconfirm = s_passwordconfirm;
s_register = s_register;
s_lang = lang;
});
end
function register_user(form, origin)
local lang = form.lang;
local username = form.username;
local password = form.password;
local confirm_password = form.confirm_password;
local jid = nil;
form.username, form.password, form.confirm_password = nil, nil, nil;
form.password, form.confirm_password = nil, nil;
local prepped_username = nodeprep(username, true);
if not prepped_username then
-- begin translation
if lang == "Español" then
return nil, "Nombre de usuario contiene caracteres prohibidos";
else
return nil, "Username contains forbidden characters";
end
-- end translation
end
if #prepped_username == 0 then
-- begin translation
if lang == "Español" then
return nil, "El campo texto de nombre de usuario estaba vacío";
else
return nil, "The username field was empty";
end
-- end translation
end
if usermanager.user_exists(prepped_username, module.host) then
-- begin translation
if lang == "Español" then
return nil, "Nombre de usuario ya ocupado";
else
return nil, "Username already taken";
end
local registering = { username = prepped_username , host = module.host, additional = form, ip = origin.ip or origin.conn:ip(), allowed = true }
-- end translation
end
local registering = { username = prepped_username , host = module.host, additional = form, ip = origin.ip or origin.conn:ip(), allowed = true, web_verified = true }
module:fire_event("user-registering", registering);
if not registering.allowed then
-- begin translation
if lang == "Español" then
return nil, registering.reason or "Registro no permitido";
else
return nil, registering.reason or "Registration not allowed";
end
-- end translation
end
if confirm_password ~= password then
-- begin translation
if lang == "Español" then
return nil, "Las contraseñas no igualar";
else
return nil, "Passwords don't match";
end
-- end translation
end
local ok, err = usermanager.create_user(prepped_username, password, module.host);
if ok then
jid = prepped_username.."@"..module.host
@ -202,20 +285,41 @@ function register_user(form, origin)
source = module.name,
ip = origin.ip or origin.conn:ip(),
});
module:log("info", "New Account Registered: %s#%s@%s", prepped_username, origin.ip, module.host);
end
return jid, err;
end
function generate_success(event, jid)
return render(success_tpl, { jid = jid });
function generate_success(event, jid, lang)
local request, response = event.request, event.response;
-- begin translation
if lang == "Español" then
s_title = "¡Registro exitoso!";
s_message = "Tu cuenta es";
else
s_title = "Registration succeeded!";
s_message = "Your account is";
end
-- end translation
response.headers.content_type = "text/html; charset=utf-8";
return render(success_tpl, {
path = request.path;
jid = jid;
lang = lang;
s_title = s_title;
s_message = s_message;
});
end
function generate_register_response(event, jid, err)
function generate_register_response(event, jid, lang, err)
event.response.headers.content_type = "text/html; charset=utf-8";
if jid then
return generate_success(event, jid);
return generate_success(event, jid, lang);
else
return generate_page(event, { register_error = err });
return generate_page(event, lang, { register_error = err });
end
end
@ -225,9 +329,9 @@ function handle_form(event)
verify_captcha(request, form, function (ok, err)
if ok then
local jid, register_err = register_user(form, request);
response:send(generate_register_response(event, jid, register_err));
response:send(generate_register_response(event, jid, form.lang, register_err));
else
response:send(generate_page(event, { register_error = err }));
response:send(generate_page(event, form.lang, { register_error = err }));
end
end);
return true; -- Leave connection open until we respond above
@ -236,6 +340,7 @@ end
module:provides("http", {
title = module:get_option_string("register_web_title", "Account Registration");
route = {
["GET /style.css"] = render(get_template("style", ".css"), {});
GET = generate_page;
["GET /"] = generate_page;
POST = handle_form;

View File

@ -2,32 +2,47 @@
<html>
<head>
<meta charset="utf-8">
<title>XMPP Account Registration</title>
<link rel="stylesheet" type="text/css" href="{path}/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<h1>XMPP Account Registration</h1>
<div id="estilo3" class="formulario">
<h1>{s_title}</h1>
</div>
<div id="estilo2" class="formulario2">
<div>
<form action="{path}" method="POST">
<input name="lang" type="submit" value="English">
<input name="lang" type="submit" value="Español">
</form>
</div>
</div>
<div id="estilo" class="formulario">
<p class="error">{notice}</p>
<form action="{path}" method="POST">
<p>{notice}</p>
<table>
<tbody>
<tr>
<th>Username:</th>
<th>{s_username}:</th>
<td><input name="username" required>@{hostname}</td>
</tr>
<tr>
<th>Password:</th>
<th>{s_password}:</th>
<td><input name="password" required type="password"></td>
</tr>
<tr>
<th>Confirm Password:</th>
<th>{s_passwordconfirm}:</th>
<td><input name="confirm_password" required type="password"></td>
</tr>
{captcha}
<input name="lang" type="hidden" value="{s_lang}">
<tr>
<td colspan="2"><input type="submit" value="Register!"></td>
<td colspan="2"><input id="button" class="button" type="submit" value="{s_register}"></td>
</tr>
</tbody>
</table>
</form>
</div>
</body>
</html>

View File

@ -1,5 +1,7 @@
<tr>
<th>What is {x} {op} {y}?</th><td>
<th>{s_question} {x} {op} {y}?</th>
<td>
<input name="captcha_challenge" type="hidden" value="{challenge}">
<input name="captcha_reply" pattern="[0-9]+" required type="number">
</td></tr>
</td>
</tr>

View File

@ -0,0 +1,164 @@
body {
font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif;
font-size:12px;
}
h1 {
text-align:center;
}
p, h1, form, button {
border:0;
margin:0;
padding:0;
}
.spacer {
clear:both;
height:1px;
}
/* ----------- My Form ----------- */
.formulario {
margin:0 auto;
width:500px;
padding:14px;
}
.formulario2 {
margin:0 auto;
width:500px;
padding:14px;
}
/* ----------- stylized ----------- */
#estilo {
border:solid 2px #f2ddb7;
background:#fbf4eb;
}
#estilo2 {
border-left:solid 2px #f2ddb7;
border-right:solid 2px #f2ddb7;
background:#fbf4eb;
}
#estilo3 {
border:solid 2px #f2ddb7;
background:#fbf4eb;
}
#estilo h1 {
font-size:14px;
font-weight:bold;
margin-bottom:8px;
}
#estilo p {
font-size:11px;
color:#666666;
margin-bottom:20px;
border-bottom:solid 1px #f2ddb7;
padding-bottom:10px;
}
#estilo p.error {
font-size:12px;
font-weight:bold;
color:red;
margin-bottom:20px;
border-bottom:solid 1px #f2ddb7;
padding-bottom:10px;
text-align:center;
}
#estilo label {
display:block;
font-weight:bold;
text-align:right;
width:140px;
float:left;
}
#estilo .small {
color:#666666;
display:block;
font-size:11px;
font-weight:normal;
text-align:right;
width:140px;
}
#estilo input {
/* float:left; */
font-size:12px;
padding:4px 2px;
border:solid 1px #e4cfaa;
width:200px;
margin:2px 0 20px 10px;
}
#estilo th {
padding-bottom:18px;
}
#estilo2 div {
text-align:center;
}
#estilo2 input {
padding:5px;
}
#estilo3 h1 {
font-size:14px;
font-weight:bold;
margin-bottom:8px;
}
#estilo .button {
margin:2px 150px 20px 150px;
}
.button {
-moz-box-shadow:inset 0px 1px 0px 0px #fce3ca;
-webkit-box-shadow:inset 0px 1px 0px 0px #fce3ca;
box-shadow:inset 0px 1px 0px 0px #fce3ca;
background-color:#ffbb79;
-webkit-border-top-left-radius:18px;
-moz-border-radius-topleft:18px;
border-top-left-radius:18px;
-webkit-border-top-right-radius:18px;
-moz-border-radius-topright:18px;
border-top-right-radius:18px;
-webkit-border-bottom-right-radius:18px;
-moz-border-radius-bottomright:18px;
border-bottom-right-radius:18px;
-webkit-border-bottom-left-radius:18px;
-moz-border-radius-bottomleft:18px;
border-bottom-left-radius:18px;
text-indent:0;
border:1px solid #f59d46;
display:inline-block;
color:#ffffff;
font-family:Arial;
font-size:15px;
font-weight:bold;
font-style:normal;
height:40px;
line-height:40px;
width:100px;
text-decoration:none;
text-align:center;
text-shadow:1px 1px 0px #ce7a28;
}
.button:hover {
background-color:#ee9741;
}
.button:active {
position:relative;
top:1px;
}

View File

@ -2,12 +2,20 @@
<html>
<head>
<meta charset="utf-8">
<title>Registration succeeded!</title>
<link rel="stylesheet" type="text/css" href="{path}/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<title>{s_title}</title>
</head>
<body>
<h1>Registration succeeded!</h1>
<p>Your account is</p>
<pre>{jid}</pre>
<p>- happy chatting!</p>
<div id="estilo" class="formulario">
<h1>{s_title}</h1>
<p></p>
<h1>{s_message}:</h1>
<h1><i>{jid}</i></h1>
<!--
<br />
<h1>- happy chatting! -</h1>
-->
</div>
</body>
</html>