Skip to content

[Medium][Bug] Make invitation acceptance transactional and stop reopening claimed invites after side effects #308

Description

@richardcmckinney

Summary

Team invitation acceptance claims an invitation by setting it to accepted, then may roll it back to pending after validation failures or after side effects have already occurred. The principal update/create and user update are not wrapped in a transaction with the invitation state change.

Evidence

  • apps/web/src/lib/server/functions/invitations.ts:153-163 claims the invitation by setting status: 'accepted'.
  • apps/web/src/lib/server/functions/invitations.ts:185-190 rolls the invitation back to pending.
  • apps/web/src/lib/server/functions/invitations.ts:193-202 rolls back expired or email-mismatched invitations to pending.
  • apps/web/src/lib/server/functions/invitations.ts:208-242 updates or creates principals and updates user names after the claim.
  • apps/web/src/lib/server/functions/invitations.ts:260-275 rolls the invitation back to pending for any caught error after didClaim is true.

Impact

A failure after membership mutation can reopen the invitation while side effects remain. Expired or mismatched invitations can also be returned to pending. This can create inconsistent invitation state, duplicate acceptance opportunities, and confusing team membership behavior.

Recommended fix

Wrap invitation claim, validation, principal mutation, user update, and final status in a single database transaction with row-level locking or a conditional update strategy. Validate expiry and email before mutating principal/user state. Do not reopen an invitation after side effects have been committed.

Acceptance criteria

  • Invitation acceptance is atomic.
  • Expired invitations are marked or treated as expired, not reset to pending.
  • Email-mismatched acceptance attempts do not mutate invitation or principal state.
  • Errors after principal mutation do not reopen an invitation.
  • Concurrency tests prove double-clicks and retries cannot accept the same invitation twice.
  • Fault-injection tests cover failures during principal update, user update, and token revocation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions