Skip to main content
Version: v1.0.0(int)

W010: Accept invitation

Adds the accepting user to the inviting org with the role from the invitation. Triggered from the public Accept Invite page; the route is unauthenticated but the caller must submit a verified Firebase ID token in the body so the workflow can match the invitation's email.

Steps

  1. Verify the Firebase ID token. Reject if Firebase rejects the token. Extract firebaseUid and email from the decoded claims.

  2. Pre-validate the invitation outside the transaction. Call Platform.getInvitationByToken. Reject if the token is unknown, accepted, revoked, or expired. This avoids opening a transaction for obviously-bad requests.

  3. Match the caller's email. Compare the verified token's email (lowercased) to the invitation's email. Reject if they differ.

  4. Open the transaction and lock the invitation row. SELECT ... FOR UPDATE on the invitation inside withTransaction to prevent a race where two accept requests for the same token both succeed.

  5. Re-validate the locked row. Re-check accepted_at and revoked_at after the lock. Either being set means a concurrent caller won; reject.

  6. Resolve or create the platform user. Call Platform.getUserByFirebaseUid. If null, call Platform.createUser({ firebaseUid, email, displayName }) to insert the platform.users row.

  7. Add the membership. Call Platform.addMembership({ userId, orgId, role }) with the invitation's orgId and role. The (user_id, org_id) UNIQUE constraint on platform.organization_users enforces no-duplicates.

  8. Mark the invitation accepted. Call Platform.acceptInvitation(invitationId) inside the same transaction.

Returns

The new membership row (platform.organization_users) joined with the user.

Business rules

  • Email match is required. A token alone is not enough; the verified Firebase email must match the invitation's email (case-insensitive).
  • Atomic with race protection. The invitation row is locked FOR UPDATE; the user upsert, membership insert, and acceptInvitation all commit together. Two concurrent accepts cannot both succeed.
  • Duplicate-membership guard. If the user already has a membership row for this org (UNIQUE (user_id, org_id) on platform.organization_users violation, Postgres error code 23505), surface as an InvalidTransitionError with the "already a member" message.
  • Unauthenticated endpoint, rate-limited. Public route guarded by a per-IP limiter (20 requests per 15 minutes).

Errors

  • ValidationError. The Firebase token is invalid, or the caller's email does not match the invitation.
  • NotFoundError. The token is unknown.
  • InvalidTransitionError. The invitation is expired, accepted, or revoked, or the user is already a member of this org.