fix(auth): app passwords now sign in SMTP, IMAP and POP3

The webmail creates app passwords (shown once, stored hashed, with scopes) but
the protocol servers only compared a login against mailboxes.password_hash, so
an app password was always refused with 535 / authentication failed. They exist
because mail clients cannot do a second factor.

- port.MailboxAuth carries the mailbox's app passwords (newest 20) with their
  scopes; the Postgres repository loads them and stamps last_used_at on use
- verifyCredential accepts the mailbox password or an app password that holds
  the scope of the protocol; SMTP, IMAP and POP3 use it. ManageSieve keeps the
  mailbox password only, as no default scope covers it
- a malformed app password hash is skipped instead of failing the login; a
  malformed mailbox hash still surfaces as the server-side error it was

Tests: unit cases for scopes and malformed hashes, the three entry points,
repository tests against Postgres, and an end-to-end test on the real
submission server that fails with 535 without the fix and passes with it.
Full `go test ./...` with TEST_DATABASE_URL, go vet and lang-lint are clean.
This commit is contained in:
Lucas Rayan committed 2026-10-16 09:21:08 -03:00
1 parent 8e7c0abd0a
commit 110a240cd9
10 files changed
+599 -5

No files matched your search

+17
View File
@@ -93,6 +93,23 @@ make reset-password EMAIL=… PASSWORD='…' # recovers a lockout, ends its se
make reset-mfa EMAIL=… # clears every second factor
```
### App passwords for mail clients
A mail client cannot answer a second factor, so an account signs a client in
with an **app password**: create one in the webmail (Settings, app passwords),
copy it once, and give it to the client as the account password. It is shown a
single time and stored only as an Argon2id hash.
An app password works for SMTP submission, IMAP and POP3, as its `scopes`
column allows (the default is all three). A scope limits it: an app password
with only `imap` can read mail and cannot send. ManageSieve takes the mailbox
password only. The mailbox password keeps working everywhere, and a login with
an app password stamps its `last_used_at`, which the settings screen shows.
Until this was wired in, the protocols compared every password only against the
mailbox password, so an app password was created, shown and then refused by
SMTP, IMAP and POP3 with `535 Authentication credentials invalid`.
An account locks itself for 15 minutes after 5 failed passwords. That is the
lockout working, not a fault; `reset-password` clears it.