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:
@@ -24,6 +24,20 @@ export const ALL_ROLES = ROLES
|
||||
/** Statuses a member may have and still be allowed to act in the app. */
|
||||
export const ACTIVE_STATUSES = ['current', 'unverified', 'late'] as const satisfies readonly MemberStatus[]
|
||||
|
||||
/**
|
||||
* Statuses that may exercise a ROLE or PERMISSION (privileged actions).
|
||||
* Deliberately excludes 'unverified': a member of a `require_approval` space
|
||||
* is 'unverified' until an admin approves them, and must hold NO privileged
|
||||
* capability in that window even if they redeemed a role-bearing invite
|
||||
* (otherwise `require_approval` is silently void). 'late' keeps its role
|
||||
* (dues lapse is grace -> late, never an authz downgrade).
|
||||
*/
|
||||
export const PRIVILEGE_STATUSES = ['current', 'late'] as const satisfies readonly MemberStatus[]
|
||||
|
||||
export function isPrivilegeEligible(status: MemberStatus | null | undefined): boolean {
|
||||
return status === 'current' || status === 'late'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if `role` is one of the `allowed` roles.
|
||||
* Use this everywhere instead of `role === 'admin' || role === 'board'` chains.
|
||||
|
||||
Reference in New Issue
Block a user