Commit Graph
6 Commits
Author SHA1 Message Date
Lucas RayanandClaude Opus 5 3a82d82428 #22: fix the admin lockout, and the regressions the UI rewrite brought back
The console demanded a second factor and refused to open without one, but
enrolment lived on the other surface - so an operator whose only account
was the administrator had no way in. The password was already verified at
that point, so the refusal now carries a short-lived grant that permits
enrolling a factor and nothing else, and the admin login screen walks
through it: secret, confirmation, recovery codes, sign in. Two tests cover
it, including that the grant opens no other endpoint.

The rewrite also undid work that was already in place:

  - The postcss override was dropped and the advisory came back, along
    with a critical one in vitest. postcss is a direct dependency now, so
    the fix is the version plus an override scoped to Next's own copy;
    vitest moves to 3.2.7. The lockfile needed regenerating for either to
    take effect. Zero advisories now.

  - 100 language-lint violations: the admin console had been rewritten
    with its strings in Portuguese, hardcoded. That fails CI, and it also
    puts those strings beyond the reach of the translation bundles - an
    English or Spanish user would have read Portuguese. They are English
    in the source again.

  - The mail screen put a fixed 320px list beside the reader with no
    breakpoint, so on a phone the viewport overflowed and the message was
    a few pixels wide. One pane at a time below md, with a way back.

  - The icon-only navigation toggles had nothing for a screen reader to
    announce.

The layouts' own mobile navigation and every previously fixed behaviour
survived the rewrite - checked rather than assumed.

Also fixes a marker collision I introduced: the E2E helper locates the
repository by a migrations directory, and services/migrations satisfied
that one directory too early. It now requires the SQL to actually be
there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 16:13:14 -03:00
Lucas RayanandClaude Opus 5 30014f0f41 #21: fix the DKIM, onboarding and console defects found reviewing the new work
DKIM rotation broke the domain it was run on. It retired the live key and
inserted a placeholder as ACTIVE - private_key_enc was the literal string
"dummy-encrypted-key" - so FindActiveKeys then failed for the whole
domain and outbound mail stopped being signed, with the real key already
gone. Reproduced against a database before changing anything.

The root cause runs deeper: the auth service derives its encryption key
with a plain SHA-256 of the master key while the protocols service uses
HKDF with its own info string, so nothing the auth service seals can ever
be opened by the signer. Rotation therefore moves to the protocols
service, which owns that vault and the real key generator. It retires the
previous key with a seven-day overlap in the same transaction rather than
deleting it, because mail already sent under it is still being verified.

Domain onboarding had three defects in one function. It built the alias
destination by interpolating the domain name into the SQL text, which was
injectable; it reused one placeholder for a CITEXT and a VARCHAR column,
which made the statement fail and happened to hide the injection; and it
ignored its scope argument entirely, so any DOMAIN_ADMIN could create
domains. It also provisioned postmaster, abuse and security, missing the
dmarc and tlsrpt aliases that the published records point at - PLAN.md
section 7.4b is explicit that reports bounce without them.

The TLS panel returned fixed values: acme_status "VALID" and 72 days to
expiry, whatever the process actually held. It now reads the certificate
watcher, and a compile-time assertion keeps the wiring from silently
regressing into "disabled". The audit log and the readiness scorecard were
fixtures too, and their field names did not match the API they now read.

Two tests failed and a third asserted a fabricated constant: one depended
on test order through a shared fixture that had a second factor enrolled,
one used a fixed domain name and so passed once per database, and one
expected "records: 13" from a function that never touches DNS. All three
are fixed, and the suite now passes twice in a row against one database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 13:33:33 -03:00
Lucas Rayan 56a176d1f0 #21: enhance webmail with rich-text editor, DOMPurify sanitizer, attachments, sieve builder, and vacation responder 2026-08-02 10:37:03 -03:00
Lucas Rayan 6e01fc7ffb #20: implement F4 webmail, admin console, 2FA TOTP, surface isolation, and i18n 2026-08-02 08:39:00 -03:00
Lucas RayanandClaude Opus 5 46d421c874 #19: add Trivy scanning and multi-architecture GHCR publishing
CI gains a security job (Trivy fs: vuln, secret and misconfig) and an
image job that builds each of the four services and scans the result. The
secret scanner earns its place here: a DKIM key or Cloudflare token
committed by accident is a full compromise of every domain served.

release.yml publishes on a merge into main and on v* tags. Four services
on two architectures is eight build jobs pushing by digest, then four
publish jobs joining each pair into one manifest list, tagged latest, sha
and semver. github.repository is lowercased first because GHCR rejects an
uppercase path and this repository is lucasrguerra/LambdaMail.

Both scanners failed on first run, so this commit also fixes what they
found rather than shipping a workflow that is red on arrival:

  - All four images ran as root (DS-0002), which section 17 risk R5
    explicitly argues against. They now run unprivileged; protocols keeps
    its eight ports below 1024 through cap_net_bind_service on the binary
    rather than through being root.

  - A HIGH advisory in postcss, reached only through Next. "npm audit fix
    --force" would have resolved it by downgrading Next from 15 to 9, so
    it is pinned with an override instead.

  - The Node images copied the build stage's node_modules wholesale,
    shipping typescript, eslint and vitest into production, and the base
    image's own bundled npm accounted for the rest of the CVEs. Runtime
    dependencies now come from npm ci --omit=dev and npm is removed from
    the final layer.

  - compose-up could never have passed: the proxy network is external
    because Coolify owns it on the deploy host, and nothing creates it in
    CI, so compose refused to start the stack at all.

Verified locally: actionlint clean, all ten action refs confirmed to
exist, Trivy fs and all four image scans exit 0, the full stack reaches
Healthy on every container, and the containers run as uid 10001/1000 with
all eight privileged ports bound and SMTP answering on 25. The arm64
build itself is unverified - this machine has no buildx - and will first
be exercised by release.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 07:58:02 -03:00
Lucas Rayan eb09fa0ced #1: implement F0 foundation - monorepo, migrations, Go domain layer, service skeletons, CI
Stands up the LambdaMail monorepo per PLAN.md's F0 milestone: local dev
scaffolding (.env.example, Makefile, .gitignore excluding PLAN.md/docs/
.superpowers/), the full initial Postgres schema as a golang-migrate
migration, the complete Go domain layer for services/protocols (value
objects, entities, domain events - zero external imports beyond
github.com/google/uuid) with unit tests, health-checked service skeletons
for auth/storage (TypeScript) and webmail (Next.js), a local
docker-compose.yaml wiring all services together, a language-enforcement
lint script, and a GitHub Actions CI workflow.

Built via 11 sequential tasks under subagent-driven development, each with
an independent implementer + reviewer cycle. Fixes applied during review:
DomainName.IsPunycode() added to satisfy the promised interface; ioredis
default-import typing under NodeNext; next.js bumped 15.0.3->15.5.21 to
resolve high/critical CVEs; lang-lint.mjs corrected to not flag its own
banned-terms list; web service healthcheck added so compose --wait
actually waits on it; webmail Dockerfile's missing public/ directory:
protocols go.mod version aligned with CI's Go 1.22 pin.

Known follow-ups for later phases (not blocking F0): go-arch-lint isn't
wired into CI yet; live docker compose up and migration apply have not
been exercised in this dev environment (no Docker/psql available here)
and will get their first real run in CI, which does have Docker.
2026-07-23 05:05:46 -03:00