Skip to content

Commit 69ea487

Browse files
committed
Handle email duplicates for OAuth signup
1 parent cd564d2 commit 69ea487

File tree

3 files changed

+40
-22
lines changed

3 files changed

+40
-22
lines changed

server/config/passport.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import { BasicStrategy } from 'passport-http';
1010

1111
import User from '../models/user';
1212

13+
function generateUniqueUsername(username) {
14+
const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
15+
return slugify(`${username} ${adj}`);
16+
}
17+
1318
passport.serializeUser((user, done) => {
1419
done(null, user.id);
1520
});
@@ -108,14 +113,20 @@ passport.use(new GitHubStrategy({
108113
existingEmailUser.verified = User.EmailConfirmation.Verified;
109114
existingEmailUser.save(saveErr => done(null, existingEmailUser));
110115
} else {
111-
const user = new User();
112-
user.email = primaryEmail;
113-
user.github = profile.id;
114-
user.username = profile.username;
115-
user.tokens.push({ kind: 'github', accessToken });
116-
user.name = profile.displayName;
117-
user.verified = User.EmailConfirmation.Verified;
118-
user.save(saveErr => done(null, user));
116+
let { username } = profile;
117+
User.findByUsername(username, true, (findByUsernameErr, existingUsernameUser) => {
118+
if (existingUsernameUser) {
119+
username = generateUniqueUsername(username);
120+
}
121+
const user = new User();
122+
user.email = primaryEmail;
123+
user.github = profile.id;
124+
user.username = profile.username;
125+
user.tokens.push({ kind: 'github', accessToken });
126+
user.name = profile.displayName;
127+
user.verified = User.EmailConfirmation.Verified;
128+
user.save(saveErr => done(null, user));
129+
});
119130
}
120131
});
121132
});
@@ -141,10 +152,9 @@ passport.use(new GoogleStrategy({
141152

142153
User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => {
143154
let username = profile._json.emails[0].value.split('@')[0];
144-
User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => {
155+
User.findByUsername(username, true, (findByUsernameErr, existingUsernameUser) => {
145156
if (existingUsernameUser) {
146-
const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
147-
username = slugify(`${username} ${adj}`);
157+
username = generateUniqueUsername(username);
148158
}
149159
// what if a username is already taken from the display name too?
150160
// then, append a random friendly word?

server/controllers/user.controller.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,7 @@ export function createUser(req, res, next) {
9393
export function duplicateUserCheck(req, res) {
9494
const checkType = req.query.check_type;
9595
const value = req.query[checkType];
96-
const query = {};
97-
query[checkType] = value;
98-
// Don't want to use findByEmailOrUsername here, because in this case we do
99-
// want to use case-insensitive search for usernames to prevent username
100-
// duplicates, which overrides the default behavior.
101-
User.findOne(query).collation({ locale: 'en', strength: 2 }).exec((err, user) => {
96+
User.findByEmailOrUsername(value, true, (err, user) => {
10297
if (user) {
10398
return res.json({
10499
exists: true,

server/models/user.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,19 @@ userSchema.statics.findByEmail = function findByEmail(email, cb) {
170170
* Queries User collection by username and returns one User document.
171171
*
172172
* @param {string} username - Username string
173+
* @param {boolean} [caseInsensitive] - Does a caseInsensitive query, defaults to false
173174
* @callback [cb] - Optional error-first callback that passes User document
174175
* @return {Promise<Object>} - Returns Promise fulfilled by User document
175176
*/
176-
userSchema.statics.findByUsername = function findByUsername(username, cb) {
177+
userSchema.statics.findByUsername = function findByUsername(username, caseInsensitive, cb) {
177178
const query = {
178179
username
179180
};
180-
return this.findOne(query, cb);
181+
if (arguments.length === 3
182+
|| (arguments.length === 2 && typeof caseInsensitive === 'boolean' && caseInsensitive)) {
183+
return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb);
184+
}
185+
return this.findOne(query, cb || caseInsensitive);
181186
};
182187

183188
/**
@@ -187,15 +192,23 @@ userSchema.statics.findByUsername = function findByUsername(username, cb) {
187192
* a username or email.
188193
*
189194
* @param {string} value - Email or username
195+
* @param {boolean} caseInsensitive - Does a caseInsensitive query rather than
196+
* default query for username or email, defaults
197+
* to false
190198
* @callback [cb] - Optional error-first callback that passes User document
191199
* @return {Promise<Object>} - Returns Promise fulfilled by User document
192200
*/
193-
userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, cb) {
201+
userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, caseInsensitive, cb) {
194202
const isEmail = value.indexOf('@') > -1;
203+
if (arguments.length === 3
204+
|| (arguments.length === 2 && typeof caseInsensitive === 'boolean' && caseInsensitive)) {
205+
const query = isEmail ? { email: value } : { username: value };
206+
return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb);
207+
}
195208
if (isEmail) {
196-
return this.findByEmail(value, cb);
209+
return this.findByEmail(value, cb || caseInsensitive);
197210
}
198-
return this.findByUsername(value, cb);
211+
return this.findByUsername(value, cb || caseInsensitive);
199212
};
200213

201214
/**

0 commit comments

Comments
 (0)