//go:build !dev && !test package payments import ( "errors" "os" ) // twoFADeliveryAvailable reports whether a 2FA code delivery channel exists in // this build. Production has no wired email/SMS transport (P6), so the ONLY // channel is the operator's explicit opt-in to insecure log delivery // (TWO_FACTOR_ALLOW_LOG_DELIVERY=true). Without a channel, codes can never // reach the customer, so the 2FA BACKUP authorization (the saved-card gate // when SCA is unavailable) cannot operate and a token-less saved-card charge is // denied 503 (see requireTwoFactorForCardAccess). Mirrors // handlers/user/twofa_prod.go; dev/test builds always deliver (twofa_delivery_dev.go). func twoFADeliveryAvailable() bool { return os.Getenv("TWO_FACTOR_ALLOW_LOG_DELIVERY") == "true" } // errTwoFAPepperRequired is returned by twoFAReissueIssueAllowed when // TWO_FACTOR_PEPPER is unset in a production build — the re-issue would // otherwise persist an offline-brute-forceable unsalted SHA-256 digest in the // 1M code space (mirrors handlers/user's errTwoFAPepperRequired). var errTwoFAPepperRequired = errors.New("TWO_FACTOR_PEPPER is not set; refusing to issue a 2FA code (an unsalted digest would be offline-brute-forceable)") // twoFAReissueIssueAllowed is the re-issue path's issuance gate // (reissueTwoFACodeAfterFailedCharge, handlers.go), mirroring the user // package's twoFAEnsureIssueAllowed (handlers/user/twofa_prod.go) build-tagged // semantics: production requires BOTH a delivery channel and TWO_FACTOR_PEPPER. // Without a channel the code could never reach the customer, and without the // pepper every stored code would be an offline-brute-forceable unsalted digest // — either way the re-issue refuses (fail-closed), exactly like the interactive // mint paths. Dev/test builds always allow issuance (twofa_delivery_dev.go). // // The pepper check is the ONLY hard gate on the re-issue (plus the delivery // channel). PEPPER-CHANGE HAZARD (Loop B finding 2): the pepper keys the // HMAC-SHA256 of every stored pending-code hash, so CHANGING TWO_FACTOR_PEPPER // invalidates ALL pending codes — a re-issued code under the new pepper can // never match a customer's code minted under the old one. An operator who // changes the pepper must re-mint every user's code (or the customer must // re-run 2FA setup), or a fresh saved-card charge whose code was consumed at // the gate will strand the customer with 400 ErrMissingOrExpired on retry. func twoFAReissueIssueAllowed() error { if os.Getenv("TWO_FACTOR_PEPPER") == "" { return errTwoFAPepperRequired } if !twoFADeliveryAvailable() { return errors.New("2FA requires an email or SMS delivery channel; contact the salon") } return nil }