feat(auth): codes go by mail, through the SMTP service an instance sets up

Email sign-in's codes are mailed over SMTP (pub.dev's mailer), through
whichever service the passwords name: emailSmtpHost, emailSmtpPort,
emailSmtpUser, emailSmtpPassword and emailFrom (EMAIL_SMTP_* and
EMAIL_FROM in deploy/.env, with EMAIL_SIGN_IN_PEPPER). TLS only:
STARTTLS, or TLS directly on 465. learn.halkyon.eu sends through Proton's
SMTP submission. Without a service, codes still go to the log in
development, and elsewhere there is no email sign-in.

The mail is plain text, in English or German: the code first and in the
subject, so it shows in the notification and the system can offer it,
the instance it signs in to, its 15 minutes, and that it can be ignored;
no links, no tracking, no HTML. A mail that couldn't be sent is a refusal
of its own (mailFailed): nothing of the request is kept, and the log
names the error's type only, as the service's answer may name the
address. A fake SMTP server on loopback drives the real mailer in the
tests: it delivers, and nothing goes to a server without TLS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-15 20:00:52 +02:00
commit 8075edb152
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -56,10 +56,19 @@ tries and completion tokens (`SecretChallengeUtil`), and a rate limiter
one network (IP address); the rate limiter's notes, which hold the IP address, go
hourly with the anonymous sign-ups' (`forgetOldSignUpAttempts`), and codes past their
time hourly too (`forgetExpiredEmailCodes`).
7. **Mail.** In development, codes go to the server's log (`LogCodeMailer`). Elsewhere
the instance offers no email sign-in (`instance.info().emailSignIn`) until its
operator sets up a mail service: chosen before the first deploy that brings this
(Proton SMTP and Scaleway TEM were the candidates, 11 Oct).
7. **Mail.** Codes go over SMTP, through whichever service the operator sets up in the
passwords (`emailSmtpHost`, `emailSmtpPort`, `emailSmtpUser`, `emailSmtpPassword`,
`emailFrom`; `deploy/.env` in production), with pub.dev's `mailer` (MIT), over TLS
only (STARTTLS, or TLS directly on port 465). learn.halkyon.eu sends through Proton's
SMTP submission (decided 2026-10-15, over Scaleway TEM): a token for an address on
its own domain. The mail is plain text: the code first and in the subject, so it
shows in the notification and the system can offer it, which instance it signs in
to, its 15 minutes, and that it can be ignored; no links, no tracking, no HTML. A
mail that couldn't be sent is a refusal of its own (`mailFailed`), keeping nothing
of the request; the log names the error's type only, as the service's answer may
name the address. Without a service, codes go to the server's log in development
(`LogCodeMailer`), and elsewhere the instance offers no email sign-in
(`instance.info().emailSignIn`).
8. **The pepper never changes** once anybody signs in with an address: every hash would
be lost, and with it those people's way in. Rotating it would need a list of earlier
peppers tried in turn, which isn't built.