5bbbb8512884bc8fcb1dd39ff45c145159ecf257
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5bbbb85128 |
release: 1.0.0, a changelog, one .env.example and the docs to match
CI / Lint de idioma (R9) (push) Successful in 5s
CI / Go (protocols) (push) Successful in 2m34s
CI / TypeScript (storage) (push) Successful in 15s
CI / Testes (webmail) (push) Successful in 51s
CI / TypeScript (auth) (push) Successful in 2m39s
CI / Trivy (dependências, segredos e Dockerfiles) (push) Successful in 9s
CI / Imagem (migrations) (push) Successful in 5s
CI / Imagem (auth) (push) Successful in 17s
CI / Imagem (protocols) (push) Successful in 7s
CI / Imagem (rspamd) (push) Successful in 5s
CI / Imagem (storage) (push) Successful in 17s
CI / Imagem (webmail) (push) Successful in 1m52s
CI / Stack completa (push) Successful in 1m11s
- CHANGELOG.md and CHANGELOG.pt-BR.md, from the project history. - .env.example now serves development and production; scripts/init-env.sh (make env) creates .env with random secrets. .env.production.example is gone. - docs/RELEASING.md: the pipeline, how a version is cut and deployed, the runners. - DEPLOYMENT.md and both READMEs updated for Gitea and the new env flow. - Service versions set to 1.0.0. - CI: the proxy network step is idempotent and the stack is torn down always, since the runner's Docker daemon now outlives a job. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
833dc76c2e |
#21: make the auth service actually authenticate
The F4 auth service issued a valid session for any email and any password, and accepted any non-empty string as a second factor. Reproduced against the router before touching it: an invented password plus the code "000000" returned a SUPER_ADMIN token that opened /api/v1/admin/dashboard. Passwords. Hashing moves from PBKDF2 to Argon2id via hash-wasm, which is what PLAN.md section 9 and the Go service already use - the previous scheme meant a password set through the mail stack could never verify here. Interoperability was checked in both directions before adopting the library: a hash written by alexedwards/argon2id verifies in Node and one written here verifies in Go. hash-wasm is pure WASM, so the multi-arch Alpine images need no native build. verifyPassword no longer falls back to comparing the password against the stored hash. That fallback turned every stored hash into a working credential: anyone with a database dump could authenticate by sending the hash back. Anything that is not an argon2id PHC string is now refused. Logins are decided against the database, with the failure counter and the temporary lockout of PLAN.md section 5.2. An unknown address and a wrong password give the same answer, and the unknown case still pays for a hash so response time does not enumerate valid addresses. Second factor. The TOTP secret is generated server-side, stored encrypted and never accepted back from the client - the confirm step used to take whichever secret the caller sent, so anyone could confirm with their own. Recovery codes are Argon2id, single-use, and consumed under a guard so two concurrent requests cannot spend the same one. App passwords are issued and revoked through real endpoints instead of browser state. Sessions gain a "purpose" claim. Without it the challenge token - handed out after the password but before the second factor - satisfied every check a real session did, which skipped MFA entirely. The dev seed carried a hand-written password_hash whose digest was 24 bytes instead of 32, so it matched no password at all. Nothing noticed while passwords went unverified; it is a real hash now. Verified with 30 tests, 13 of them against a real Postgres: wrong password, unknown account, pass-the-hash, wrong TOTP code, challenge token replay, lockout, single-use recovery codes, and the surface isolation that F4's acceptance criteria require. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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. |