mirror of
https://github.com/chatmail/relay.git
synced 2026-09-02 13:33:14 +00:00
feat: distinguish AUTHENTICATION_FAILED/UNAVAILABLE login failures
on doveauth.py being down, users would "AUTHENTIFICATIONFAILED"
while they now get:
IMAP failed to login as tovnlbmsz@_lua0.localchat: no response:
code: None, info: Some("[UNAVAILABLE] Temporary authentication
failure. [lua0-localchat:2026-09-01 11:00:17]")
This commit is contained in:
@@ -31,7 +31,7 @@ end
|
||||
local function create(user, password)
|
||||
local request = http_client:request({url = create_url, method = "POST"})
|
||||
request:set_payload(user .. "\t" .. password)
|
||||
return request:submit():status() == 200
|
||||
return request:submit():status()
|
||||
end
|
||||
|
||||
-- Entry points called by dovecot
|
||||
@@ -54,7 +54,11 @@ function auth_password_verify(req, password)
|
||||
end
|
||||
local hash = password_hash(req.user)
|
||||
if hash == nil then
|
||||
if not create(req.user, password) then
|
||||
-- doveauth refuses with 4xx; dovecot reports its own failures as 9000 and up
|
||||
local status = create(req.user, password)
|
||||
if status >= 500 then
|
||||
return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, {}
|
||||
elseif status ~= 200 then
|
||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, {}
|
||||
end
|
||||
elseif req:password_verify(hash, password) ~= 1 then
|
||||
|
||||
@@ -8,7 +8,7 @@ from cmdeploy.basedeploy import get_resource
|
||||
|
||||
USER1 = "user12345@chat.example.org"
|
||||
USER2 = "newuser12@chat.example.org"
|
||||
OK, UNKNOWN, MISMATCH = 1, -2, -3
|
||||
OK, UNKNOWN, MISMATCH, INTERNAL = 1, -2, -3, -4
|
||||
|
||||
DOVECOT_MOCKS = """
|
||||
create_status = 200
|
||||
@@ -18,6 +18,7 @@ dovecot = {
|
||||
PASSDB_RESULT_OK = OK,
|
||||
PASSDB_RESULT_USER_UNKNOWN = UNKNOWN,
|
||||
PASSDB_RESULT_PASSWORD_MISMATCH = MISMATCH,
|
||||
PASSDB_RESULT_INTERNAL_FAILURE = INTERNAL,
|
||||
USERDB_RESULT_OK = OK,
|
||||
USERDB_RESULT_USER_UNKNOWN = UNKNOWN,
|
||||
},
|
||||
@@ -40,7 +41,8 @@ dovecot = {
|
||||
|
||||
|
||||
def load_authlua(lua, config):
|
||||
lua.g.OK, lua.g.UNKNOWN, lua.g.MISMATCH = OK, UNKNOWN, MISMATCH
|
||||
lua.g.OK, lua.g.UNKNOWN = OK, UNKNOWN
|
||||
lua.g.MISMATCH, lua.g.INTERNAL = MISMATCH, INTERNAL
|
||||
lua.rt.execute(DOVECOT_MOCKS)
|
||||
template = jinja2.Template(get_resource("dovecot/auth.lua.j2").read_text())
|
||||
lua.rt.execute(template.render(config=config))
|
||||
@@ -147,14 +149,22 @@ def test_unknown_address_is_created_via_endpoint(authlua, request_for):
|
||||
assert authlua.g.create_request["url"] == "http://127.0.0.1:10084/create"
|
||||
|
||||
|
||||
# a policy refusal and a doveauth that is down are both fail-closed
|
||||
@pytest.mark.parametrize("status", [403, 0])
|
||||
def test_creation_that_is_not_answered_with_200_is_user_unknown(
|
||||
# doveauth refusing is the user's problem, doveauth failing is ours
|
||||
@pytest.mark.parametrize("status", [400, 403, 404])
|
||||
def test_creation_refused_by_doveauth_is_user_unknown(authlua, request_for, status):
|
||||
authlua.g.create_status = status
|
||||
res, _ = authlua.g.auth_password_verify(request_for(USER2), "brandnewpass")
|
||||
assert res == UNKNOWN
|
||||
|
||||
|
||||
# 9003 is dovecot's own CONNECT_FAILED, what a stopped doveauth actually yields
|
||||
@pytest.mark.parametrize("status", [500, 502, 9003, 9005])
|
||||
def test_creation_that_doveauth_could_not_answer_is_internal_failure(
|
||||
authlua, request_for, status
|
||||
):
|
||||
authlua.g.create_status = status
|
||||
res, _ = authlua.g.auth_password_verify(request_for(USER2), "brandnewpass")
|
||||
assert res == UNKNOWN
|
||||
assert res == INTERNAL
|
||||
|
||||
|
||||
def test_userdb_unknown_before_creation_ok_after(authlua, request_for, create_user):
|
||||
|
||||
Reference in New Issue
Block a user