sahat/hackathon-starter · error · Error
EMAIL_COLLISION
EMAIL_COLLISION
Error message
EMAIL_COLLISION
What it means
Thrown when an OAuth signup for a brand-new provider identity would create an account whose normalized email already exists on another user. The app refuses to auto-merge accounts by email to prevent account takeover.
Source
Thrown at config/passport.js:129
user.profile.location = user.profile.location || providerProfile.location;
user.profile.website = user.profile.website || providerProfile.website;
await user.save();
return user;
}
// User is not logged in:
const existingUser = await User.findOne({ [providerName]: { $eq: providerProfile.id } });
if (existingUser) {
return existingUser;
}
const normalizedEmail = providerProfile.email ? validator.normalizeEmail(providerProfile.email, { gmail_remove_dots: false }) : undefined;
if (!normalizedEmail) {
throw new Error('EMAIL_REQUIRED');
}
const existingEmailUser = await User.findOne({
email: { $eq: normalizedEmail },
});
if (existingEmailUser) {
throw new Error('EMAIL_COLLISION');
}
const user = new User();
user.email = normalizedEmail;
user[providerName] = providerProfile.id;
req.user = user;
if (oauth2provider) {
await saveOAuth2UserTokens(req, accessToken, refreshToken, params.expires_in, refreshTokenExpiration, providerName, tokenConfig);
} else {
user.tokens.push({ kind: providerName, accessToken, ...(tokenSecret && { tokenSecret }) });
}
user.profile.name = providerProfile.name;
user.profile.gender = providerProfile.gender;
if (providerProfile.picture) {
user.profile.pictures = new Map();
user.profile.pictures.set(providerName, providerProfile.picture);
user.profile.picture = providerProfile.picture;
user.profile.pictureSource = providerName;View on GitHub (pinned to c12e339564)
Solutions
- Log in with the existing local account (email/password) and link the provider from the profile page instead
- Delete or rename the old account if it is a leftover test account
- As the operator, manually merge the provider id onto the existing user document
Example fix
// before: user hits EMAIL_COLLISION on new OAuth signup
// after (operator): attach provider id to the existing account
db.users.updateOne({ email: 'bob@example.com' }, { $set: { google: '12345' } }); Defensive patterns
Strategy: validation
Validate before calling
const emailUser = await User.findOne({ email: normalizedEmail });
if (emailUser && !emailUser[provider]) {
// prompt: log in with existing account, then link provider
} Try / catch
catch (e) { if (e.message === 'EMAIL_COLLISION') { req.flash('errors', {msg:'Email already registered. Log in with your password, then link this provider in your profile.'}); return res.redirect('/login'); } throw e; } Prevention
- Offer a 'link provider' flow prominently on the profile page
- Never auto-merge accounts by email — keep the explicit error
When it happens
Trigger: User previously signed up locally with bob@example.com; now signs in with Google whose profile email normalizes to the same address; no user has google === profile.id yet, so existingEmailUser is found and the error is thrown.
Common situations: Signing in with a provider before realizing you already have a local account; case/dot variations of Gmail addresses normalizing to the same mailbox; testing OAuth with the same email across multiple providers.
Related errors
- EMAIL_REQUIRED
- PROVIDER_COLLISION
- Missing or invalid token/tokenSecret
- HTTP error! status: ${response.status}
AI-assisted analysis of sahat/hackathon-starter@c12e339564 (2026-08-27).
Data as JSON: /api/errors/0750d47ff721529b.
Report an issue: GitHub.