fix(api): 未配置 SMTP 时发码门统一 503 mail_unavailable 且不再把验证码写日志,非本机中继默认强制 STARTTLS(require_tls)
This commit is contained in:
27 files changed
+529
-87
No files matched your search
@@ -68,10 +68,9 @@ const (
|
||||
otpLiveLoginCodes = 3
|
||||
)
|
||||
|
||||
// OTPMailer delivers a one-time code to an email address. It is a seam, not a
|
||||
// dependency: the demo ships without SMTP, so a nil Mailer logs the code
|
||||
// server-side instead of mailing it (a KNOWN-LIMITATION, never a code returned to
|
||||
// the client). Production wires a real sender.
|
||||
// OTPMailer delivers a one-time code to an email address. felis api wires the
|
||||
// [smtp] relay (internal/mail); with none configured it stays nil and every door
|
||||
// that mails a code answers 503 mail_unavailable before minting one.
|
||||
type OTPMailer interface {
|
||||
SendOTP(ctx context.Context, email, code string) error
|
||||
}
|
||||
@@ -140,6 +139,12 @@ func (a *API) handleEmailOTPStart(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, r, newError(http.StatusBadRequest, "bad_request", "a valid email is required"))
|
||||
return
|
||||
}
|
||||
// No relay (or a spent budget) is said before asking for a re-verification
|
||||
// the player could not then use.
|
||||
if err := a.checkMailBudget(); err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
// Gate the start: the verify only redeems a code minted here.
|
||||
if !a.requireReauth(w, r, p) {
|
||||
return
|
||||
@@ -274,16 +279,17 @@ func (a *API) handleEmailOTPVerify(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"verified": true, "email": email})
|
||||
}
|
||||
|
||||
// deliverOTP hands the code to the configured Mailer, or — when none is wired (the
|
||||
// demo) — logs it server-side as a KNOWN-LIMITATION. The code is logged ONLY in the
|
||||
// no-mailer fallback and ONLY to the server log; it is never put in an HTTP response.
|
||||
// deliverOTP hands the code to the configured Mailer. The code goes nowhere
|
||||
// else: never into a response and never into a log, since anyone who can read
|
||||
// the API's logs could otherwise sign in as any player. The doors refuse a
|
||||
// relay-less install before minting (checkMailBudget); the nil check here only
|
||||
// keeps a future caller that skips that check from minting a code no one gets.
|
||||
//
|
||||
// Every real send spends one token of the install-wide mail budget (mailGate);
|
||||
// a spent budget is a 429 mail_rate_limited and nothing reaches the relay.
|
||||
func (a *API) deliverOTP(ctx context.Context, email, code string) error {
|
||||
if a.Mailer == nil {
|
||||
log.Printf("email-otp: no Mailer configured; code for %s is %s (KNOWN-LIMITATION: demo has no SMTP)", email, code)
|
||||
return nil
|
||||
return errMailUnavailable()
|
||||
}
|
||||
if ok, wait := a.mailGate().take(mailGateKey); !ok {
|
||||
metrics.MailTotal.WithLabelValues("otp", "throttled").Inc()
|
||||
|
||||
Reference in new issue
Block a user