Skip to content

feat(auth): turn off new email-only signups by default - #8492

Merged
pandemicsyn merged 3 commits into
mainfrom
feat/disable-email-signups
Oct 21, 2026
Merged

pandemicsyn merged 3 commits into
mainfrom
feat/disable-email-signups

Conversation

@pandemicsyn

@pandemicsyn pandemicsyn commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Turns off new accounts created via plain email (web magic link and mobile email code) by default. Set EMAIL_SIGNUPS_ENABLED=true to allow them again. Existing users keep signing in with email, and OAuth (Google, GitHub, GitLab, Apple, Anaconda, LinkedIn, Discord, ChatGPT), passkey, and SSO signups are unaffected.

  • Authoritative block: createOrUpdateUser refuses provider === 'email' only after existing-user matching and auto-linking, so an existing OAuth user using a magic link for the first time is still linked and signed in. A normalized-email match keeps its existing EMAIL-ALREADY-USED outcome. Nothing is created on refusal (no Bouncer call, Stripe customer, or user row). Magic links issued before deploy are also refused for new addresses.
  • Early checks: checkEmailSignInEligibility refuses new users before a magic link is sent; discovery omits Email for new addresses and flags emailSignupDisabled.
  • Web UX: new EMAIL-SIGNUP-DISABLED notice ("Email Sign-up Is Paused … choose one of the other sign-in options. Already have an account? You can still sign in with email."). It appears on the new-account provider screen, after a refused magic-link request (previously any refusal showed "We could not send your magic link"), and on callback redirects. It does not name providers, since the buttons are rendered below it.
  • Mobile: the code request still delivers a code (no account enumeration). The token exchange maps the refusal to SIGNUP-STEP-UP with Google/Apple, which released apps already render.
  • Switch: EMAIL_SIGNUPS_ENABLED must be exactly true to allow email signups; unset or anything else keeps them off. apps/web/.env.test sets it to true so existing suites keep exercising email signup; new tests turn it off explicitly.

Verification

  • No manual testing yet. Automated: pnpm --filter web typecheck, pnpm --filter web lint, pnpm test:web-env, and targeted jest suites (eligibility incl. default-off values, magic-link, discovery, native OTP/token, src/app/api/auth, AuthErrorNotification, useSignInFlow, signIn callback, auth components, and the full DB-backed src/lib/user/index.test.ts with new/existing/OAuth-link/normalized/OAuth-signup cases).

Visual Changes

New error notice copy only (existing AuthErrorNotification styling). Screenshots to follow.

Reviewer Notes

  • Default-off also applies to local dev and preview deployments unless EMAIL_SIGNUPS_ENABLED=true is set there.
  • Sign-up page still shows "Continue with Email" so existing users who land there can sign in; a new address gets the notice and is returned to the provider buttons.
  • Org invitees with a new email and no SSO domain must use an OAuth provider whose email matches the invite.

EMAIL_SIGNUPS_DISABLED=true refuses new accounts via web magic link and
mobile email code. Existing users keep email sign-in, including OAuth
users linking email for the first time; OAuth, passkey, and SSO signups
are unaffected.
@pandemicsyn
pandemicsyn marked this pull request as ready for review October 21, 2026 19:16
@pandemicsyn pandemicsyn changed the title feat(auth): add switch to pause new email signups feat(auth): turn off new email-only signups by default Oct 21, 2026

@iscekic iscekic left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

bot: Approved. One inline remark is worth fixing before merge: the signup guard can be bypassed for a legacy user whose stored email differs only by case. Two other remarks are informational.

Verified in the head tree:

  • The authoritative guard runs after existing-user matching and after the same-email auto-link, and before the Turnstile reuse check, admission, Bouncer, and Stripe. A refusal creates nothing.
  • Only two paths create an account with provider: 'email' (the NextAuth callback and native/token). Both reach that guard. No other provider is touched.
  • Existing email users match by auth-provider row. An OAuth user with the same address takes the auto-link branch. An alias match still returns EMAIL-ALREADY-USED.
  • Mobile: the OTP route stays enumeration-resistant, and the exchange maps the refusal to SIGNUP-STEP-UP with the Google and Apple methods, which parseAuthError and signupStepUpMessage already handle.
  • Web: callbackAuthErrors, AuthErrorNotification, and useSignInFlow all accept the new code. The discovery flag appears only on kind: 'new'.
  • CI: every check except the pending Kilo Code Review is green, and the branch has no conflicts.

Correction: I first posted that the magic-link refusal adds an account-existence signal. That was wrong: /api/sso/organizations already answers new versus existing behind the same Turnstile cookie, so I withdrew that remark.

Not covered: the PR says no manual test, so the new notice has only been rendered in jest. A screenshot would close the Visual Changes item.

Operational note, also in the reviewer notes: with the default off, local dev and preview deployments need EMAIL_SIGNUPS_ENABLED=true, or email signup stops there after this deploys.

return {
ok: false,
status: 403,
errorCode: 'EMAIL-SIGNUP-DISABLED',

@iscekic iscekic Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

bot: Withdrawn as a false positive: no new disclosure. POST /api/sso/organizations already returns kind: 'new' versus kind: 'existing' for any address, behind the same Turnstile cookie, and the sign-in UI relies on that answer. The magic-link refusal therefore reveals nothing that the discovery endpoint does not already reveal. No action needed.

Comment thread apps/web/src/lib/user/index.ts Resolved
Comment thread apps/web/src/lib/user/index.ts Resolved
Comment thread apps/web/.env.test Resolved
Comment thread apps/web/src/lib/user/index.ts Resolved
@kilo-code-bot

kilo-code-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commit closes the legacy normalized_email IS NULL gap by rejecting every email signup while the switch is off, and moves the test default into Jest global setup; no new issues in the incremental diff.

Assumptions: [INFERENCE] Jest global setup env mutations reach workers, consistent with the existing .env.test loading pattern in apps/web/src/tests/setup/globalSetup.ts; Playwright loads .env.test directly and has no email-signup E2E relying on the removed default. Prior findings on apps/web/.env.test, the missing operationId, and the legacy case-variant fall-through are resolved (tests added in apps/web/src/lib/user/index.test.ts).

Files Reviewed (5 files)
  • ENVIRONMENT.md
  • apps/web/.env.test
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/tests/setup/globalSetup.ts
Previous Review Summary (commit 004f22a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 004f22a)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1

Assumptions: The magic-link 403 for new addresses is an accepted account-existence oracle because /api/sso/organizations already returns kind: existing|new; no memory-leak concerns were found in the changed client hook.

Issue Details (click to expand)

WARNING

File Line Issue
apps/web/.env.test 115 The tracked EMAIL_SIGNUPS_ENABLED=true default makes pnpm web:env set EMAIL_SIGNUPS_ENABLED true fail, blocking the documented path to re-enable signups.

SUGGESTION

File Line Issue
apps/web/src/lib/user/index.ts 614 Legacy normalized_email IS NULL rows pass this gate but not the insert-time dedup, so a case-variant email signup can create a duplicate account while the pause is on.
Files Reviewed (21 files)
  • ENVIRONMENT.md - 0 issues
  • apps/web/.env.test - 1 issue
  • apps/web/src/app/api/auth/magic-link/route.test.ts - 0 issues
  • apps/web/src/app/api/auth/native/otp/route.test.ts - 0 issues
  • apps/web/src/app/api/auth/native/otp/route.ts - 0 issues
  • apps/web/src/app/api/auth/native/token/route.test.ts - 0 issues
  • apps/web/src/app/api/auth/native/token/route.ts - 0 issues
  • apps/web/src/app/api/sso/organizations/route.test.ts - 0 issues
  • apps/web/src/app/api/sso/organizations/route.ts - 0 issues
  • apps/web/src/components/auth/AuthErrorNotification.test.ts - 0 issues
  • apps/web/src/components/auth/AuthErrorNotification.tsx - 0 issues
  • apps/web/src/hooks/useSignInFlow.test.ts - 0 issues
  • apps/web/src/hooks/useSignInFlow.ts - 0 issues
  • apps/web/src/lib/auth/email-signin-eligibility.test.ts - 0 issues
  • apps/web/src/lib/auth/email-signin-eligibility.ts - 0 issues
  • apps/web/src/lib/auth/email-signup-switch.ts - 0 issues
  • apps/web/src/lib/schemas/sso-organizations.ts - 0 issues
  • apps/web/src/lib/user/index.test.ts - 0 issues
  • apps/web/src/lib/user/index.ts - 1 issue
  • apps/web/src/lib/user/next-auth-options.ts - 0 issues
  • packages/web-shared/src/lib/auth/constants.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 51.1K · Output: 9.5K · Cached: 941.7K

Review guidance: REVIEW.md from base branch main

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants