Files
deighton-ar/server/src/api/routes/UserRoutes.js
2018-06-27 22:34:21 -07:00

253 lines
6.4 KiB
JavaScript

import passport from "passport"
import createError from "http-errors"
import crypto from "crypto"
import urlSafeBase64 from "urlsafe-base64"
import url from "url"
import util from "util"
import autobind from "autobind-decorator"
import config from "config"
import { catchAll } from "."
@autobind
export class UserRoutes {
constructor(container) {
const app = container.app
this.log = container.log
this.db = container.db
this.mq = container.mq
this.ws = container.ws
this.maxEmailTokenAgeInHours = config.get("email.maxEmailTokenAgeInHours")
this.sendEmail = config.get("email.sendEmail")
this.emailServiceName = config.get("serviceName.email")
app
.route("/users")
.get(
passport.authenticate("bearer", { session: false }),
catchAll(this.listUsers)
)
// Add a new user, send email confirmation email
.post(
passport.authenticate("bearer", { session: false }),
catchAll(this.createUser)
)
.put(
passport.authenticate("bearer", { session: false }),
catchAll(this.updateUser)
)
app
.route("/users/:_id([a-f0-9]{24})")
.get(
passport.authenticate("bearer", { session: false }),
catchAll(this.getUser)
)
.delete(
passport.authenticate("bearer", { session: false }),
catchAll(this.deleteUser)
)
app
.route("/users/enter-room/:roomName")
.put(
passport.authenticate("bearer", { session: false }),
catchAll(this.enterRoom)
)
app
.route("/users/leave-room")
.put(
passport.authenticate("bearer", { session: false }),
catchAll(this.leaveRoom)
)
}
async listUsers(req, res, next) {
const User = this.db.User
const limit = req.query.limit || 20
const skip = req.query.skip || 0
const partial = !!req.query.partial
const isAdmin = !!req.user.administrator
const team = req.query.team
let query = {}
if (team) {
query = { team }
}
if (!isAdmin) {
throw createError.Forbidden()
}
const total = await User.count({})
let users = []
let cursor = User.find(query)
.limit(limit)
.skip(skip)
.cursor()
.map((doc) => {
return doc.toClient(req.user)
})
cursor.on("data", (doc) => {
users.push(doc)
})
cursor.on("end", () => {
res.json({
total: total,
offset: skip,
count: users.length,
items: users,
})
})
cursor.on("error", (err) => {
throw err
})
}
async getUser(req, res, next) {
let User = this.db.User
const _id = req.params._id
const isSelf = _id === req.user._id
const isAdmin = req.user.administrator
// User can see themselves, otherwise must be admin
if (!isSelf && !isAdmin) {
throw createError.Forbidden()
}
const user = await User.findById(_id)
if (!user) {
return Promise.reject(
createError.NotFound(`User with _id ${_id} was not found`)
)
}
res.json(user.toClient(req.user))
}
async createUser(req, res, next) {
const isAdmin = req.user.administrator
if (!isAdmin) {
throw new createError.Forbidden()
}
let User = this.db.User
let user = new User(req.body)
// Add email confirmation required token
const buf = await util.promisify(crypto.randomBytes)(32)
user.emailToken = {
value: urlSafeBase64.encode(buf),
created: new Date(),
}
const savedUser = await user.save()
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
const siteUrl = url.parse(req.headers.referer)
const msg = {
toEmail: savedUser.email,
templateName: "welcome",
templateData: {
recipientFullName: userFullName,
senderFullName: senderFullName,
confirmEmailLink: `${siteUrl.protocol}//${
siteUrl.host
}/confirm-email?email-token%3D${savedUser.emailToken.value}`,
confirmEmailLinkExpirationHours: this.maxEmailTokenAgeInHours,
supportEmail: this.supportEmail,
},
}
res.json(savedUser.toClient())
if (this.sendEmail) {
await this.mq.request(this.emailServiceName, "sendEmail", msg)
}
}
async updateUser(req, res, next) {
const isAdmin = req.user.administrator
// Do this here because Mongoose will add it automatically otherwise
if (!req.body._id) {
throw createError.BadRequest("No user _id given in body")
}
const isSelf = req.body._id === req.user._id.toString()
// User can change themselves, otherwise must be super user
if (!isSelf && !isAdmin) {
throw createError.Forbidden()
}
if (isSelf && isAdmin && !req.body.administrator) {
throw createError.BadRequest("Cannot remove own administrator level")
}
const User = this.db.User
const user = await User.findById(req.body._id)
if (!user) {
throw createError.NotFound(`User with _id ${req.body._id} was not found`)
}
// We don't allow direct updates to the email field so remove it if present
const userUpdates = new User(req.body)
userUpdates.email = undefined
user.merge(userUpdates)
const savedUser = await user.save()
res.json(savedUser.toClient(req.user))
}
enterRoom(req, res, next) {
this.ws.enterRoom(req.user._id, req.params.roomName)
res.json({})
}
leaveRoom(req, res, next) {
this.ws.enterRoom(req.user._id)
res.json({})
}
async deleteUser(req, res, next) {
const isAdmin = req.user.administrator
if (!isAdmin) {
throw createError.Forbidden()
}
let User = this.db.User
const _id = req.params._id
const deletedUser = await User.remove({ _id })
if (!deletedUser) {
throw createError.NotFound(`User with _id ${_id} was not found`)
}
const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}`
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
const msg = {
toEmail: deletedUser.newEmail,
templateName: "accountDeleted",
templateData: {
recipientFullName: userFullName,
senderFullName: senderFullName,
supportEmail: this.supportEmail,
},
}
res.json({})
if (this.sendEmail) {
await this.mq.request(this.emailServiceName, "sendEmail", msg)
}
}
}