Skip to content

feat(auth): Replace Slack sign-in with an allowed domain list - #2229

Merged
dcramer merged 6 commits into
mainfrom
feat/sign-in-domain-allowlist
Oct 26, 2026
Merged

dcramer merged 6 commits into
mainfrom
feat/sign-in-domain-allowlist

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

This removes the experimental slack-sign-in step that required a Google sign-in before using Junior in Slack (#2220). In its place, one optional domain list limits who can use Slack and who can sign in to the dashboard. Setting the list is recommended. Slack and Google each verify the domain.

One list: createApp({ allowedDomains }) / JUNIOR_ALLOWED_DOMAINS

  • The list is a top-level app setting, not a dashboard or Google setting. Core resolves it once and passes it to the Slack gate and the dashboard.
  • junior init scaffolds allowedDomains: ["example.com"] as a placeholder, and the quickstart docs show the same.
  • Slack: a mention or DM starts a Turn only when the author's Slack profile email is on an allowed domain. Slack verifies that email. Junior drops other messages without replying, so the duplicate sign-in prompt bug goes away with the gate. The check is in chat/ingress/slack-allowed-domain.ts. Results are cached for 5 minutes when allowed and 30 seconds when not, like the membership check.
  • Google (dashboard): core's Better Auth getUserInfo accepts an account only when the ID token's hd claim is on the list. It does not use the email domain. A personal Google account can carry any verified email, for example an ex-employee's @sentry.io address. Only Workspace accounts get hd.
  • hd hint: with one domain, Google gets hd=<domain>. With more than one, it gets hd=*, so the account chooser shows only Workspace accounts. Better Auth's built-in hd check supports only one domain, so Junior replaces its getUserInfo.
  • Empty list: any member of the Slack workspace can use Junior, as before feat(slack): Require Google sign-in before first Slack use #2220. Startup logs app.allowed_domains.unset. Dashboard auth still requires at least one domain.

Removed

  • The slack-sign-in experiment, slack.signInMaxAgeDays, /api/sign-in/slack, and the Slack sign-in state and prompt. Also slackUserHasGoogleSignIn and findSlackIdentityUserId, which nothing else used.
  • The email allowlist: dashboard.allowedEmails and JUNIOR_DASHBOARD_ALLOWED_EMAILS. It cannot be checked against hd. /api/config no longer returns allowedEmailCount.
  • dashboard.allowedGoogleDomains and JUNIOR_DASHBOARD_GOOGLE_DOMAINS, replaced by the top-level setting. /api/config returns allowedDomainCount instead of allowedGoogleDomainCount.
  • Core no longer mounts /api/auth/* when the dashboard is not mounted. Only the Slack gate needed that.

Rollout

  • getsentry/junior-prod had to stop setting experimental["slack-sign-in"] before it upgrades, because the removed flag now throws at startup. That landed in getsentry/junior-prod#544.
  • Startup also fails when dashboard.allowedGoogleDomains or JUNIOR_DASHBOARD_GOOGLE_DOMAINS is set, so a stale config cannot leave the list empty and let every Slack workspace member in. junior-prod must move dashboard.allowedGoogleDomains: ["sentry.io"] to top-level allowedDomains: ["sentry.io"] before it upgrades.
  • Once set, Slack becomes domain-gated. Prod uses ["sentry.io"], so Slack users with emails on other domains are blocked.

Adversarial review
Fixed in this PR:

  • A malformed Google ID token caused a 500. The parse is now safe, and the callback redirects with Better Auth's normal error.
  • A Slack app without users:read.email would block everyone with no signal. Rejections now log slack.allowed_domain.rejected, with app.reason set to missing_email or domain_not_allowed.
  • Removed stale comments that described a Google session shared with Slack.

Open issues, worth a decision:

  1. Dashboard read-time check still uses the email domain. I tried storing hd on the session so each read could recheck it. Better Auth only copies provider profile fields for input: true additional fields, and those are writable through /update-user. So I backed that out. Effect: sign-in is gated on hd, and existing sessions and personal tokens are rechecked against the email domain. A Workspace user whose email is on an alias domain (hd=sentry.io, email @sentry.dev) gets a 403.
  2. Sessions from before the deploy. Cookie sessions from before this change last up to 8 hours. In that window, a personal Google account with an allowed-domain email can still open the dashboard. Renaming the cookie prefix would sign everyone out once and close this. I left it out.
  3. Silent drop in Slack. Blocked Slack authors get no reply, which matches how Slack Connect authors are treated. A private notice would help them, but it would need the dedupe that caused the double prompt.
  4. Same scope as feat(slack): Require Google sign-in before first Slack use #2220. Passive thread replies, slash commands, Slack actions, and stop commands are not domain-checked. Bot users have no email, so they are blocked from mentions when a list is set.
  5. Two lookups. A new author can cost two users.info calls, one for membership and one for the domain check. Both are cached. They could be merged later.
  6. Dead column. recordGoogleSignIn still writes signed_in_at, and nothing reads it now. I kept the identity write and did not add a migration to drop the column.

Verification

  • Merged with current main (TypeScript 7 upgrade). Repo-wide pnpm typecheck and pnpm lint pass.
  • Tests: core unit tests, the Google sign-in, Slack auth-boundary, and example-build integration tests, and the full dashboard suite (328 tests). oauth-identity-link times out at 5s in this sandbox, including on the base commit, so that one isn't verified here.

via David Cramer.

--

View Junior Session [Sentry]

Remove the experimental slack-sign-in pre-auth flow. One optional,
recommended domain list (dashboard.allowedGoogleDomains) now limits
both surfaces:

- Slack: mentions and DMs start a Turn only when the author's
  Slack-verified profile email is on an allowed domain.
- Google: dashboard sign-in accepts only Workspace accounts whose
  verified hd claim is on an allowed domain. Junior sends hd=<domain>
  for one domain and hd=* for more.

Drop the email allowlist (allowedEmails, JUNIOR_DASHBOARD_ALLOWED_EMAILS).

Co-Authored-By: Sergiy Deceive-skiy <3760543+sergical@users.noreply.github.com>
Co-Authored-By: David Cramer <david@sentry.io>
@vercel

vercel Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
junior-docs Ready Ready Preview Oct 26, 2026 7:17pm UTC

Request Review

@dcramer
dcramer marked this pull request as ready for review October 26, 2026 18:37
@github-actions github-actions Bot added the risk: high PR risk score: high label Oct 26, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 950279d. Configure here.

Comment thread packages/junior/src/chat/sign-in/auth.ts
Domain allowlisting gates Slack and the dashboard, so it no longer lives
under dashboard or carries a Google-specific name. Configure it with
createApp({ allowedDomains }) or JUNIOR_ALLOWED_DOMAINS. Startup fails on
the removed dashboard.allowedGoogleDomains and
JUNIOR_DASHBOARD_GOOGLE_DOMAINS so a stale config cannot silently allow
every Slack workspace member.

Co-Authored-By: David Cramer <david@sentry.io>
decodeJwt throws on a malformed compact JWT before the claims schema
runs, so a bad id_token still returned a 500 from the callback. Catch it
and return null so Better Auth redirects with unable_to_get_user_info.
Drop stale comments that still describe a Google session shared with
Slack, read the Slack profile email domain only when the email has an
@, and remove a unit test that duplicated the createApp legacy-name
coverage.

Co-Authored-By: David Cramer <david@sentry.io>
sentry-junior Bot and others added 2 commits October 26, 2026 19:12
New apps from junior init set allowedDomains to example.com so the
recommended domain gate is visible from the start. Docs snippets use the
same placeholder.

Co-Authored-By: David Cramer <david@sentry.io>
…sign-in-domain-allowlist

Co-Authored-By: David Cramer <david@sentry.io>
@dcramer
dcramer merged commit 71e973c into main Oct 26, 2026
44 checks passed
@dcramer
dcramer deleted the feat/sign-in-domain-allowlist branch October 26, 2026 19:30

This branch was successfully deployed

1 active deployment
Preview – junior-docs — 3871c87a Deployed Oct 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants