feat: SCA-only saved-card charges — 2FA charge fallback removed (C6), versioned consent fields, token provenance
PSR 2017 reg 100 makes SCA mandatory and non-waivable for customer-initiated stored-credential charges; a merchant-side 2FA check cannot legally substitute for it (authorising a token-less charge via 2FA leaves the MERCHANT liable for ECI 7 / SLI 210 chargebacks and reg 77(6) compensation regardless of consent). - payments/twofa.go: the homegrown 2FA fallback for token-less saved-card charges is REMOVED ENTIRELY. requireTwoFactorForCardAccess is now SCA-only: a non-empty Square verification_token (charge surfaces, token forwarded to Square) skips the gate; anything else is refused 402 verification_required. enforceSCAFallbackConsent is a compile-compatible no-op (fallback never runs). - New requireTwoFactorForCardAccessWithTokenValidation distinguishes surfaces where the token IS forwarded to Square (charge — Square validates it) from card-SAVE surfaces (token client-asserted, never forwarded: a non-empty token must NOT skip the save gate, auth-F1). - SCA tokenize-result wire contract (C1): a saved card charged with a fresh one-time tokenize-result sends the token as the charge SOURCE (new_card_token -> source_id) alongside saved_card_id, never a separate verification_token. resolveChargeSource resolves the saved-card branch FIRST (customer from the card row, token as source) so combined token+card requests are SCA-clean. - C6 consent fields (consent_version / consent_accepted) added to the booking/ tip/till/gift-card charge requests, enforced server-side before any fallback charge could reach Square and recorded on the 2fa_fallback_charge audit row; logVerificationTokenProvenance traces minted tokens to their charge. - user 2FA issuance gate refactored into pure build-agnostic functions (twoFAPepperConfigured / twoFADeliveryChannelConfigured / twoFAEnsureIssueAllowedStrict) shared with the payments re-issue path and exercised directly by the test,dev suite; TWO_FACTOR_FALLBACK switch and .env.example entry removed; startup posture notes updated. - Test coverage: fail-closed 2FA production gates (pepper/delivery), token validation on save vs charge surfaces, completion idempotency, idempotency key determinism, refund-policy 72h/24h epsilon boundaries, VAT parity.
This commit is contained in:
+19
-19
@@ -55,19 +55,25 @@ SQUARE_ENVIRONMENT=mock
|
||||
# deliberately route a dev build to the real production API. Never set in a
|
||||
# deployed production build.
|
||||
SQUARE_ALLOW_REAL_API=
|
||||
# 2FA — backup merchant-level authorization gate on saved-card online payments.
|
||||
# NOT PSD2 SCA: Square buyer verification (3-D Secure / SCA) is the PRIMARY
|
||||
# authorisation for saved-card charges and is wired for both new-card and
|
||||
# saved-card charges. The 2FA gate fires only when SCA is unavailable (e.g. a
|
||||
# customer's bank does not support the in-app approval flow) and every such
|
||||
# fallback charge is audited (admin_audit_log 2fa_fallback_charge). Enforcement
|
||||
# is FAIL-CLOSED:
|
||||
# 2FA — homegrown two-factor authentication for ADMIN/ACCOUNT VERIFICATION ONLY
|
||||
# (setup, disable, delete-account re-auth). It is NEVER a card-charge
|
||||
# authorisation. Saved-card online payments are authorised EXCLUSIVELY by
|
||||
# Square PSD2 SCA (3-D Secure / buyer verification), wired for both new-card
|
||||
# and saved-card charges. PSR 2017 reg 100 makes SCA mandatory and non-waivable
|
||||
# for customer-initiated stored-credential charges, so a saved-card charge
|
||||
# carrying no Square verification token is REFUSED 402 verification_required
|
||||
# (the payment does not go through; the customer can try again later, or at
|
||||
# the till they can be invited to pay online later instead) — the homegrown
|
||||
# 2FA fallback was removed entirely and no TWO_FACTOR_FALLBACK switch exists.
|
||||
# Enforcement is FAIL-CLOSED:
|
||||
# ON unless REQUIRE_2FA explicitly disables it (false/0/off/no, case-insensitive)
|
||||
# OR SQUARE_ENVIRONMENT explicitly equals one of mock/dev/development/test.
|
||||
# Empty or unknown SQUARE_ENVIRONMENT values are treated as production-enforced
|
||||
# (a mistyped env var can never silently disarm the gate; the backend logs a
|
||||
# startup warning in that case). Set REQUIRE_2FA=false only in controlled
|
||||
# environments.
|
||||
# environments. The dev mock simulates SCA
|
||||
# (SimulateSavedCardVerificationRequired + cnon:sca-... tokenize-results), so
|
||||
# development has full parity with the SCA-only production posture.
|
||||
# Code delivery: the intended channel is email/SMS (the method chosen at
|
||||
# setup) — NOT wired yet. Until it lands, the verification code is delivered
|
||||
# via the server log (a [2FA]-prefixed line) when the operator opts into
|
||||
@@ -75,16 +81,6 @@ SQUARE_ALLOW_REAL_API=
|
||||
# environments an operator must relay the logged code to the user out-of-band;
|
||||
# the API never returns the code while enforcement is ON.
|
||||
REQUIRE_2FA=true
|
||||
# TWO_FACTOR_FALLBACK: whether the homegrown 2FA code gate may be used as a
|
||||
# BACKUP authorisation for saved-card charges when Square SCA (3-D Secure /
|
||||
# buyer verification) is unavailable — e.g. a customer's bank does not support
|
||||
# in-app approval. Defaults true. Square 3DS2 SCA is the PRIMARY authorisation;
|
||||
# when a saved-card charge carries a Square verification_token the 2FA gate is
|
||||
# skipped entirely. Set TWO_FACTOR_FALLBACK=false for a security-first posture
|
||||
# in which a saved-card charge without SCA cannot proceed via 2FA (the frontend
|
||||
# surfaces the SCA challenge; if the bank can't complete it, the charge fails).
|
||||
# 2FA fallback success always writes an admin_audit_log 2fa_fallback_charge row.
|
||||
TWO_FACTOR_FALLBACK=true
|
||||
# TWO_FACTOR_PEPPER — server-side pepper for HMAC-hashing 2FA codes. REQUIRED
|
||||
# in production builds: code issuance FAILS CLOSED when it is unset (an
|
||||
# unsalted SHA-256 digest in the 1M code space would be offline-brute-forceable
|
||||
@@ -97,7 +93,7 @@ TWO_FACTOR_PEPPER=
|
||||
# FAILS CLOSED without a delivery channel: there is no email/SMS transport yet,
|
||||
# so the ONLY production channel is the operator's explicit opt-in to the
|
||||
# insecure server-log delivery ([2FA] prefix — anyone with backend log access
|
||||
# can defeat the gate on saved-card charges). MUST be set to true to deliver
|
||||
# can defeat the account 2FA gate). MUST be set to true to deliver
|
||||
# 2FA codes via the server log in production until email/SMS lands. Dev/test
|
||||
# builds always deliver via the log and never consult this flag.
|
||||
TWO_FACTOR_ALLOW_LOG_DELIVERY=false
|
||||
@@ -137,6 +133,10 @@ TEST_DB_DSN=
|
||||
GO_TESTING=
|
||||
|
||||
# CardDAV (SabreDAV) — profile photo sync
|
||||
# DAV_BASE_URL is retained for reference only: the Go backend no longer reads it
|
||||
# — updateCardDAV writes through the dav.Service directly (Postgres-backed
|
||||
# CardDAV store), it never calls this URL. Only the sabredav PHP container needs
|
||||
# the server-side credential below.
|
||||
DAV_BASE_URL=http://localhost:8080
|
||||
# DAV_ADMIN_PASSWORD — REQUIRED, FAIL-CLOSED. sabredav/server.php refuses to
|
||||
# start when unset or set to a known weak/default value ('admin' etc.) — this
|
||||
|
||||
Reference in New Issue
Block a user