fix(auth): unverified members cannot exercise roles; single-membership join (D2)
require_approval was silently void: redeeming an admin-role invite into a require_approval space yielded role=admin,status=unverified and requireMemberWithRole never checked status -> instant admin pre-approval. - PRIVILEGE_STATUSES/isPrivilegeEligible (current|late only; excludes unverified/inactive). requireMemberWithRole now rejects non-privilege- eligible status with 'pending approval' before the role check; Member carries status (getAuthMember selects it). requireMember stays permissive so unverified members still see their own /me. - joinSpace rejects a second active membership (single-space model; prevents the .single() self-DoS). Deferred (HANDOFF): SECURITY DEFINER helpers user_has_role_in_space/ user_has_permission still ignore status (RLS defense-in-depth, own careful pass). 524 tests; build clean.
This commit is contained in:
@@ -4,6 +4,7 @@ import { createClient } from '@/lib/supabase/server'
|
||||
import { createAdminClient } from '@/lib/supabase/admin'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { checkRateLimit, sanitizeString, sanitizeSlug } from '@/lib/security'
|
||||
import { ACTIVE_STATUSES } from '@/lib/permissions'
|
||||
|
||||
function generateInviteCode() {
|
||||
// Cryptographically random, and wider (8 chars) so the code space is not
|
||||
@@ -157,6 +158,19 @@ export async function joinSpace(formData: {
|
||||
? invite.role
|
||||
: 'member'
|
||||
|
||||
// Single active membership per user (the product's locked single-space
|
||||
// model; getAuthMember's .single() fails closed on 2+). Reject a second
|
||||
// join rather than letting it self-DoS the user out of every action.
|
||||
const { data: existingMembership } = await admin
|
||||
.from('space_members')
|
||||
.select('id')
|
||||
.eq('user_id', user.id)
|
||||
.in('status', ACTIVE_STATUSES as unknown as string[])
|
||||
.maybeSingle()
|
||||
if (existingMembership) {
|
||||
return { error: 'You are already a member of a space.' }
|
||||
}
|
||||
|
||||
const { data: newMember, error: memberErr } = await admin
|
||||
.from('space_members')
|
||||
.insert({
|
||||
|
||||
Reference in New Issue
Block a user