//go:build !dev && !test package user // Production builds (neither the `dev` nor the `test` tag) must never persist // an unsalted digest and must never write a 2FA code in plaintext by default: // the plaintext [2FA] log delivery and the TWO_FACTOR_PEPPER fallback exist // only in dev/test builds (twofa_dev.go). Here code issuance fails closed on // BOTH missing configuration pieces: // // - a missing TWO_FACTOR_PEPPER (an unsalted digest in the 1M code space // would be offline-brute-forceable from a log/DB leak), mirroring how // main.go refuses to start without a strong JWT_SECRET_KEY; and // - a missing delivery channel. The email/SMS transport is not wired yet // (P6), so the ONLY production channel is the operator's explicit opt-in // to the insecure log-delivery mode (TWO_FACTOR_ALLOW_LOG_DELIVERY=true). // Without it, issuing a code would silently dead-end setup — the user // could never receive the code and the enforced saved-card-payments gate // would lock them out with no way forward. Issuance is refused and the // handlers surface errTwoFADeliveryUnavailable ("2FA requires an email or // SMS delivery channel; contact the salon"). // // The plaintext code is therefore never written to the server log unless the // operator explicitly opted into log delivery and accepted its risk. import ( "crussell/internal/twofa" "log" "os" ) // twoFAAllowLogDeliveryEnv (the TWO_FACTOR_ALLOW_LOG_DELIVERY opt-in) and // errTwoFAPepperRequired are defined in twofa.go — shared by the pure issuance // gate (twoFAEnsureIssueAllowedStrict), which the test,dev suite exercises // directly, and this production build. // init registers the production pepper reader into the shared verification // core (crussell/internal/twofa): raw env read, no fallback — code issuance // fails closed via twoFAEnsureIssueAllowed, so no pending code is ever // persisted as an unsalted SHA-256 digest. func init() { twofa.SetPepperProvider(func() string { return os.Getenv(twoFAPepperEnv) }) } // twoFADeliveryAvailable reports whether a 2FA code delivery channel exists in // this build. Production: true only when the operator explicitly opted into the // insecure log-delivery mode (TWO_FACTOR_ALLOW_LOG_DELIVERY=true) or a real // email/SMS transport is wired (not yet — P6). Default false: no channel, so // code issuance is refused and setup surfaces errTwoFADeliveryUnavailable // instead of a silent dead-end. Delegates to the pure build-agnostic // twoFADeliveryChannelConfigured (twofa.go); dev/test builds always return true // (twofa_dev.go). func twoFADeliveryAvailable() bool { return twoFADeliveryChannelConfigured() } // twoFAEnsureIssueAllowed reports whether a 2FA code may be issued in this // deployment. Production requires BOTH a delivery channel and TWO_FACTOR_PEPPER: // without a channel (no email/SMS, no TWO_FACTOR_ALLOW_LOG_DELIVERY=true) the // code could never reach the user — issuing one would silently lock the user // out of the enforced saved-card-payments gate; and without the pepper every // stored code would be an offline-brute-forceable unsalted digest. Either way // issuance is refused (fail-closed). Delegates to the pure build-agnostic gate // twoFAEnsureIssueAllowedStrict (twofa.go), which the test,dev suite also // exercises directly; dev/test builds always allow issuance (twofa_dev.go). // // The pepper check is the ONLY hard gate here (plus the delivery channel), and // it is also the ONLY hard gate on the payments re-issue path // (payments.twoFAReissueIssueAllowed). 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 — every stored hash // was computed with the old pepper and can never match a code minted under the // new one. An operator who changes the pepper must re-mint every user's code // (or have each user re-run 2FA setup), or enforced saved-card charges will // strand customers with 400 ErrMissingOrExpired forever. func twoFAEnsureIssueAllowed() error { return twoFAEnsureIssueAllowedStrict() } // twoFADeliverCode delivers a fresh verification code to the user. Production // has no wired email/SMS transport (P6), so the ONLY channel is the operator's // explicit, insecure opt-in to log delivery (TWO_FACTOR_ALLOW_LOG_DELIVERY=true // — anyone with backend log access could defeat the 2FA gate on saved-card // charges). WITHOUT that flag the plaintext code is NEVER written to the log; // twoFAEnsureIssueAllowed already refused issuance, so this no-op is // unreachable. With the flag set, the code is written to the [2FA] log line // and an operator relays it to the user out-of-band, exactly like the // documented dev flow — the operator has accepted the risk of log-based // delivery. MEDIUM-3b: the user id and the plaintext code go to SEPARATE log // lines so a single record cannot trivially pair a code with its owner. func twoFADeliverCode(userID, label, code string) { if os.Getenv(twoFAAllowLogDeliveryEnv) == "true" { log.Printf("[2FA] code delivery requested (user=%s, purpose=%s)", userID, label) log.Printf("[2FA] code: %s", code) } // Otherwise: deliberate no-op — never log the plaintext code by default. }