view mod_password_policy/mod_password_policy.lua @ 4976:75b6e5df65f9

various: Improve error reporting if missing file server module on 0.12 If there is some error loading net.http.files then it would be swallowed by the pcall and then it would proceed to trying mod_http_files, which might cause unexpected behavior on 0.12 Ref #1765
author Kim Alvefur <zash@zash.se>
date Mon, 18 Jul 2022 22:47:54 +0200
parents bfd4af4caddc
children d3b69859553a
line wrap: on
line source

-- Password policy enforcement for Prosody
--
-- Copyright (C) 2012 Waqas Hussain
--
--
-- Configuration:
--    password_policy = {
--        length = 8;
--    }


local it = require "util.iterators";
local set = require "util.set";
local st = require "util.stanza";

local options = module:get_option("password_policy");

options = options or {};
options.length = options.length or 8;
if options.exclude_username == nil then
	options.exclude_username = true;
end

local builtin_policies = set.new({ "length", "exclude_username" });
local extra_policies = set.new(it.to_array(it.keys(options))) - builtin_policies;

local extra_policy_handlers = {};

module:handle_items("password-policy-provider", function (event)
	-- Password policy handler added
	local item = event.item;
	module:log("error", "Adding password policy handler '%s'", item.name);
	extra_policy_handlers[item.name] = item.check_password;
end, function (event)
	-- Password policy handler removed
	local item = event.item;
	extra_policy_handlers[item.name] = nil;
end);

function check_password(password, additional_info)
	if not password or password == "" then
		return nil, "No password provided", "no-password";
	end

	if #password < options.length then
		return nil, ("Password is too short (minimum %d characters)"):format(options.length), "length";
	end

	if additional_info then
		local username = additional_info.username;
		if username and password:lower():find(username:lower(), 1, true) then
			return nil, "Password must not include your username", "username";
		end
	end

	for policy in extra_policies do
		local handler = extra_policy_handlers[policy];
		if not handler then
			module:log("error", "No policy handler found for '%s' (typo, or module not loaded?)", policy);
			return nil, "Internal error while verifying password", "internal";
		end
		local ok, reason_text, reason_name = handler(password, options[policy], additional_info);
		if ok ~= true then
			return nil, reason_text or ("Password failed %s check"):format(policy), reason_name or policy;
		end
	end

	return true;
end

function get_policy() --luacheck: ignore 131/get_policy
	return options;
end

function handler(event)
	local origin, stanza = event.origin, event.stanza;

	if stanza.attr.type == "set" then
		local query = stanza.tags[1];

		local passwords = {};

		local dataform = query:get_child("x", "jabber:x:data");
		if dataform then
			for _,tag in ipairs(dataform.tags) do
				if tag.attr.var == "password" then
					table.insert(passwords, tag:get_child_text("value"));
				end
			end
		end

		table.insert(passwords, query:get_child_text("password"));

		local additional_info = {
			username = origin.username;
		};

		for _,password in ipairs(passwords) do
			if password then
				local pw_ok, pw_err, pw_failed_policy = check_password(password, additional_info);
				if not pw_ok then
					module:log("debug", "Password failed check against '%s' policy", pw_failed_policy);
					origin.send(st.error_reply(stanza, "cancel", "not-acceptable", pw_err));
					return true;
				end
			end
		end
	end
end

module:hook("iq/self/jabber:iq:register:query", handler, 10);
module:hook("iq/host/jabber:iq:register:query", handler, 10);
module:hook("stanza/iq/jabber:iq:register:query", handler, 10);