fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(web): require User.email and reject SSO sign-ins without an email - #1310

Merged
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335
Jun 17, 2026
Merged

fix(web): require User.email and reject SSO sign-ins without an email#1310
brendan-kellam merged 4 commits into
mainfrom
brendan/require-user-email-SOU-1335

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1335

Problem

The Members settings page (and the Pending Requests tab) crashed with TypeError: Cannot read properties of null (reading 'toLowerCase') on instances that had User rows with a null email. These rows are created when an OAuth/OIDC identity provider returns a profile without an email — next-auth's adapter persists whatever profile() produced, and nothing in the app ever backfills it. The getOrgMembers/getOrgAccountRequests actions asserted email!, hiding the nullability from the type checker, and the client list components then called member.email.toLowerCase().

Approach

Make a non-null email an enforced invariant rather than something the UI has to defensively handle:

  1. SchemaUser.email is now String @unique (was String?).
  2. Self-healing migration (20260616000000_make_user_email_required) — because prisma migrate deploy runs automatically on container startup, a bare SET NOT NULL would fail and brick the upgrade on any instance that still has null rows. The migration first backfills existing nulls with a deterministic, unique, obviously-synthetic placeholder (placeholder-<id>@no-email.invalid), then applies NOT NULL. Backfilled rows are findable via WHERE email LIKE 'placeholder-%@no-email.invalid'.
  3. Guard — the signIn callback now rejects any sign-in that arrives without an email, so a null can never reach createUser (which would otherwise 500 on the NOT NULL insert). In practice only OAuth/OIDC profiles can lack one; the check is unconditional so future providers are covered too.
  4. Cleanup — removed the now-redundant email! assertions across the user-management/invite/auth code, corrected the stale "email is nullable" comment in the encrypted adapter, and tightened the public EE user API schemas (email is no longer nullable) + regenerated the OpenAPI spec.

Notes

  • Backfilled placeholder users will be blocked from re-signing-in until their IdP returns a real email (they were unusable anyway); admins can find them with the LIKE query above.
  • The API schema change is non-breaking for consumers — email was already always populated in practice; the contract now states it's guaranteed.

Testing

  • tsc --noEmit on @sourcebot/web: no new errors (pre-existing lastActiveAt test-fixture errors are unrelated).
  • Prisma client regenerated; OpenAPI spec regenerated and verified email is type: string and in required for PublicEeUser / PublicEeUserListItem.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a crash on the Members page when a user’s email is missing.
    • Rejected SSO sign-ins when the identity provider does not provide an email address.
    • Updated account and invitation-related flows to consistently require and use email where applicable.
  • Documentation
    • Updated the public API documentation/schemas to make email non-nullable.
  • Chores
    • Enforced User.email as required in the database via backfill + schema update.

Some User rows could be created with a null email — most commonly OAuth/OIDC
accounts created from an identity-provider profile that returned no email. These
rows crashed the Members and Pending Requests pages (`email.toLowerCase()` on a
null) and left unusable identities in the org.
- Make `User.email` required (`String @unique`) with a self-healing migration:
backfill any existing null emails with a deterministic, unique, synthetic
placeholder before applying NOT NULL, so the startup `migrate deploy` can never
fail on instances that already have null rows.
- Reject any sign-in that arrives without an email in the `signIn` callback, so a
null can never reach `createUser`.
- Drop the now-redundant `email!` assertions and tighten the public EE user API
schemas (`email` is no longer nullable); regenerate the OpenAPI spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai

coderabbitaiBot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4d16caf-ddde-4c10-8e2f-f1fc4ae571cc

📥 Commits

Reviewing files that changed from the base of the PR and between e1a4b15 and 7004b07.

📒 Files selected for processing (2)
  • packages/web/src/app/login/error/page.tsx
  • packages/web/src/auth.ts

Walkthrough

User.email is enforced as non-nullable: a migration backfills NULL emails with synthetic placeholders then sets NOT NULL, the Prisma schema updates accordingly, an SSO sign-in guard rejects email-less accounts, OpenAPI/Zod schemas remove nullable, and non-null assertions (!) are removed from application actions.

Changes

User email non-nullable enforcement

Layer / File(s)Summary
Database migration and Prisma schema
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/.../migration.sql
User.email changes from String? to String in the Prisma schema. The migration backfills any NULL email rows with <id>@no-email.invalid``, then ALTER TABLE ... SET NOT NULL.
Sign-in guard for missing email
packages/web/src/auth.ts, packages/web/src/app/login/error/page.tsx
callbacks.signIn now destructures user alongside account and returns false unconditionally when user.email is absent, redirecting to /login/error?error=EmailRequired. The error page displays a new EmailRequired case.
OpenAPI and Zod schema contracts
packages/web/src/openapi/publicApiSchemas.ts, docs/api-reference/sourcebot-public.openapi.json
publicEeUserSchema and publicEeUserListItemSchema change email from z.string().nullable() to z.string(). The generated OpenAPI JSON removes nullable: true from PublicEeUser and PublicEeUserListItem.
Non-null assertion removal in application actions
packages/web/src/actions.ts, packages/web/src/app/invite/actions.ts, packages/web/src/features/userManagement/actions.ts, packages/web/src/lib/authUtils.ts, packages/web/src/lib/encryptedPrismaAdapter.ts, CHANGELOG.md
Removes ! non-null assertions on .email across join-request emails, invite info host/recipient mapping, approval and invite email flows, org member/account-request mappings, and the invite-deletion filter. Adapter comment is updated; changelog entry is added.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#1066: Both PRs touch the Enterprise "public EE user" OpenAPI schema definitions in docs/api-reference/sourcebot-public.openapi.json—the main PR changes PublicEeUser.email/PublicEeUserListItem.email to non-nullable, while the other PR restructures/adds those EE schemas.
  • sourcebot-dev/sourcebot#1221: Both PRs modify packages/web/src/auth.ts's NextAuth callbacks.signIn logic to reject certain sign-in flows (main PR blocks when user.email is missing; retrieved PR blocks OAuth account-linking when there's no signed-in session).

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately and concisely summarizes the main changes: making User.email required and rejecting SSO sign-ins without email.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/require-user-email-SOU-1335

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Jun 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewJun 16, 2026, 11:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitaicoderabbitaiBot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 20: The changelog entry in CHANGELOG.md contains multiple sentences
separated by periods, but the repository format requires a single sentence
followed by the PR link. Combine the two sentences about fixing the Members page
crash and making User.email required into one cohesive sentence, then follow it
with the PR link [`#1310`](https://github.com/sourcebot-dev/sourcebot/pull/1310)
to match the required format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a97f7ab-f167-441c-a0a0-266c9a164f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4de1 and f054e81.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • packages/db/prisma/migrations/20260616000000_make_user_email_required/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/actions.ts
  • packages/web/src/app/invite/actions.ts
  • packages/web/src/auth.ts
  • packages/web/src/features/userManagement/actions.ts
  • packages/web/src/lib/authUtils.ts
  • packages/web/src/lib/encryptedPrismaAdapter.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadCHANGELOG.md
@brendan-kellam
brendan-kellam merged commit 8c37902 into mainJun 17, 2026
9 of 10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/require-user-email-SOU-1335 branch June 17, 2026 00:01
@github-actionsgithub-actionsBot mentioned this pull request Jun 17, 2026
Sign up for freeto 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

@brendan-kellam