toeverything/AFFiNE · error · InvalidInvitation

invalid_invitation

invalid_invitation

Error message

Invalid invitation provided.

What it means

Thrown by `acceptInviteById` on the email-invitation branch when an invitation row exists for `inviteId`, the caller is signed in, but `user.id !== role.userId` — i.e. a different authenticated account is trying to accept an invitation emailed to someone else. Classified as `invalid_invitation` (invalid_input).

Source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:704

  @Mutation(() => Boolean)
  @Public()
  async acceptInviteById(
    @CurrentUser() user: CurrentUser | undefined,
    @Args('inviteId') inviteId: string,
    @Args('workspaceId', { deprecationReason: 'never used', nullable: true })
    _workspaceId: string,
    @Args('sendAcceptMail', {
      nullable: true,
      deprecationReason: 'never used',
    })
    _sendAcceptMail: boolean
  ) {
    const role = await this.models.workspaceUser.getById(inviteId);
    // invitation by email
    if (role) {
      if (user && user.id !== role.userId) {
        throw new InvalidInvitation();
      }

      await this.acceptInvitationByEmail(role);
    } else {
      // invitation by link
      if (!user) {
        throw new AuthenticationRequired();
      }

      const invitation = await this.cache.get<{
        workspaceId: string;
        inviterUserId: string;
      }>(`workspace:inviteLinkId:${inviteId}`);

      if (!invitation) {
        throw new InvalidInvitation();
      }

View on GitHub (pinned to 26c515e050)

Solutions

  1. Sign out and accept the invite in an incognito window, then sign in with the exact email the invite was sent to.
  2. Have an admin re-send the invitation to the account the user is actually signed in with.
  3. In the client, compare the invited email to the current session email before calling `acceptInviteById`.
  4. If the invite is meant to be transferable, use a link invitation instead of an email invitation.

Example fix

// before
await sdk.acceptInviteById({ inviteId });

// after
const info = await sdk.getInviteInfo({ inviteId });
if (currentUser && info.inviteeUserId && currentUser.id !== info.inviteeUserId) {
  promptSignOutAndRedirect(inviteId);
  return;
}
await sdk.acceptInviteById({ inviteId });
Defensive patterns

Strategy: validation

Validate before calling

// Compare current session to the invited user before accepting
const info = await sdk.getInviteInfo({ inviteId });
if (currentUser && info.inviteeUserId && currentUser.id !== info.inviteeUserId) {
  promptSignOut(inviteId);
  return;
}
await sdk.acceptInviteById({ inviteId });

Type guard

function inviteMatchesSession(currentUser, inviteInfo) {
  return !currentUser || !inviteInfo.inviteeUserId || currentUser.id === inviteInfo.inviteeUserId;
}

Try / catch

try {
  await sdk.acceptInviteById({ inviteId });
} catch (e) {
  if (e.code === 'invalid_invitation') {
    notify('Sign in with the email that received this invite, or request a new one.');
  } else throw e;
}

Prevention

When it happens

Trigger: Clicking an invite link while signed in as account B when the invite was emailed to account A; sharing an email-invite id with a colleague who is logged into their own account; a session that was switched mid-flow.

Common situations: User received the invite at work email but is signed in with a personal account; browser auto-filled a different account; testing invites by forwarding the link to another user who is already authenticated.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/24879a9b525b2e62. Report an issue: GitHub.