Commit Graph
4 Commits
Author SHA1 Message Date
Lucas RayanandClaude Opus 5 c7251211b5 #29: add reset-mfa, so a broken enrolment does not cost the mailbox
An enrolment can end up confirmed against a secret the authenticator no longer
holds - the account is then locked behind codes nobody can produce, and the
only documented recovery was a DELETE against mfa_totp. That SQL was also
incomplete: it left the recovery codes valid and the sessions live.

reset-mfa removes the authenticator secrets and the recovery codes, and signs
the account's sessions out, because a live session would keep the console open
without the factor that was just removed. The password is untouched, so the
command alone grants nobody access.

The alternative people reach for - delete the mailbox and create it again -
takes its mail, folders and aliases with it, and none of those are the problem.
The docs now say so.

Also corrects the advice printed after create-admin, which still told the
operator to enrol from the webmail settings before the console would open. The
console has enrolled inline since #23.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:20:21 -03:00
Lucas RayanandClaude Opus 5 084b1bc204 #23: document the new sign-in path, container health and the gaps found
The deployment guide still described enrolling a second factor from webmail
settings before the console would open. That detour is gone: the console
enrols one inline, and the console itself is now reached from a link inside
the app rather than from a chooser at the root.

Also records what the console does not do, since it previously implied
otherwise on screen: DNS records are not verified per record, attachments are
not uploaded, and the vacation responder, signature and Sieve rules are stored
but not yet consumed during delivery.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 19:15:43 -03:00
Lucas RayanandClaude Opus 5 2282dc96e9 #21: run the new images as non-root, and scope the image scan honestly
Trivy failed the two images added in the previous commit. The Dockerfile
rule was straightforward - neither declared a user - and both now run
unprivileged, with the migrate base moved from v4.17.1 to v4.19.1 because
the older one sits on Alpine 3.19 with six HIGH advisories.

The rest is not fixable here. What remains in those two images is the
contents of golang-migrate and Rspamd themselves: 37 and 48 fixable
HIGH/CRITICAL findings that belong to those projects. Our layers add .sql
and .conf files and nothing else, so gating this pipeline on them would
gate it on somebody else's release cadence, and a blanket ignore rule
would quietly weaken the scan for the images we do control.

The image scan therefore covers the four images whose contents this
project authors, with the reason stated at the step and the upstream pair
recorded in OPERATIONS.md as something to watch. Both are still built in
CI, so a broken Dockerfile still fails.

Worth being clear: these two images were already running in the stack
before this change. Putting them in the matrix made their state visible;
it did not create it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 14:48:48 -03:00
Lucas RayanandClaude Opus 5 c0387f6961 #21: make the stack deployable, and document how
Three things stood between this and a real deployment.

A missing JWT_SECRET fell back to a constant that is published in this
repository, so any deployment that forgot the variable would have accepted
session tokens anyone could mint, including an admin one. The auth service
now refuses to start without a secret of at least 32 characters. The test
suites set it through vitest config, because ESM hoists imports and an
assignment inside a test file runs too late.

Session cookies were issued without the Secure attribute: it is driven by
NODE_ENV, which compose never set. Behind a TLS terminator that is a
downgrade waiting to happen.

Nothing created the first mailbox. A fresh install has no domain and no
account, and every route into the console needs one - so there was no
first login. There is now a create-admin command, which also provisions
the standard folders and the four system aliases. It is a deliberate
command rather than a bootstrap on boot: creating an administrator as a
side effect of starting a container is how a deleted account quietly comes
back.

Also: the production images were shipping compiled test files, since the
build compiled everything under src. A separate build config excludes
them while lint still type-checks them.

Documentation. README.md covers what this is, how it is put together, and
what is not finished - stated plainly, because a mail server that half
works loses mail. docs/DEPLOYMENT.md is the Coolify path in the order it
has to happen, starting with the three prerequisites no code can satisfy:
outbound port 25, the PTR record, and the DNSSEC delegation.
docs/OPERATIONS.md is the runbook - health states, certificates, backups,
reading a delivery failure, DKIM rotation, and an explicit list of what is
not automated. .env.production.example replaces guessing which of the dev
defaults are safe: none of them are.

docs/ was gitignored as a place for internal notes; only docs/internal/ is
now, so these guides ship with the code.

Every command the documentation tells an operator to run was executed
against a running stack: create-admin, preflight, the health endpoint, the
published mail ports, and the Secure cookie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 13:33:51 -03:00