A buried or broken invitation flow directly limits expansion revenue in any multi-user SaaS. If an invited user clicks an email link and sees a 'User not found' error instead of a signup prompt, that seat is lost — and the inviter loses confidence in the product. CWE-284 (improper access control) applies when the invite acceptance route fails to redirect non-users to registration, effectively creating a dead end that prevents legitimate account creation through a valid access path.
Medium because a broken invite acceptance flow causes direct seat loss and inviter trust damage, but doesn't create unauthorized access — the failure is exclusion, not intrusion.
In the invite acceptance handler at src/app/invite/[token]/page.tsx, redirect non-users to signup with the invite context preserved:
const invite = await getInviteByToken(token);
if (!invite || invite.expiresAt < new Date()) notFound();
const user = await getUserByEmail(invite.email);
if (!user) {
redirect(`/signup?email=${encodeURIComponent(invite.email)}&invite=${token}`);
}
await acceptInvite(invite.id, user.id);
redirect('/dashboard');
Also add an 'Invite teammates' entry to src/components/Sidebar.tsx reachable within 2 clicks of the dashboard — users shouldn't have to dig through settings to find it.
ID: saas-onboarding.onboarding-ux.invitation-flow-clear
Severity: medium
What to look for: Count the clicks required to reach the invitation UI from the main dashboard. Enumerate the invitation flow: invite by email, invite link generation, role assignment. Check whether the invitation acceptance flow handles non-users.
Pass criteria: An invitation UI exists and is accessible within no more than 2 clicks from the main dashboard. Invitation emails include the inviter's name and the application name. Invitees without accounts are directed to create one before accepting.
Fail criteria: Invitation UI requires more than 2 clicks to reach from the main dashboard; invitation links expire without warning; invitees who click an invitation link and don't have an account see an error rather than a signup prompt.
Skip (N/A) when: The application is explicitly single-user only (no team, workspace, or multi-user concepts exist in the codebase — no invitation routes, no team/workspace models, no role assignment).
Detail on fail: "Invitation feature exists but is nested 3+ clicks deep in settings. Invitation link acceptance for non-users shows a 'User not found' error instead of redirecting to signup."
Remediation: In the invitation acceptance handler at src/app/invite/[token]/page.tsx:
const invite = await getInviteByToken(token);
const user = await getUserByEmail(invite.email);
if (!user) {
redirect(`/signup?email=${invite.email}&invite=${token}`);
}
Add an "Invite teammates" option to src/components/Sidebar.tsx within 2 clicks.