From 208638e3749c725f52655a30ef3e68686ee58894 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sat, 25 Apr 2026 18:42:36 +0100 Subject: [PATCH] feat(launch): security and correctness fixes for 2026-05-01 launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Day-of-launch deep-dive audit and remediation. 11 issues fixed across security, correctness, and reliability. Tests: 698 → 758 passing (+60), 0 failing, 2 skipped. CRITICAL (security) Fix #1 — HELCIM_API_TOKEN removed from runtimeConfig.public; dead useHelcim.js deleted. Production token MUST BE ROTATED post-deploy (was previously exposed in window.__NUXT__ payload). Fix #2 — /api/helcim/customer gated with origin check + per-IP/email rate limit + magic-link email verification (replaces unauthenticated setAuthCookie). Adds payment-bridge token for paid-tier signup so users can complete Helcim checkout before email verify. New utils: server/utils/{magicLink,rateLimit}.js. UX: signup success copy now prompts user to check email. Fix #3 — /api/events/[id]/payment deleted (dead code with unauth member-spoof bypass — processHelcimPayment was a permanent stub). Removes processHelcimPayment export and eventPaymentSchema. Fix #4 — /api/helcim/initialize-payment re-derives ticket amount server-side via calculateTicketPrice and calculateSeriesTicketPrice. Adds new series_ticket metadata type (was being shoved through event_ticket with seriesId in metadata.eventId). Fix #5 — /api/helcim/customer upgrades existing status:guest members in place rather than rejecting with 409. Lowercases email at lookup; preserves _id so prior event registrations stay linked. HIGH (correctness / reliability) Fix #6 — Daily reconciliation cron via Netlify scheduled function (@daily). New: netlify.toml, netlify/functions/reconcile-payments.mjs, server/api/internal/reconcile-payments.post.js. Shared-secret auth via NUXT_RECONCILE_TOKEN env var. Inline 3-retry exponential backoff on Helcim transactions API. Fix #7 — validateBeforeSave: false on event subdoc saves (waitlist endpoints) to dodge legacy location validators. Fix #8 — /api/series/[id]/tickets/purchase always upserts a guest Member when caller is unauthenticated, mirrors event-ticket flow byte-for-byte. SeriesPassPurchase.vue adds guest-account hint and client auth refresh on signedIn:true response. Fix #9 — /api/members/cancel-subscription leaves status active per ratified bylaws (was pending_payment). Adds lastCancelledAt audit field on Member model. Indirectly fixes false-positive detectStuckPendingPayment admin alert for cancelled members. Fix #10 — /api/auth/verify uses validateBody with strict() Zod schema (verifyMagicLinkSchema, max 2000 chars). Fix #11 — 8 vitest cases for cancel-subscription handler (was uncovered). Specs and audit at docs/superpowers/specs/2026-04-25-fix-*.md and docs/superpowers/plans/2026-04-25-launch-readiness-fixes.md. LAUNCH_READINESS.md updated with new test count, 3 deploy-time tasks (rotate Helcim token, set NUXT_RECONCILE_TOKEN, verify Netlify scheduled function), and Fixed-2026-04-25 fix log. --- app/components/SeriesPassPurchase.vue | 11 +- app/components/SignupFlowOverlay.vue | 9 +- app/composables/useHelcim.js | 90 ---- app/composables/useHelcimPay.js | 46 ++- app/pages/join.vue | 7 +- docs/LAUNCH_READINESS.md | 45 +- netlify.toml | 13 + netlify/functions/reconcile-payments.mjs | 40 ++ nuxt.config.ts | 4 +- server/api/auth/verify.post.js | 12 +- server/api/events/[id]/payment.post.js | 128 ------ server/api/events/[id]/waitlist.delete.js | 3 +- server/api/events/[id]/waitlist.post.js | 3 +- server/api/helcim/customer.post.js | 100 ++++- server/api/helcim/initialize-payment.post.js | 98 ++++- server/api/helcim/subscription.post.js | 9 +- .../api/internal/reconcile-payments.post.js | 116 ++++++ .../api/members/cancel-subscription.post.js | 7 +- .../api/series/[id]/tickets/purchase.post.js | 38 ++ server/models/member.js | 1 + server/utils/auth.js | 53 +++ server/utils/helcim.js | 12 - server/utils/magicLink.js | 66 +++ server/utils/rateLimit.js | 18 + server/utils/schemas.js | 18 +- tests/server/api/auth-verify.test.js | 96 ++++- tests/server/api/cancel-subscription.test.js | 201 +++++++++ .../server/api/event-save-validators.test.js | 49 +++ .../api/events/payment-deletion.test.js | 31 ++ tests/server/api/helcim-customer.test.js | 385 ++++++++++++++++++ tests/server/api/helcim-payment.test.js | 171 +++++++- tests/server/api/helcim-subscription.test.js | 5 +- .../api/reconcile-payments-route.test.js | 269 ++++++++++++ .../api/series-tickets-purchase.test.js | 98 +++++ tests/server/api/validation-phase3.test.js | 21 - .../runtime-config-public.test.js.snap | 11 + .../config/runtime-config-public.test.js | 36 ++ 37 files changed, 1980 insertions(+), 340 deletions(-) delete mode 100644 app/composables/useHelcim.js create mode 100644 netlify.toml create mode 100644 netlify/functions/reconcile-payments.mjs delete mode 100644 server/api/events/[id]/payment.post.js create mode 100644 server/api/internal/reconcile-payments.post.js create mode 100644 server/utils/magicLink.js create mode 100644 server/utils/rateLimit.js create mode 100644 tests/server/api/cancel-subscription.test.js create mode 100644 tests/server/api/event-save-validators.test.js create mode 100644 tests/server/api/events/payment-deletion.test.js create mode 100644 tests/server/api/helcim-customer.test.js create mode 100644 tests/server/api/reconcile-payments-route.test.js create mode 100644 tests/server/api/series-tickets-purchase.test.js create mode 100644 tests/server/config/__snapshots__/runtime-config-public.test.js.snap create mode 100644 tests/server/config/runtime-config-public.test.js diff --git a/app/components/SeriesPassPurchase.vue b/app/components/SeriesPassPurchase.vue index 8d3e7f1..ef293af 100644 --- a/app/components/SeriesPassPurchase.vue +++ b/app/components/SeriesPassPurchase.vue @@ -144,6 +144,7 @@

By registering, you'll be automatically registered for all {{ seriesInfo.totalEvents }} events in this series. + We'll create a free guest account so you can access your pass.

@@ -182,7 +183,7 @@ const props = defineProps({ const emit = defineEmits(["purchase-success", "purchase-error"]); const toast = useToast(); -const { initializeTicketPayment, verifyPayment } = useHelcimPay(); +const { initializeSeriesTicketPayment, verifyPayment } = useHelcimPay(); // State const loading = ref(true); @@ -264,10 +265,9 @@ const handleSubmit = async () => { paymentProcessing.value = true; // Initialize Helcim payment for series pass - await initializeTicketPayment( + await initializeSeriesTicketPayment( props.seriesId, form.value.email, - passInfo.value.ticket.price, props.seriesInfo.title, ); @@ -298,6 +298,11 @@ const handleSubmit = async () => { } ); + // Refresh client auth state if server signed us in (guest upgrade) + if (purchaseResponse?.signedIn) { + await useAuth().checkMemberStatus(); + } + // Show success message toast.add({ title: "Series Pass Purchased!", diff --git a/app/components/SignupFlowOverlay.vue b/app/components/SignupFlowOverlay.vue index f29559f..6ad3321 100644 --- a/app/components/SignupFlowOverlay.vue +++ b/app/components/SignupFlowOverlay.vue @@ -33,14 +33,9 @@

- We've sent a confirmation email to {{ summary?.email }}. Redirecting - you to your dashboard... + Check {{ summary?.email }} for a sign-in link to finish setting up + your account. The link expires in 15 minutes.

-
- - Go to Dashboard Now - -