Repository navigation
Harden payment endpoint authorization and state transitions #27
Copy link
Copy link
Open
Labels
agent-candidateMaintainer-approved issue for a DigitalOcean agent PRMaintainer-approved issue for a DigitalOcean agent PR
Description
Summary
Replace #4 with a trackable fix for payment authorization and payment state changes. The reported /forms/payments/<submission_id>/cancel/ route is still accessible without proving ownership. The same payment flow also needs protection at initiation, confirmation, provider return, and webhook handling.
Current gaps
initiate_paymentlooks up a pending submission by numeric ID and creates a payment attempt without checking the caller's right to that submission. It does this on GET and can expose the payment client configuration.confirm_paymentaccepts a payment record ID and calls the provider without checking the caller's right to its submission.payment_returnfinds a pending attempt by submission ID and processes a provider return without authenticating the returning checkout or binding it to the expected attempt.payment_cancelaccepts GET or POST, requires no ownership check, and sets any submission todraft, including one that has already progressed beyondpending_payment.- Stripe webhook handling accepts unverified event payloads when
STRIPE_WEBHOOK_SECRETis unset. The webhook must remain callable by the provider, but events that change payment state need authenticated provenance.
These paths are in payments/views.py and stripe_provider.py. Public forms intentionally allow anonymous submissions, so adding login_required everywhere would break supported checkout flows.
Acceptance criteria
- Define and enforce one payment access policy: an authenticated submitter can act only on their own submission; an anonymous public-form submitter can continue checkout only with a session-bound or otherwise unguessable, short-lived checkout credential. A numeric submission/payment ID alone grants no access. The policy applies before rendering payment details, exposing client secrets, calling providers, or changing records. Unauthorized callers get a non-disclosing response.
- Make initiation safe against arbitrary GETs and repeat requests. Repeated navigation or retries must not create uncontrolled payment attempts or duplicate provider charges; use stable idempotency for an attempt and document how a legitimate retry starts a new one.
- Restrict confirmation, return, and cancellation to the correct payment attempt and valid lifecycle states. Bind redirect returns to the originating checkout and verify the provider's result. Cancellation must use an authorized, CSRF-protected user action (or a verified provider callback), and must never revert a completed, submitted, or approved submission to
draft. - Require verification of Stripe webhook signatures before applying an event; fail closed if the verification secret is absent or the signature is invalid. Apply the same authenticated-event requirement to other providers through the provider interface. Handle replayed or out-of-order events without rolling state backward or creating duplicate workflow tasks.
- Add regression tests for anonymous callers, a different signed-in user, the rightful signed-in user, a rightful anonymous public-form checkout, guessed IDs, GET versus POST, repeated requests, invalid/missing webhook signatures, and cancellation after completion. Update payment setup docs for the required webhook secret and checkout behavior.
Original report: #4.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
agent-candidateMaintainer-approved issue for a DigitalOcean agent PRMaintainer-approved issue for a DigitalOcean agent PR
Activity
Harden payment endpoint authorization and state transitions (#27)