Skip to content

Invites: one target per invite, applied only to verified emails - #1715

Merged
HugoGresse merged 1 commit into
mainfrom
claude/invite-authz
Nov 1, 2026
Merged

HugoGresse merged 1 commit into
mainfrom
claude/invite-authz

Conversation

@HugoGresse

Copy link
Copy Markdown
Owner

Why

Invite documents are written by clients, and the invite functions trusted their content and the invitee's identity more than they should. This tightens both sides so the rules and the functions agree on what an invite is and who it is for.

What

Firestore rules (config/firestore.rules, invite create/get):

  • An invite targets a project or an organization (string id), never both. Organization invites carry a role among Viewer, Editor, Admin.
  • Fields are validated: keys().hasOnly, one lowercase plain email address as destinationUserInfo, originUserId equal to the creator, status == 'new', timestamps, names up to 1000 characters.
  • Only verified (or phone) non-anonymous users can create invites; only a verified invitee can read one.

Functions (functions/src/triggers/):

  • getInviteTarget is the functions' single definition of an invite's target. Malformed invites are marked status: 'error' and never applied, both when created and when processed later.
  • applyInvites no longer has an Owner case (Owner was never offered in the UI; ownership moves through the organization settings).
  • The invitee is looked up with admin.auth().getUserByEmail instead of the users collection, and invites are only applied to accounts whose email is verified (phone accounts unchanged).
  • New userEmailVerified trigger: when a user doc's emailVerified turns true (the admin app writes it at the first sign-in after verification), it re-reads the Firebase Auth record and applies the pending invites.

Deploy

Needs both firestore:rules and functions (new function userEmailVerified).

Tests

  • Rules: config/firestore.rules.spec.js (49 tests, Firestore emulator). The new cases fail against main's rules; the "never both" clauses are each covered (checked by removing them).
  • Functions: getInviteTarget.spec.ts, userEmailVerified.spec.ts, updated userInvite, userCreated and applyInvites specs.

npm run lint, npm run typecheck, npm run build, npx vitest run (291 tests) pass in functions/ on Node 24. Reviewed with /code-review max before opening. No UI change, so no screenshots.

🤖 Generated with Claude Code

Invite docs are written by clients and the functions trusted them.

Firestore rules (invite create):
- an invite targets a project or an organization, never both, with a
  string id; organization roles are Viewer, Editor or Admin
- validate the fields: one lowercase plain email address, the creator as
  originUserId, status 'new', timestamps, bounded names
- only verified (or phone) non-anonymous users can create invites, and
  only a verified invitee can read one

Functions:
- getInviteTarget is the trigger's single definition of an invite's
  target; malformed invites are marked as errored, never applied
- applyInvites never grants Owner
- the invitee is looked up in Firebase Auth instead of the users
  collection, and invites are applied only once the email is verified;
  the new userEmailVerified trigger applies them when it becomes so

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Preview for 878d51a (sandbox project open-feedback-hugo, expires in 7 days): https://open-feedback-hugo--pr-1715-ve5opkq3.web.app

@cypress

cypress Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

open-feedback    Run #3158

Run Properties:  status check passed Passed #3158  •  git commit 3587c3caae: Invites: one target per invite, applied only to verified emails (#1715)
Project open-feedback
Branch Review main
Run status status check passed Passed #3158
Run duration 02m 21s
Commit git commit 3587c3caae: Invites: one target per invite, applied only to verified emails (#1715)
Committer Hugo Gresse
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 16
View all changes introduced in this branch ↗︎

@HugoGresse
HugoGresse merged commit 3587c3c into main Nov 1, 2026
14 checks passed
@HugoGresse
HugoGresse deleted the claude/invite-authz branch November 1, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant