diff --git a/bin/cli-users b/bin/cli-users index 71c51e44d..208db5053 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -105,8 +105,7 @@ function printUserAsTable(user) { table.push( {'ID': user.id.gray}, {'Username': user.username}, - {'Emails': user.profiles.filter(({provider}) => provider === 'local').map(({id})=> id) - .join(', ')}, + {'Emails': user.emails}, {'Tags': user.tags ? user.tags.map(({tag: {name}}) => name) : ''}, {'Role': user.role}, {'Verified': user.hasVerifiedEmail}, @@ -161,11 +160,7 @@ async function searchUsers() { } return data.users.nodes.map((user) => { - const emails = user.profiles - .filter(({provider}) => provider === 'local') - .map(({id})=> id) - .join(', '); - + const emails = user.emails.join(', '); return { name: `${user.username} (${emails}) ${user.id.gray} - ${user.role.gray}`, value: user.id, @@ -230,8 +225,8 @@ async function verifyUserEmail(userID, email) { } // Get all the user's email addresses. - const emails = user.profiles.filter(({provider}) => provider === 'local').map(({id}) => id); - if (!emails || emails.length === 0) { + const emails = user.emails; + if (emails.length === 0) { throw new Error('user did not have any email addresses'); } diff --git a/models/user.js b/models/user.js index 37edf4dde..70de303e7 100644 --- a/models/user.js +++ b/models/user.js @@ -279,6 +279,27 @@ UserSchema.method('can', function(...actions) { return can(this, ...actions); }); +/** + * firstEmail will return the first email on the user. + */ +UserSchema.virtual('firstEmail').get(function() { + const emails = this.emails; + if (emails.length === 0) { + return null; + } + + return emails[0]; +}); + +/** + * emails will return all the emails on a user. + */ +UserSchema.virtual('emails').get(function() { + return (this.profiles || []) + .filter(({provider}) => provider === 'local') + .map(({id}) => id); +}); + /** * hasVerifiedEmail will return true if at least one of the local email accounts * have their email verified. diff --git a/routes/api/users/index.js b/routes/api/users/index.js index fd71fecda..17ecfae75 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -79,13 +79,13 @@ router.post('/:user_id/email/confirm', authorization.needed('ADMIN', 'MODERATOR' } // Find the first local profile. - let localProfile = user.profiles.find((profile) => profile.provider === 'local'); - if (!localProfile) { + const email = user.firstEmail; + if (!email) { return next(errors.ErrMissingEmail); } // Send the email to the first local profile that was found. - await UsersService.sendEmailConfirmation(user, localProfile.id); + await UsersService.sendEmailConfirmation(user, email); res.status(204).end(); } catch (e) { diff --git a/services/passport.js b/services/passport.js index 1d5d0ddcf..36c98fc31 100644 --- a/services/passport.js +++ b/services/passport.js @@ -11,6 +11,7 @@ const uuid = require('uuid'); const debug = require('debug')('talk:services:passport'); const bowser = require('bowser'); const ms = require('ms'); +const _ = require('lodash'); // Create a redis client to use for authentication. const {createClientFactory} = require('./redis'); @@ -144,7 +145,7 @@ async function ValidateUserLogin(loginProfile, user, done) { // If the profile doesn't have a metadata field, or it does not have a // confirmed_at field, or that field is null, then send them back. - if (!profile.metadata || !profile.metadata.confirmed_at || profile.metadata.confirmed_at === null) { + if (_.get(profile, 'metadata.confirmed_at', null) === null) { return done(errors.ErrNotVerified); } } @@ -318,7 +319,7 @@ const CheckIfNeedsRecaptcha = (user, email) => { throw new Error('ID indicated by loginProfile is not on user object'); } - if (profile.metadata && profile.metadata.recaptcha_required) { + if (_.get(profile, 'metadata.recaptcha_required', false)) { return true; } @@ -501,7 +502,7 @@ passport.use(new LocalStrategy({ } // Define the loginProfile being used to perform an additional - // verificaiton. + // verification. let loginProfile = {id: email, provider: 'local'}; // Perform final steps to login the user. diff --git a/services/users.js b/services/users.js index 33c6047fa..03d10c771 100644 --- a/services/users.js +++ b/services/users.js @@ -339,7 +339,7 @@ class UsersService { } /** - * Sets or unsets the recaptcha_required flag on a user's local profile. + * Sets or removes the recaptcha_required flag on a user's local profile. */ static flagForRecaptchaRequirement(email, required) { return UserModel.update( @@ -436,20 +436,17 @@ class UsersService { } static async sendEmail(user, options) { - const localProfile = user.profiles.find( - (profile) => profile.provider === 'local' - ); - if (!localProfile) { - throw new Error('user does not have an email'); + const email = user.firstEmail; + if (!email) { + + // Rather than throwing an error here, we'll + console.warn(new Error('user does not have an email')); + return; } - const {id: to} = localProfile; - - options = merge(options, { - to, - }); - - return mailer.send(options); + return mailer.send(merge({}, options, { + to: email, + })); } static async changePassword(id, password) {