harden: defense-in-depth on vote/signup/area-lead boundaries
Audit follow-up (RLS already enforced these; adding boundary checks so a future RLS regression cannot silently allow cross-space writes): - castVote: verify the proposal is in the caller's space and still open before the upsert. - cancelMySignup: scope every admin-client query/update in the waitlist promotion path by member.space_id (not session_id alone). - setAreaLead: reject a lead_id that is not a member of the space. - signUpForClass: comment the space-safety invariant on the count. No behavior change for valid callers.
This commit is contained in:
@@ -28,6 +28,19 @@ export async function upsertAreaLead(formData: {
|
||||
if (!auth.ok) return { error: auth.error }
|
||||
const { member } = auth
|
||||
|
||||
// If a lead member is referenced, confirm they belong to this space (the
|
||||
// row is space-scoped and RLS scopes reads, but reject a foreign id at the
|
||||
// boundary for consistency with the rest of the codebase).
|
||||
if (v.data.lead_id) {
|
||||
const { data: lead } = await supabase
|
||||
.from('space_members')
|
||||
.select('id')
|
||||
.eq('id', v.data.lead_id)
|
||||
.eq('space_id', member.space_id)
|
||||
.maybeSingle()
|
||||
if (!lead) return { error: 'The selected lead is not a member of this space.' }
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('area_leads')
|
||||
.upsert(
|
||||
|
||||
Reference in New Issue
Block a user