feat(authentication): add OAuth sign-in-only mode - #1596
kkopanidis wants to merge 3 commits into
Conversation
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.
This reverts commit b94fe9a.
4a2bc05 to
e368e70
Compare
ChrisPdgn
left a comment
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
invitationTokenas registration intent and skip this assert, or - fail at
/initwhen both are sent, so the user never completes Google/Apple consent first.
| payload, | ||
| stateToken.data.invitationToken, | ||
| stateToken.data.anonymousUserId, | ||
| resolveOAuthMode(stateToken.data.mode), |
There was a problem hiding this comment.
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.
Description
Adds a per-request OAuth
modeon login/register entry points so clients can start a flow as sign-in only or login and register.bothremains 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
API
modesignIn|bothbothwhen omittedApplied on:
GET /init/:providerandGET /initNative/:provider(stored on the OAuth state token)hookandPOST /native/:provider(read from state)POST /google,POST /facebookWhen
mode=signInand no matching user exists, the request fails withREGISTRATION_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?
resolveOAuthModeandassertOAuthRegistrationAllowed