feat(mail): deliver email one-time codes over SMTP and add the setup email screen
Felis never actually sent mail: OTP codes for onboarding, email login and
op-login were only written to the felis-api log behind a "demo has no SMTP"
limitation, and the Settings/SMTP flow those comments promised was never
built. Combined with the bootstrap Owner's address being recorded unverified
(87279a1), op-login start always took the anti-enumeration neutral branch and
minted a fake request_id, so the in-game approve inevitably answered "No
pending operator sign-in with that code".
Give the codes a real delivery path, configured in felis.toml rather than a
web settings page so config keeps a single source of truth:
- config: new [smtp] table (host, port defaulting to 587, from, username,
password_ref). Validation requires a plausible from address and a sane
port; the password itself never enters the config file.
- internal/mail (new): stdlib net/smtp mailer implementing the api.OTPMailer
seam. Port 465 dials implicit TLS, other ports upgrade via STARTTLS when
advertised; AUTH only when a username is configured (PlainAuth itself
refuses plaintext, so the password cannot leak to a TLS-less relay).
Ping() proves reachability and credentials without sending mail. The
message shape (CRLF, Q-encoded bilingual subject) is pinned by test.
- platform: felis-smtp Secret constants and an optional FELIS_SMTP_PASSWORD
env var on the felis-api Deployment, mirroring felis-uploads-s3.
- cmd/felis api: construct the real mailer when [smtp] is configured; keep
the log fallback otherwise and say so at startup. Warn when a username is
set but the credentials env is empty.
- setup TUI: "e" on the summary/status screen opens the email form (host,
port, from, optional auth). Apply order: Ping preflight, [smtp] into both
host and pod config files, felis-smtp Secret piped to kubectl via stdin,
config Secret, felis-api rollout. A failed preflight leaves the install
untouched. SMTP is deliberately not a wizard rail step: first-run stays
mail-less by design, and the passkey minted at onboarding is the pre-SMTP
owner credential.
Also make PGRepo.UserByEmail match case-insensitively (lower(email) =
lower($1)), honoring the interface contract and the users_verified_email_
unique partial index; the fake repo already matched with EqualFold.
Existing installs need the felis-api Deployment manifest re-applied (e.g. a
bootstrap re-run) before the new env var exists; a rollout restart alone
cannot add it.
This commit is contained in:
12 files changed
+730
-10
No files matched your search
@@ -103,6 +103,17 @@ const (
|
||||
UploadsS3AccessKeyEnv = "FELIS_UPLOADS_S3_ACCESS_KEY"
|
||||
UploadsS3SecretKeyEnv = "FELIS_UPLOADS_S3_SECRET_KEY"
|
||||
|
||||
// SMTPSecretName is the out-of-band Secret carrying the [smtp] relay password.
|
||||
// Same red line as the S3 credentials: the setup wizard's "configure email"
|
||||
// step creates it, felis-api reads it via SMTPPasswordEnv (optionally — a
|
||||
// mailer-less install has no such Secret and still starts, falling back to
|
||||
// logging codes), and it is never rendered into the bundle.
|
||||
SMTPSecretName = "felis-smtp"
|
||||
SMTPSecretPasswordKey = "password"
|
||||
// SMTPPasswordEnv is the env var felis-api reads the relay password from;
|
||||
// [smtp] password_ref defaults to this name.
|
||||
SMTPPasswordEnv = "FELIS_SMTP_PASSWORD"
|
||||
|
||||
// worldsMountPath is where the reaper CronJob mounts the worlds-root (read-only).
|
||||
// It is the default of `felis reaper --worlds-root`; the resolver then reads each
|
||||
// world at <worldsMountPath>/<pvc>. Single-sourced with cmd/felis/reaper.go.
|
||||
@@ -234,6 +245,11 @@ func APIDeployment(p Params) *appsv1.Deployment {
|
||||
corev1.EnvVar{Name: UploadsS3SecretKeyEnv, ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: UploadsS3SecretName}, Key: UploadsS3SecretSecretKey, Optional: optional,
|
||||
}}},
|
||||
// The [smtp] relay password, same optional-Secret pattern: absent until the
|
||||
// setup wizard's "configure email" step creates felis-smtp.
|
||||
corev1.EnvVar{Name: SMTPPasswordEnv, ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: SMTPSecretName}, Key: SMTPSecretPasswordKey, Optional: optional,
|
||||
}}},
|
||||
)
|
||||
|
||||
container := corev1.Container{
|
||||
|
||||
Reference in new issue
Block a user