Skip to content

feat(authentication): add OAuth sign-in-only mode - #1596

Open
kkopanidis wants to merge 3 commits into
mainfrom
cursor/oauth-signin-mode-af9f
Open

kkopanidis wants to merge 3 commits into
mainfrom
cursor/oauth-signin-mode-af9f

Conversation

@kkopanidis

@kkopanidis kkopanidis commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a per-request OAuth mode on login/register entry points so clients can start a flow as sign-in only or login and register. both remains the default.

This is a client-controlled UX switch (login screen vs register-after-terms). It is not a security boundary: anyone who can call the Client API can pass mode=both. A config flag that disables OAuth registration entirely was considered and dropped, because that would block legitimate registration after extra steps.

Type of change

  • New feature (non-breaking change which adds functionality)

API

  • Param: mode
  • Values: signIn | both
  • Default: both when omitted

Applied on:

  • GET /init/:provider and GET /initNative/:provider (stored on the OAuth state token)
  • Redirect hook and POST /native/:provider (read from state)
  • Direct token routes: POST /google, POST /facebook

When mode=signIn and no matching user exists, the request fails with REGISTRATION_NOT_ALLOWED (403). Existing-user login and account linking still work. Anonymous-user conversion is treated as registration and is blocked in sign-in-only mode.

How Has This Been Tested?

  • Unit tests for resolveOAuthMode and assertOAuthRegistrationAllowed
Open in Web Open in Cursor 

cursoragent and others added 3 commits September 10, 2026 12:09
Allow OAuth init and token routes to request signIn or both (default).
Sign-in-only blocks new user creation so clients can collect terms and
profile data before registration.

Co-authored-by: Konstantinos Kopanidis <kkopanidis@users.noreply.github.com>
Request mode remains a per-call UX switch. Provider allowRegistration
(default true) is the policy ceiling so clients cannot force account
creation when registration is disabled.
@cursor
cursor Bot force-pushed the cursor/oauth-signin-mode-af9f branch from 4a2bc05 to e368e70 Compare September 10, 2026 12:10
@kkopanidis
kkopanidis marked this pull request as ready for review September 10, 2026 12:49

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

Two product holes in mode=signIn. Default both is unchanged and non-breaking.

if (!user.isVerified) user.isVerified = true;
user = await User.getInstance().findByIdAndUpdate(user._id, user);
} else {
assertOAuthRegistrationAllowed(mode);

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.

invitationToken + mode=signIn rejects a real invite signup.

This assert runs in the “no User found” branch before invite validation (inviteValidation / addUserToTeam below). A new invitee who starts OAuth with both invitationToken and mode=signIn gets REGISTRATION_NOT_ALLOWED after they already consented at the provider. The invite token is not consumed.

That will happen if an invite landing page reuses the login-screen OAuth helper (same “Continue with Google”, plus the token). Local POST /local/new still accepts invites; only OAuth invite signup breaks.

An invite is registration. Either:

  • treat a present invitationToken as registration intent and skip this assert, or
  • fail at /init when both are sent, so the user never completes Google/Apple consent first.

payload,
stateToken.data.invitationToken,
stateToken.data.anonymousUserId,
resolveOAuthMode(stateToken.data.mode),

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.

Web redirect signIn failures never return to the app.

On success, TokenProvider.provideUserTokens redirects to customRedirectUri / finalRedirect with tokens. If createOrUpdateUser throws REGISTRATION_NOT_ALLOWED here, the router returns 403 JSON on /hook/authentication/{provider} and redirectUri is never used.

That is pre-existing for rare hook errors (inactive user, bad invite), but mode=signIn on a login screen makes “this Google account has no User yet” a normal outcome of tapping Continue with Google.

Native POST /google / POST /facebook are fine (the app can handle 403). Redirect /init/:provider is not — the browser is stuck on Conduit.

Catch REGISTRATION_NOT_ALLOWED in authorize (and Apple’s override) and redirect to customRedirectUri with conduitCode=REGISTRATION_NOT_ALLOWED instead of throwing on the hook. Same issue exists on Apple’s authorize.

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.

3 participants