Skip to content

fix(web): org invite accept page has no email match check — any authenticated user can join any org #183

Description

@walidboulanouar

Problem

apps/web/src/app/(auth)/accept-invite/page.tsxacceptInviteAction takes orgSlug from the query string and adds the currently-authenticated Supabase user to that org, with no verification that:

  1. The authenticated user's email matches the invited email
  2. The invite has not already been accepted (no consumed/single-use guard)
  3. The orgSlug in the URL corresponds to the org the invite was issued for

Result: any user with an active Supabase session can navigate to /accept-invite?org=target-org-slug and join an arbitrary org as a viewer. Additionally, if Supabase magic-link tokens are not single-use (they are not by default), two people can click the same invite link and both gain membership.

Fix

// In acceptInviteAction:
const { data: invite } = await supabase
  .from('org_invites')
  .select('email, org_id, accepted_at')
  .eq('token', inviteToken)
  .single();

if (!invite || invite.accepted_at) return { error: 'Invalid or expired invite' };
if (invite.email !== session.user.email) return { error: 'Email mismatch' };

// Mark consumed
await supabase.from('org_invites').update({ accepted_at: new Date() }).eq('token', inviteToken);

Requires an org_invites table with token + consumed tracking.

Severity

HIGH — unauthorized org membership; any user with the invite URL slug can join.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions