audit: remove duplicate Stripe integration card + tighten input validation

Deep audit of the recent change set (passes 56-60):

- Remove the generic Integrations-tab Stripe card. saveIntegration
  overwrites integrations.config wholesale, so configuring Stripe there
  clobbered the dues config (mode/prices/grace_days) that the Dues tab
  manages, and couldn't actually set up dues. Stripe is now configured in
  one place (the Dues tab). PayPal/Zeffy/Venmo sync cards unchanged.
- deleteDuesPaymentMethod validates platform against DUES_LINK_PLATFORMS
  instead of passing a raw client string into the enum column.
- markNotificationsRead uses a strict uuid regex for the id filter.

PGRST201 class characterized + confirmed contained: PostgREST treats a
2-FK table as an ambiguous junction only when its PK covers both FKs; the
only such table now is space_member_custom_roles (a legitimate M2M).
Guarded by integration/auth-embed.test.ts.
This commit is contained in:
Moheeb Zara
2026-05-20 00:33:08 -07:00
parent 49d8805b4c
commit 36afa7ea2c
4 changed files with 33 additions and 18 deletions
+5 -1
View File
@@ -5,7 +5,7 @@ import { createClient } from '@/lib/supabase/server'
import { requireMember, requireMemberWithRole, parseInput } from '@/lib/auth-helpers'
import { ADMIN_ROLES } from '@/lib/permissions'
import { duesPaymentMethodSchema } from '@/lib/validations'
import { isSafeDuesUrl } from '@/lib/dues-payments-logic'
import { isSafeDuesUrl, isDuesLinkPlatform } from '@/lib/dues-payments-logic'
export type DuesPaymentMethod = {
platform: string
@@ -100,6 +100,10 @@ export async function deleteDuesPaymentMethod(input: {
if (!auth.ok) return { error: auth.error }
const { member } = auth
// Validate at the boundary (same as the upsert path) instead of passing a
// raw client string into the enum column.
if (!isDuesLinkPlatform(input.platform)) return { error: 'Invalid platform' }
const { error } = await supabase
.from('dues_payment_methods')
.delete()
+3 -1
View File
@@ -77,7 +77,9 @@ export async function markNotificationsRead(
if (input.ids && input.ids.length > 0) {
// Defend the uuid column against a malformed client id (a non-uuid would
// error the cast). Only well-formed ids pass; an empty result is a no-op.
const ids = input.ids.filter(id => /^[0-9a-f-]{36}$/i.test(id))
const ids = input.ids.filter(id =>
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id),
)
if (ids.length === 0) return { ok: true }
q = q.in('id', ids)
}