Skip to content

feat: add option to never match accounts by email address - #181

Open
heudev wants to merge 2 commits into
NodeBB:mainfrom
heudev:feat/disable-email-fallback
Open

feat: add option to never match accounts by email address#181
heudev wants to merge 2 commits into
NodeBB:mainfrom
heudev:feat/disable-email-fallback

Conversation

@heudev

@heudev heudev commented Aug 12, 2026

Copy link
Copy Markdown

Adds a per-strategy "Never attach this provider to an existing account that shares its email address" option.

Why

Before creating an account, OAuth.login looks for an existing user with the same address:

// Check for user via email fallback
if (email && email_verified) {
	uid = await user.getUidByEmail(payload.email);
}

That is a reasonable default — one person, one account, several ways to sign in — but it is unconditional, and the consequence surprises people. Concretely, on a forum with GitHub and LinkedIn strategies configured, signing in with GitHub creates an account, and then signing in with LinkedIn using the same address does not create a second account: it attaches linkedinId to the GitHub account. The profile sync that runs on every login then overwrites the avatar and full name with the second provider's values, so the account visibly changes identity.

Some deployments genuinely want one account per provider, and today there is no way to express that short of turning off trustEmailVerified — which also disables automatic email confirmation, an unrelated concern.

What changed

  • New disableEmailFallback checkbox on the strategy editor, registered in editStrategy's checkbox list so it is normalised to 1/0 like its siblings.
  • When enabled, OAuth.login skips the getUidByEmail lookup and always registers a new account.

Default is off, so existing installs keep matching by email exactly as before.

Confirmation is no longer allowed to abandon the account

Turning the option on exposed a second problem. UserEmail.confirmByUid refuses an address that another uid already holds confirmed:

const oldUid = await db.sortedSetScore('email:uid', currentEmail.toLowerCase());
if (oldUid && oldUid !== parseInt(uid, 10)) {
	throw new Error('[[error:email-taken]]');
}

Which is exactly the situation this option creates: the second provider registers a new account for an address the first account already confirmed. The throw escaped OAuth.login after user.create had run but before ${provider}Id was written, so every attempt left behind an account nobody could sign into, and the user just saw "Bu e-posta adresi halihazırda başka biri tarafından kullanılıyor". Two logins produced two such accounts.

Confirmation failure is now logged and swallowed. The account is created, the provider association is written, and the login completes — the address simply stays unconfirmed on the second account, which is the only outcome NodeBB's one-confirmed-address-per-uid rule permits.

Testing

Verified on a live NodeBB 4.14.10 forum with GitHub and LinkedIn strategies sharing one address. With the option off, the LinkedIn login attached itself to the existing GitHub account (reproducing the behaviour above). With it on, LinkedIn registered its own account and the GitHub account was left untouched. npx eslint . is clean.

heudev added 2 commits August 12, 2026 16:30
A verified address is matched against existing users, so a second
provider sharing it joins the first account instead of registering its
own. Some forums want one account per provider.
NodeBB refuses to confirm an address that another uid already holds
confirmed, so with the email fallback disabled the registration threw
after user.create, leaving an account with no provider association and
no way to sign into it.
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.

1 participant