view mod_register_web/mod_register_web.lua @ 1268:854a3933cfcd

mod_muc_log_http: URL-encode room names. This allows special characters in room names to work. Ideally this escaping shouldn’t be done in the user visible content, but the module’s template system doesn’t currently allow that.
author Waqas Hussain <waqas20@gmail.com>
date Sat, 04 Jan 2014 16:50:57 -0500
parents 34fbe58d19da
children e670d4cc5027
line wrap: on
line source

local captcha_options = module:get_option("captcha_options", {});
local nodeprep = require "util.encodings".stringprep.nodeprep;
local usermanager = require "core.usermanager";
local http = require "net.http";

local extra_fields = {
	nick = true; name = true; first = true; last = true; email = true;
	address = true; city = true; state = true; zip = true;
	phone = true; url = true; date = true;
}

function template(data)
	-- Like util.template, but deals with plain text
	return { apply = function(values) return (data:gsub("{([^}]+)}", values)); end }
end

local function get_template(name)
	local fh = assert(module:load_resource("templates/"..name..".html"));
	local data = assert(fh:read("*a"));
	fh:close();
	return template(data);
end

local function render(template, data)
	return tostring(template.apply(data));
end

local register_tpl = get_template "register";
local success_tpl = get_template "success";

if next(captcha_options) ~= nil then
	local recaptcha_tpl = get_template "recaptcha";

	function generate_captcha(display_options)
		return recaptcha_tpl.apply(setmetatable({
	  		recaptcha_display_error = display_options and display_options.recaptcha_error
	  			and ("&error="..display_options.recaptcha_error) or "";
	  	}, {
	  		__index = function (t, k)
	  			if captcha_options[k] then return captcha_options[k]; end
	  			module:log("error", "Missing parameter from captcha_options: %s", k);
			end
		}));
	end
	function verify_captcha(request, form, callback)
		http.request("https://www.google.com/recaptcha/api/verify", {
			body = http.formencode {
				privatekey = captcha_options.recaptcha_private_key;
				remoteip = request.conn:ip();
				challenge = form.recaptcha_challenge_field;
				response = form.recaptcha_response_field;
			};
		}, function (verify_result, code)
			local verify_ok, verify_err = verify_result:match("^([^\n]+)\n([^\n]+)");
			if verify_ok == "true" then
				callback(true);
			else
				callback(false, verify_err)
			end
		end);
	end
else
	module:log("debug", "No Recaptcha options set, using fallback captcha")
	local random = math.random;
	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 op = ops[random(1, #ops)];
		local x, y = random(1, 9)
		repeat
			y = random(1, 9);
		until x ~= y;
		local answer;
		if op == '+' then
			answer = x + y;
		elseif op == '-' then
			if x < y then
				-- Avoid negative numbers
				x, y = y, x;
			end
			answer = x - y;
		end
		local challenge = hmac_sha1(secret, answer, true);
		return captcha_tpl.apply {
			op = op, x = x, y = y, challenge = challenge;
		};
	end
	function verify_captcha(request, form, callback)
		if hmac_sha1(secret, form.captcha_reply, true) == form.captcha_challenge then
			callback(true);
		else
			callback(false, "Captcha verification failed");
		end
	end
end

function generate_page(event, display_options)
	local request = event.request;

	return render(register_tpl, {
		path = request.path; hostname = module.host;
		notice = display_options and display_options.register_error or "";
		captcha = generate_captcha(display_options);
	})
end

function register_user(form)
	local prepped_username = nodeprep(form.username);
	if not prepped_username then
		return nil, "Username contains forbidden characters";
	end
	if #prepped_username == 0 then
		return nil, "The username field was empty";
	end
	if usermanager.user_exists(prepped_username, module.host) then
		return nil, "Username already taken";
	end
	local registering = { username = prepped_username , host = module.host, allowed = true }
	module:fire_event("user-registering", registering);
	if not registering.allowed then
		return nil, "Registration not allowed";
	end
	local ok, err = usermanager.create_user(prepped_username, form.password, module.host);
	if ok then
		local extra_data = {};
		for field in pairs(extra_fields) do
			local field_value = form[field];
			if field_value and #field_value > 0 then
				extra_data[field] = field_value;
			end
		end
		if next(extra_data) ~= nil then
			datamanager.store(prepped_username, module.host, "account_details", extra_data);
		end
		module:fire_event("user-registered", {
			username = prepped_username,
			host = module.host,
			source = module.name
		});
	end
	return ok, err;
end

function generate_success(event, form)
	return render(success_tpl, { jid = nodeprep(form.username).."@"..module.host });
end

function generate_register_response(event, form, ok, err)
	local message;
	if ok then
		return generate_success(event, form);
	else
		return generate_page(event, { register_error = err });
	end
end

function handle_form(event)
	local request, response = event.request, event.response;
	local form = http.formdecode(request.body);
	verify_captcha(request, form, function (ok, err)
		if ok then
			local register_ok, register_err = register_user(form);
			response:send(generate_register_response(event, form, register_ok, register_err));
		else
			response:send(generate_page(event, { register_error = err }));
		end
	end);
	return true; -- Leave connection open until we respond above
end

module:provides("http", {
	route = {
		GET = generate_page;
		["GET /"] = generate_page;
		POST = handle_form;
		["POST /"] = handle_form;
	};
});