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
-
Verify the Firebase ID token. Reject if Firebase rejects the token. Extract
firebaseUidandemailfrom the decoded claims. -
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. -
Match the caller's email. Compare the verified token's email (lowercased) to the invitation's email. Reject if they differ.
-
Open the transaction and lock the invitation row.
SELECT ... FOR UPDATEon the invitation insidewithTransactionto prevent a race where two accept requests for the same token both succeed. -
Re-validate the locked row. Re-check
accepted_atandrevoked_atafter the lock. Either being set means a concurrent caller won; reject. -
Resolve or create the platform user. Call
Platform.getUserByFirebaseUid. If null, callPlatform.createUser({ firebaseUid, email, displayName })to insert theplatform.usersrow. -
Add the membership. Call
Platform.addMembership({ userId, orgId, role })with the invitation'sorgIdand role. The(user_id, org_id)UNIQUE constraint onplatform.organization_usersenforces no-duplicates. -
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, andacceptInvitationall 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)onplatform.organization_usersviolation, Postgres error code23505), surface as anInvalidTransitionErrorwith 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.