|
@ -4,6 +4,8 @@ |
|
|
|
|
|
|
|
|
'use strict'; |
|
|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
|
const path = require('path'); |
|
|
|
|
|
|
|
|
const mongoose = require('mongoose'); |
|
|
const mongoose = require('mongoose'); |
|
|
|
|
|
|
|
|
const User = mongoose.model('User'); |
|
|
const User = mongoose.model('User'); |
|
@ -23,6 +25,8 @@ class UserService { |
|
|
this.dtp = dtp; |
|
|
this.dtp = dtp; |
|
|
this.log = new SiteLog(dtp, `svc:${module.exports.slug}`); |
|
|
this.log = new SiteLog(dtp, `svc:${module.exports.slug}`); |
|
|
|
|
|
|
|
|
|
|
|
this.reservedNames = require(path.join(this.dtp.config.root, 'config', 'reserved-names')); |
|
|
|
|
|
|
|
|
this.populateUser = [ |
|
|
this.populateUser = [ |
|
|
{ |
|
|
{ |
|
|
path: 'picture.large', |
|
|
path: 'picture.large', |
|
@ -99,13 +103,18 @@ class UserService { |
|
|
canChat: true, |
|
|
canChat: true, |
|
|
canComment: true, |
|
|
canComment: true, |
|
|
canReport: true, |
|
|
canReport: true, |
|
|
canAuthorPages: false, |
|
|
}; |
|
|
canAuthorPosts: false, |
|
|
|
|
|
|
|
|
user.optIn = { |
|
|
|
|
|
system: true, |
|
|
|
|
|
newsletter: false, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
this.log.info('creating new user account', { email: userDefinition.email }); |
|
|
this.log.info('creating new user account', { email: userDefinition.email }); |
|
|
await user.save(); |
|
|
await user.save(); |
|
|
|
|
|
|
|
|
|
|
|
await this.sendWelcomeEmail(user); |
|
|
|
|
|
|
|
|
return user.toObject(); |
|
|
return user.toObject(); |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
this.log.error('failed to create user', { error }); |
|
|
this.log.error('failed to create user', { error }); |
|
@ -113,6 +122,71 @@ class UserService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async sendWelcomeEmail (user) { |
|
|
|
|
|
const { email: emailService } = this.dtp.services; |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* Remove all pending EmailVerify tokens for the User. |
|
|
|
|
|
*/ |
|
|
|
|
|
await emailService.removeVerificationTokensForUser(user); |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* Create the new/only EmailVerify token for the user. This will be the only |
|
|
|
|
|
* token accepted. Previous emails sent (if they were received) are invalid |
|
|
|
|
|
* after this. |
|
|
|
|
|
*/ |
|
|
|
|
|
const verifyToken = await emailService.createVerificationToken(user); |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* Send the welcome email using the new EmailVerify token so it can |
|
|
|
|
|
* construct a new, valid link to use for verifying the email address. |
|
|
|
|
|
*/ |
|
|
|
|
|
const templateModel = { |
|
|
|
|
|
site: this.dtp.config.site, |
|
|
|
|
|
recipient: user, |
|
|
|
|
|
emailVerifyToken: verifyToken.token, |
|
|
|
|
|
}; |
|
|
|
|
|
const message = { |
|
|
|
|
|
from: process.env.DTP_EMAIL_SMTP_FROM, |
|
|
|
|
|
to: user.email, |
|
|
|
|
|
subject: `Welcome to ${this.dtp.config.site.name}!`, |
|
|
|
|
|
html: await emailService.renderTemplate('welcome', 'html', templateModel), |
|
|
|
|
|
text: await emailService.renderTemplate('welcome', 'text', templateModel), |
|
|
|
|
|
}; |
|
|
|
|
|
await emailService.send(message); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async setEmailVerification (user, isVerified) { |
|
|
|
|
|
await User.updateOne( |
|
|
|
|
|
{ _id: user._id }, |
|
|
|
|
|
{ |
|
|
|
|
|
$set: { 'flags.isEmailVerified': isVerified }, |
|
|
|
|
|
}, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async emailOptOut (userId, category) { |
|
|
|
|
|
userId = mongoose.Types.ObjectId(userId); |
|
|
|
|
|
const user = await this.getUserAccount(userId); |
|
|
|
|
|
if (!user) { |
|
|
|
|
|
throw new SiteError(406, 'Invalid opt-out token'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const updateOp = { $set: { } }; |
|
|
|
|
|
switch (category) { |
|
|
|
|
|
case 'marketing': |
|
|
|
|
|
updateOp.$set['optIn.marketing'] = false; |
|
|
|
|
|
break; |
|
|
|
|
|
case 'system': |
|
|
|
|
|
updateOp.$set['optIn.system'] = false; |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
throw new SiteError(406, 'Invalid opt-out category'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await User.updateOne({ _id: userId }, updateOp); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
async update (user, userDefinition) { |
|
|
async update (user, userDefinition) { |
|
|
if (!user.flags.canLogin) { |
|
|
if (!user.flags.canLogin) { |
|
|
throw SiteError(403, 'Invalid user account operation'); |
|
|
throw SiteError(403, 'Invalid user account operation'); |
|
@ -123,9 +197,6 @@ class UserService { |
|
|
const username_lc = userDefinition.username.toLowerCase(); |
|
|
const username_lc = userDefinition.username.toLowerCase(); |
|
|
|
|
|
|
|
|
userDefinition.displayName = striptags(userDefinition.displayName.trim()); |
|
|
userDefinition.displayName = striptags(userDefinition.displayName.trim()); |
|
|
if (userDefinition.bio) { |
|
|
|
|
|
userDefinition.bio = striptags(userDefinition.bio.trim()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.log.info('updating user', { userDefinition }); |
|
|
this.log.info('updating user', { userDefinition }); |
|
|
await User.updateOne( |
|
|
await User.updateOne( |
|
@ -135,7 +206,8 @@ class UserService { |
|
|
username: userDefinition.username, |
|
|
username: userDefinition.username, |
|
|
username_lc, |
|
|
username_lc, |
|
|
displayName: userDefinition.displayName, |
|
|
displayName: userDefinition.displayName, |
|
|
bio: userDefinition.bio, |
|
|
'optIn.system': userDefinition['optIn.system'] === 'on', |
|
|
|
|
|
'optIn.marketing': userDefinition['optIn.marketing'] === 'on', |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
@ -147,9 +219,6 @@ class UserService { |
|
|
const username_lc = userDefinition.username.toLowerCase(); |
|
|
const username_lc = userDefinition.username.toLowerCase(); |
|
|
|
|
|
|
|
|
userDefinition.displayName = striptags(userDefinition.displayName.trim()); |
|
|
userDefinition.displayName = striptags(userDefinition.displayName.trim()); |
|
|
if (userDefinition.bio) { |
|
|
|
|
|
userDefinition.bio = striptags(userDefinition.bio.trim()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.log.info('updating user for admin', { userDefinition }); |
|
|
this.log.info('updating user for admin', { userDefinition }); |
|
|
await User.updateOne( |
|
|
await User.updateOne( |
|
@ -159,15 +228,12 @@ class UserService { |
|
|
username: userDefinition.username, |
|
|
username: userDefinition.username, |
|
|
username_lc, |
|
|
username_lc, |
|
|
displayName: userDefinition.displayName, |
|
|
displayName: userDefinition.displayName, |
|
|
bio: userDefinition.bio, |
|
|
|
|
|
'flags.isAdmin': userDefinition.isAdmin === 'on', |
|
|
'flags.isAdmin': userDefinition.isAdmin === 'on', |
|
|
'flags.isModerator': userDefinition.isModerator === 'on', |
|
|
'flags.isModerator': userDefinition.isModerator === 'on', |
|
|
'permissions.canLogin': userDefinition.canLogin === 'on', |
|
|
'permissions.canLogin': userDefinition.canLogin === 'on', |
|
|
'permissions.canChat': userDefinition.canChat === 'on', |
|
|
'permissions.canChat': userDefinition.canChat === 'on', |
|
|
'permissions.canComment': userDefinition.canComment === 'on', |
|
|
'permissions.canComment': userDefinition.canComment === 'on', |
|
|
'permissions.canReport': userDefinition.canReport === 'on', |
|
|
'permissions.canReport': userDefinition.canReport === 'on', |
|
|
'permissions.canAuthorPages': userDefinition.canAuthorPages === 'on', |
|
|
|
|
|
'permissions.canAuthorPosts': userDefinition.canAuthorPosts === 'on', |
|
|
|
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
@ -179,7 +245,6 @@ class UserService { |
|
|
const username_lc = userDefinition.username.toLowerCase(); |
|
|
const username_lc = userDefinition.username.toLowerCase(); |
|
|
|
|
|
|
|
|
userDefinition.displayName = striptags(userDefinition.displayName.trim()); |
|
|
userDefinition.displayName = striptags(userDefinition.displayName.trim()); |
|
|
userDefinition.bio = striptags(userDefinition.bio.trim()); |
|
|
|
|
|
|
|
|
|
|
|
this.log.info('updating user settings', { userDefinition }); |
|
|
this.log.info('updating user settings', { userDefinition }); |
|
|
await User.updateOne( |
|
|
await User.updateOne( |
|
@ -189,7 +254,6 @@ class UserService { |
|
|
username: userDefinition.username, |
|
|
username: userDefinition.username, |
|
|
username_lc, |
|
|
username_lc, |
|
|
displayName: userDefinition.displayName, |
|
|
displayName: userDefinition.displayName, |
|
|
bio: userDefinition.bio, |
|
|
|
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
@ -354,7 +418,7 @@ class UserService { |
|
|
username = username.trim().toLowerCase(); |
|
|
username = username.trim().toLowerCase(); |
|
|
const user = await User |
|
|
const user = await User |
|
|
.findOne({ username_lc: username }) |
|
|
.findOne({ username_lc: username }) |
|
|
.select('_id created username username_lc displayName bio picture header') |
|
|
.select('_id created username username_lc displayName picture header') |
|
|
.populate(this.populateUser) |
|
|
.populate(this.populateUser) |
|
|
.lean(); |
|
|
.lean(); |
|
|
return user; |
|
|
return user; |
|
@ -363,7 +427,7 @@ class UserService { |
|
|
async getRecent (maxCount = 3) { |
|
|
async getRecent (maxCount = 3) { |
|
|
const users = User |
|
|
const users = User |
|
|
.find() |
|
|
.find() |
|
|
.select('_id created username username_lc displayName bio picture') |
|
|
.select('_id created username username_lc displayName picture') |
|
|
.sort({ created: -1 }) |
|
|
.sort({ created: -1 }) |
|
|
.limit(maxCount) |
|
|
.limit(maxCount) |
|
|
.lean(); |
|
|
.lean(); |
|
@ -447,28 +511,7 @@ class UserService { |
|
|
if (!username || (typeof username !== 'string') || (username.length === 0)) { |
|
|
if (!username || (typeof username !== 'string') || (username.length === 0)) { |
|
|
throw new SiteError(406, 'Invalid username'); |
|
|
throw new SiteError(406, 'Invalid username'); |
|
|
} |
|
|
} |
|
|
const reservedNames = [ |
|
|
if (this.reservedNames.includes(username.trim().toLowerCase())) { |
|
|
'about', |
|
|
|
|
|
'admin', |
|
|
|
|
|
'auth', |
|
|
|
|
|
'digitaltelepresence', |
|
|
|
|
|
'dist', |
|
|
|
|
|
'dtp', |
|
|
|
|
|
'fontawesome', |
|
|
|
|
|
'fonts', |
|
|
|
|
|
'img', |
|
|
|
|
|
'image', |
|
|
|
|
|
'less', |
|
|
|
|
|
'manifest.json', |
|
|
|
|
|
'moment', |
|
|
|
|
|
'newsletter', |
|
|
|
|
|
'numeral', |
|
|
|
|
|
'socket.io', |
|
|
|
|
|
'uikit', |
|
|
|
|
|
'user', |
|
|
|
|
|
'welcome', |
|
|
|
|
|
]; |
|
|
|
|
|
if (reservedNames.includes(username.trim().toLowerCase())) { |
|
|
|
|
|
throw new SiteError(403, 'That username is reserved for system use'); |
|
|
throw new SiteError(403, 'That username is reserved for system use'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|