Skip to content

H-6771: Prevent additional emails at signup, only use verified emails when fetching org invites - #9245

Open
CiaranMn wants to merge 6 commits into
mainfrom
cm/email-handling-improvements
Open

H-6771: Prevent additional emails at signup, only use verified emails when fetching org invites#9245
CiaranMn wants to merge 6 commits into
mainfrom
cm/email-handling-improvements

Conversation

@CiaranMn

Copy link
Copy Markdown
Member

🌟 What is the purpose of this PR?

  1. We currently only support/properly handle 1 email per user, but it was possible for users to specify more than one at signup. The PR updates the Kratos identity schema to enforce exactly 1.
  2. Acceptance of org invitations requires that the email is verified, but the 'get pending invitations' was also checking against unverified emails. The PR returns only pending invitations for verified emails (as the others can't be accepted anyway).

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

🛡 What tests cover this?

  • A couple added in this PR.

@CiaranMn CiaranMn self-assigned this Aug 19, 2026
Copilot AI balanced review requested due to automatic review settings August 19, 2026 10:21
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

4 Skipped Deployments
Project Deployment Actions Updated (UTC)
hash Ignored Ignored Preview Aug 19, 2026 8:29pm
hashdotdesign-tokens Ignored Ignored Preview Aug 19, 2026 8:29pm
petrinaut Skipped Skipped Aug 19, 2026 8:29pm
petrinaut-docs Skipped Skipped Aug 19, 2026 8:29pm

@cursor

cursor Bot commented Aug 19, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches registration webhooks and invitation discovery in auth-adjacent flows; behavior changes who sees which invites but does not broaden acceptance beyond verified email rules.

Overview
Signup is tightened so each account has exactly one email: the Kratos identity schema now caps traits.emails at one item, and the after-registration webhook rejects any other count and deletes the already-persisted Kratos identity so extra credential identifiers are not left behind.

Pending org invitations are listed only when they match verified Kratos emails (plus shortname when set), via getUserVerifiedEmails, instead of all emails on the user—so unverified addresses no longer surface invites the user cannot accept anyway.

Reviewed by Cursor Bugbot for commit 2946358. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions github-actions Bot added area/apps > hash* Affects HASH (a `hash-*` app) area/infra Relates to version control, CI, CD or IaC (area) area/apps > hash-api Affects the HASH API (app) type/eng > backend Owned by the @backend team area/tests New or updated tests area/tests > integration New or updated integration tests area/apps labels Aug 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enforces single-email signup and restricts pending email invitations to verified addresses.

Changes:

  • Limits Kratos identities to one email.
  • Rejects and cleans up malformed registrations.
  • Filters invitation lookup by verified emails and adds integration coverage.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
infra/compose/kratos/identity.schema.json Enforces exactly one email.
apps/hash-api/src/auth/create-auth-handlers.ts Rejects malformed registrations.
apps/hash-api/src/graph/knowledge/system-types/user.ts Uses verified emails for invitation lookup.
tests/hash-backend-integration/src/tests/graph/knowledge/system-types/user.test.ts Covers signup and invitation protections.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.63%. Comparing base (27d9fc0) to head (ef38b58).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
.../hash-api/src/graph/knowledge/system-types/user.ts 0.00% 6 Missing ⚠️
apps/hash-api/src/auth/create-auth-handlers.ts 0.00% 3 Missing ⚠️
...pps/hash-api/src/shared/user-has-access-to-hash.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9245      +/-   ##
==========================================
- Coverage   59.63%   59.63%   -0.01%     
==========================================
  Files        1420     1420              
  Lines      138767   138775       +8     
  Branches     6555     6557       +2     
==========================================
  Hits        82753    82753              
- Misses      54950    54958       +8     
  Partials     1064     1064              
Flag Coverage Δ
apps.hash-api 13.98% <0.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

apps/hash-api/src/graph/knowledge/system-types/user.ts:662

  • There is no test for the newly introduced verified-email filtering. The existing getUserPendingInvitations coverage at tests/hash-backend-integration/src/tests/graph/knowledge/system-types/org-invitation-email-casing.test.ts:150-168 creates an identity with verifyEmails: true, so it would still pass if this code regressed to user.emails. Add a case with an unverified Kratos email and a matching email invitation that asserts the invitation is omitted (ideally alongside the verified case).
  const verifiedEmails = await getUserVerifiedEmails(context, _authentication, {
    user,
  });

infra/compose/kratos/identity.schema.json:13

  • The new maxItems: 1 runtime contract is not reflected in the TypeScript contracts that represent this schema: apps/hash-api/src/auth/ory-kratos.ts:21-24 still permits [string, ...string[]], and apps/hash-frontend/src/pages/shared/ory-kratos.ts:30-35 still declares string[]. As a result, createKratosIdentity and trait-update callers can compile with multiple emails and then fail only at Kratos. Please change these schema-facing types to an exactly-one tuple (and update affected callers) so the application contract matches the enforced schema.
          "maxItems": 1,

@CiaranMn
CiaranMn requested a review from TimDiekmann August 19, 2026 11:14
TimDiekmann
TimDiekmann previously approved these changes Aug 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/hash-api/src/graph/knowledge/system-types/user.ts:662

  • The changed filtering has no regression test for its core case: an invitation addressed to an email that exists on the user's Kratos identity but is still unverified. The existing invitation test only creates an identity with verifyEmails: true (tests/hash-backend-integration/src/tests/graph/knowledge/system-types/org-invitation-email-casing.test.ts:85-88), so it would still pass if this code reverted to user.emails. Add an integration case that creates an unverified identity and asserts getUserPendingInvitations excludes its email invitation (while preserving any shortname behavior).
  const verifiedEmails = await getUserVerifiedEmails(context, _authentication, {
    user,
  });

Copilot AI review requested due to automatic review settings August 19, 2026 20:29
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 19, 2026 20:29 Inactive
@vercel
vercel Bot temporarily deployed to Preview – petrinaut-docs August 19, 2026 20:29 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

tests/hash-backend-integration/src/tests/graph/knowledge/system-types/user.test.ts:459

  • deleteKratosIdentity removes only the Kratos record, but both attacker and inviter were also persisted as graph users (createUser above and createTestUser in tests/.../util.ts:131-139). This therefore leaves dangling graph users (and the inviter's actor-group role) in the shared integration graph after the test. Track both created users outside the try and clean them up with deleteUser({ userId: ... }), falling back to deleting the standalone identity only when user creation did not complete.
      await deleteKratosIdentity({ kratosIdentityId: identity.id });
      if (inviter) {
        await deleteKratosIdentity({
          kratosIdentityId: inviter.kratosIdentityId,
        });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps > hash-api Affects the HASH API (app) area/apps area/infra Relates to version control, CI, CD or IaC (area) area/tests > integration New or updated integration tests area/tests New or updated tests type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

3 participants