Loop B red-team (money/security/dup-mod adversarial) findings on the full payments overhaul: - CRITICAL-ish: IDEMPOTENCY_KEY_REUSED (409) no longer classified as a definitive 402 in chargeFailureStatus — it means the ORIGINAL charge may have landed with a different body, so it is now AMBIGUOUS (503): the frontend keeps the same idempotency key, the pending row stays rescuable by the sweep (which already treated it as ambiguous), and the frontend no longer regenerates the key into a possible double charge. SCA verification-required codes remain definitive 402. - HIGH: reissueTwoFACodeAfterFailedCharge now writes a CRITICAL admin notification (insertCriticalPaymentNotification) when issuance is refused (missing pepper / unavailable delivery) instead of silently stranding the customer; documented that a pepper CHANGE invalidates all pending codes. - MEDIUM: family-alive cache invalidation crash window documented (invalidate-after- commit leaves up to 30s warm on a crash; the near-TTL DB re-check bounds it). - Consolidation regression checks (8b2fe3b helpers): writeChargeSnapshot guard preserved at all sites, postChargeRecheck identical, squareRefundStatusToLocal mappings verified, reissue fresh-only semantics confirmed at all 5 call sites. Verified: 26/26 dev packages, both vet tags, frontend tests + build, env-docs 42/42.
54 lines
2.7 KiB
Go
54 lines
2.7 KiB
Go
//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
|
|
}
|