OurBigBook logoOurBigBook Docs OurBigBook logoOurBigBook.comSite Source code
web/api/users.js
const url = require('url');

const { htmlEscapeAttr } = require('ourbigbook')

const { hashToHex, sendJsonHttp } = require('ourbigbook/web_api')

const axios = require('axios')
const router = require('express').Router()
const passport = require('passport')
const sharp = require('sharp')
const { ValidationError: SequelizeValidationError } = require('sequelize')

const auth = require('../auth')
const lib = require('./lib')
const {
  validateParam,
  ValidationError,
} = lib
const { cant } = require('../front/cant')
const front = require('../front/js')
const { ipBlockedForSignupMessage } = front
const config = require('../front/config')
const routes = require('../front/routes')
const { isIpBlockedForSignup } = require('../back/webpack_safe')

async function authenticate(req, res, next, opts={}) {
  const { forceVerify } = opts
  passport.authenticate('local', { session: false }, async function(err, user, info) {
    if (err) {
      return next(err)
    }
    if (user) {
      if (user.verified || forceVerify) {
        user.token = user.generateJWT()
        verified = true
      } else {
        verified = false
      }
      return res.json({
        user: await user.toJson(user),
        verified,
      })
    } else {
      return res.status(422).json(info)
    }
  })(req, res, next)
}

// Preload user profile on routes with ':username'
router.param('username', function(req, res, next, username) {
  req.app.get('sequelize').models.User.findOne({ where: { username } })
    .then(function(user) {
      if (!user) {
        return res.sendStatus(404)
      }
      req.user = user
      return next()
    })
    .catch(next)
})

async function unlistUserContent(sequelize, user, transaction) {
  const { Article, Comment, Issue, Upload, User } = sequelize.models
  const counts = []
  for (const Model of [Article, Issue, Comment]) {
    const [count] = await Model.update(
      { list: false },
      { where: { authorId: user.id, list: true }, transaction },
    )
    counts.push(count)
  }
  const [uploadCount] = await Upload.update(
    { list: false },
    { where: { ...Upload.fileIndexWhere(user.id), list: true }, transaction },
  )
  counts.push(uploadCount)
  await User.update(
    { discussionCount: 0, commentCount: 0 },
    { where: { id: user.id }, transaction },
  )
  return counts
}

// Login to the website.
router.post('/login', async function(req, res, next) {
  try {
    const body = validateParam(req, 'body')
    const user = validateParam(body, 'user')
    const username = validateParam(user, 'username')
    const password = validateParam(user, 'password')
    await authenticate(req, res, next)
  } catch(error) {
    next(error);
  }
})

router.post('/reset-password-request', async function(req, res, next) {
  try {
    const body = validateParam(req, 'body')
    const emailOrUsername = validateParam(body,
      'emailOrUsername',
      { validators: [front.isString, front.isTruthy] }
    )
    await validateCaptcha(config, req, res)
    const sequelize = req.app.get('sequelize')
    const where = {}
    if (front.isEmail(emailOrUsername)) {
      where.email = emailOrUsername
    } else {
      where.username = emailOrUsername
    }
    const User = sequelize.models.User
    const user = await User.findOne({ where })
    if (!user)
      throw new ValidationError([`email or username is not registered: ${emailOrUsername}`])
    if (!user.verified)
      throw new ValidationError([`user is not verified, you must verify your account before you can reset your password`])
    const timeToWaitMs = getTimeToWaitForNextEmailMs(user)
    if (timeToWaitMs > 0) {
      throw new ValidationError([`Email already registered but not verified. You can re-send a confirmation email in: ${lib.msToRoundedTime(timeToWaitMs)}`])
    }
    const verificationCode = User.generateVerificationCode()
    const resetPasswordUrl = `${routes.host(req)}${routes.resetPasswordUpdate()}?email=${encodeURIComponent(user.email)}&code=${verificationCode}`
    user.verificationCode = verificationCode
    user.verificationCodeN += 1
    user.verificationCodeSent = new Date()
    await Promise.all([
      user.saveSideEffects(),
      lib.sendEmail({
        req,
        to: user.email,
        subject: `Change your OurBigBook.com password`,
        html:
          `<p>Hello, ${user.displayName}!</p>` +
          `<p>Someone (hopefully you) requested a password reset for your account.</p>` +
          `<p>If that was you, please <a href="${resetPasswordUrl}">click this link to change your password</a>.</p>` +
          `<p>If it wasn't, you can safely ignore this email.</p>`
        ,
        text: `Hello, ${user.displayName}!

Someone (hopefully you) requested a password reset for your account.

If that was you, please click this link to change your password: ${resetPasswordUrl}

If it wasn't, you can safely ignore this email.
`,
      })
    ])
    res.sendStatus(200)
  } catch(error) {
    next(error);
  }
})

router.post('/reset-password', async function(req, res, next) {
  try {
    const body = validateParam(req, 'body')
    const email = validateParam(body,
      'email',
      { validators: [front.isString, front.isTruthy] }
    )
    const password = validateParam(body,
      'password',
      { validators: [front.isString, front.isTruthy] }
    )
    const code = validateParam(body,
      'code',
      { validators: [front.isString, front.isTruthy] }
    )
    const sequelize = req.app.get('sequelize')
    const User = sequelize.models.User
    const user = await User.findOne({ where: { email } })
    if (!user)
      throw new ValidationError([`email not registered: ${email}`])
    if (code === user.verificationCode) {
      res.sendStatus(200)
      user.verificationCode = null
      user.verificationCodeN = 0
      User.setPassword(user, password)
      await user.saveSideEffects()
    } else {
      throw new ValidationError(['verification code invalid. Please send a new one.'])
    }
  } catch(error) {
    next(error);
  }
})

router.get('/users', auth.optional, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const { User } = sequelize.models
    const [limit, offset] = lib.getLimitAndOffset(req, res)
    const [loggedInUser, {count: usersCount, rows: users}] = await Promise.all([
      req.payload ? User.findByPk(req.payload.id) : null,
      User.getUsers({
        // https://github.com/ourbigbook/ourbigbook/issues/260
        followedBy: req.query.followedBy,
        following: req.query.following,
        limit,
        offset,
        order: lib.getOrder(req, {
          allowedSorts: User.ALLOWED_SORTS,
          allowedSortsExtra: User.ALLOWED_SORTS_EXTRA,
        }),
        sequelize,
        username: req.query.username,
      }),
    ])
    return res.json({
      users: await Promise.all(users.map((user) => {
        return user.toJson(loggedInUser)
      })),
      usersCount,
    })
  } catch(error) {
    next(error);
  }
})

async function validateCaptcha(config, req, res) {
  if (config.useCaptcha) {
    const {data, status} = await sendJsonHttp(
      'POST',
      '/recaptcha/api/siteverify',
      {
        contentType: 'application/x-www-form-urlencoded',
        https: true,
        hostname: 'www.google.com',
        validateStatus: () => true,
        body: new url.URLSearchParams({
          secret: process.env.RECAPTCHA_SECRET_KEY,
          response: req.body.recaptchaToken,
        }).toString(),
      }
    )
    if (status !== 200) {
      return res.sendStatus(503)
    }
    if (!data.success) {
      console.error(`recaptcha error: ${data}`);
      throw new ValidationError(['reCAPTCHA failed'])
    }
  }
}

function getTimeToWaitForNextEmailMs(user) {
  const next = user.nextVerificationEmailAt()
  return next ? next.getTime() - Date.now() : -1
}

// Create a new user.
router.post('/users', async function(req, res, next) {
  try {
    const body = validateParam(req, 'body')
    const userPost = validateParam(body, 'user')
    const username = validateParam(userPost, 'username')
    const email = validateParam(userPost, 'email')
    const password = validateParam(userPost, 'password')
    const displayName = validateParam(userPost, 'displayName', {
      validators: [front.isString, front.isTruthy],
      defaultValue: undefined,
    })
    const sequelize = req.app.get('sequelize')
    const { User } = sequelize.models
    let user
    const ip = front.getClientIp(req)
    await sequelize.transaction(async (transaction) => {
      const [ipBlockPrefix,] = await Promise.all([
        isIpBlockedForSignup(sequelize, ip),
        validateCaptcha(config, req, res),
      ])
      if (ipBlockPrefix) {
        throw new ValidationError([ipBlockedForSignupMessage(ip, ipBlockPrefix.ip)])
      }

      // Check if VPN and block if yes.
      if (
        ip &&
        config.ipapiIsApiKey
      ) {
        const response = await axios.get(`https://api.ipapi.is?q=${ip}&key=${config.ipapiIsApiKey}`)
        if (response.status === 200) {
          const data = response.data
          if (data.is_vpn) {
            console.log(`ipapi.js VPN detected: ${JSON.stringify(data)}`)
            throw new ValidationError([
              `Your IP ${ip} is from a VPN according to https://ipapi.is ` +
              `which is not allowed due to past abuse. Please try again ` +
              `from another network, or contact a site admin to create the account for you.`
            ])
          }
        } else {
          console.log('ipapi error')
          console.log(response.data)
        }
      }

      // We fetch the existing account by email.
      // https://github.com/ourbigbook/ourbigbook/issues/329
      const existingUser = await User.findOne({ where: { email }, transaction})
      let adminsPromise
      if (existingUser) {
        user = existingUser
        if (user.verified) {
          throw new ValidationError([`email already taken: ${email}`])
        }
        // Re-send the email if enough time passed.
        const timeToWaitMs = getTimeToWaitForNextEmailMs(user)
        if (timeToWaitMs > 0) {
          throw new ValidationError([`Email already registered but not verified. You can re-send a confirmation email in: ${lib.msToRoundedTime(timeToWaitMs)}`])
        }
        user.verificationCode = User.generateVerificationCode()
        user.verificationCodeN += 1
        adminsPromise = null
      } else {
        user = new (User)()
        // username is set only in this else.
        // https://github.com/ourbigbook/ourbigbook/issues/329
        user.username = username
        user.verificationCodeN = 1
        user.email = email
        user.ip = ip
        if (config.isTest) {
          // Authenticate all users automatically.
          user.verified = true
        }
        adminsPromise = User.findAll({ where: { admin: true }, transaction })
      }
      user.displayName = displayName
      User.setPassword(user, password)
      user.verificationCodeSent = new Date()
      const [, admins] = await Promise.all([
        user.saveSideEffects({ transaction }),
        adminsPromise,
      ])
      const verifyUrl = `${routes.host(req)}${routes.userVerify()}?email=${encodeURIComponent(user.email)}&code=${user.verificationCode}`
      const sendEmailsPromises = []
      sendEmailsPromises.push(lib.sendEmail({
        req,
        to: user.email,
        subject: `Verify your new OurBigBook.com account`,
        html:
          `<p>Welcome to OurBigBook.com, ${user.displayName}!</p>` +
          `<p>Please <a href="${verifyUrl}">click this link to verify your account</a>.</p>`
        ,
        text: `Welcome to OurBigBook.com, ${user.displayName}!

Please click this link to verify your account: ${verifyUrl}
`,
      }))
      const profileUrl = `${routes.host(req)}${routes.user(user.username)}`
      if (admins) {
        for (const admin of admins) {
          sendEmailsPromises.push(lib.sendEmail({
            req,
            to: admin.email,
            subject: `A new user signed up: ${user.displayName} (@${user.username}, ${user.email}, ${user.ip})!`,
            html: `<p><a href="${profileUrl}">${profileUrl}</a></p><p>Another step towards world domination is taken!</p>`,
            text: `${profileUrl}

Another step towards world domination is taken!
`,
          }))
        }
      }
      await Promise.all(sendEmailsPromises)
    })
    if (config.isTest) {
      // TODO get rid of this horror.
      return authenticate(req, res, next, { forceVerify: true })
    } else {
      return res.json({ user: await user.toJson(user) })
    }
  } catch(error) {
    next(error);
  }
})

async function validateEmailChange(User, user, email, transaction) {
  const candidate = User.build({ email })
  try {
    await candidate.validate({ fields: ['email'], hooks: false })
  } catch (error) {
    if (!(error instanceof SequelizeValidationError)) throw error
    throw new ValidationError({ email: error.errors.map(item => item.message) })
  }
  const existing = await User.findOne({ attributes: ['id'], where: { email: candidate.email }, transaction })
  if (existing && existing.id !== user.id) {
    throw new ValidationError({ email: 'This email is taken.' })
  }
  return candidate.email
}

async function prepareEmailChange(req, user, email, transaction) {
  if (!user.verified) throw new ValidationError({ email: 'Verify your current email before changing it.' })
  const User = user.sequelize.models.User
  email = await validateEmailChange(User, user, email, transaction)
  if (email === user.email) throw new ValidationError({ email: 'This is already your current email.' })
  const wait = getTimeToWaitForNextEmailMs(user)
  if (wait > 0) {
    throw new ValidationError({ email: `You can send a new email in ${lib.msToRoundedTime(wait)}` }, 429)
  }
  user.pendingEmail = email
  user.emailChangeCode = User.generateVerificationCode()
  user.verificationCodeN += 1
  user.verificationCodeSent = new Date()
  const verifyUrl = `${routes.host(req)}${routes.userVerify()}?emailChange=${encodeURIComponent(user.username)}&code=${user.emailChangeCode}`
  return {
    req,
    to: email,
    subject: 'Verify your new OurBigBook.com email address',
    html: `<p>Please <a href="${htmlEscapeAttr(verifyUrl)}">verify your new email address</a> for your OurBigBook account.</p><p>If you did not request this change, ignore this email.</p>`,
    text: `Verify your new email address for your OurBigBook account: ${verifyUrl}

If you did not request this change, ignore this email.`,
  }
}

router.post('/users/:username/email-change', auth.required, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const { User } = sequelize.models
    const loggedInUser = await User.findByPk(req.payload.id)
    if (cant.setUserEmail(loggedInUser, req.user)) throw new ValidationError(['You cannot change this email'], 403)
    let user
    await sequelize.transaction(async transaction => {
      user = await User.findByPk(req.user.id, { transaction, lock: transaction.LOCK.UPDATE })
      const email = validateParam(req.body, 'email', {
        defaultValue: user.pendingEmail,
        validators: [front.isString, front.isTruthy],
      })
      const mail = await prepareEmailChange(req, user, email, transaction)
      await user.saveSideEffects({ transaction })
      await lib.sendEmail(mail)
    })
    return res.json({ user: await user.toJson(loggedInUser) })
  } catch (error) {
    next(error)
  }
})

// Check availability only for accounts the caller can edit.
router.post('/users/:username/email-check', auth.required, async function(req, res, next) {
  res.set('Cache-Control', 'no-store')
  try {
    const { User } = req.app.get('sequelize').models
    const loggedInUser = await User.findByPk(req.payload.id)
    if (cant.setUserEmail(loggedInUser, req.user)) {
      throw new ValidationError(['You cannot check email availability for this user'], 403)
    }
    const email = validateParam(req.body, 'email', { validators: [front.isString, front.isTruthy] })
    await validateEmailChange(User, req.user, email)
    return res.json({ available: true })
  } catch (error) {
    next(error)
  }
})

// Modify information about the given user. Backend for the settings page.
router.put('/users/:username', auth.required, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const user = req.user
    const loggedInUser = await sequelize.models.User.findByPk(req.payload.id)
    const msg = cant.editUser(loggedInUser, user)
    if (msg) {
      throw new ValidationError([msg], 403)
    }
    await sequelize.transaction(async transaction => {
      await user.reload({ transaction, lock: transaction.LOCK.UPDATE })
      let mail
      const userArg = req.body.user
      if (userArg) {
        // only update fields that were actually passed...
        if (typeof userArg.username !== 'undefined') {
          //user.username = userArg.username
          if (user.username !== userArg.username) {
            throw new ValidationError(
              [`username cannot be modified currently, would change from ${user.username} to ${userArg.username}`],
            )
          }
        }
        if (typeof userArg.email !== 'undefined') {
          const email = validateParam(userArg, 'email', { validators: [front.isString, front.isTruthy] })
          if (user.email !== email.toLowerCase()) {
            if (loggedInUser.admin) {
              user.email = email
              user.pendingEmail = null
              user.emailChangeCode = null
              user.verificationCode = null
              user.verificationCodeN = 0
            } else {
              mail = await prepareEmailChange(req, user, email, transaction)
            }
          }
        }
        if (typeof userArg.displayName !== 'undefined') {
          const displayName = validateParam(userArg, 'displayName', {
            validators: [front.isString, front.isTruthy],
            defaultValue: undefined,
          })
          user.displayName = displayName
        }
        const emailNotifications = validateParam(userArg, 'emailNotifications', {
          validators: [front.isBoolean],
          defaultValue: undefined,
        })
        if (emailNotifications !== undefined) {
          user.emailNotifications = userArg.emailNotifications
        }
        const emailNotificationsForArticleAnnouncement = validateParam(userArg, 'emailNotificationsForArticleAnnouncement', {
          validators: [front.isBoolean],
          defaultValue: undefined,
        })
        if (emailNotificationsForArticleAnnouncement !== undefined) {
          user.emailNotificationsForArticleAnnouncement = userArg.emailNotificationsForArticleAnnouncement
        }
        const hideArticleDates = validateParam(userArg, 'hideArticleDates', {
          validators: [front.isBoolean],
          defaultValue: undefined,
        })
        if (hideArticleDates !== undefined) {
          user.hideArticleDates = userArg.hideArticleDates
        }

        // User limits
        {
          const msg = cant.setUserLimits(loggedInUser)
          function set(val, key) {
            if (val !== undefined) {
              if (msg) {
                throw new ValidationError([msg], 403)
              } else {
                user[key] = val
              }
            }
          }
          set(
            validateParam(userArg, 'maxArticles', {
              typecast: front.typecastInteger,
              validators: [front.isPositiveInteger],
              defaultValue: undefined,
            }),
            'maxArticles',
          )
          set(
            validateParam(userArg, 'maxArticleSize', {
              typecast: front.typecastInteger,
              validators: [front.isPositiveInteger],
              defaultValue: undefined,
            }),
            'maxArticleSize',
          )
          set(
            validateParam(userArg, 'maxUploads', {
              typecast: front.typecastInteger,
              validators: [front.isPositiveInteger],
              defaultValue: undefined,
            }),
            'maxUploads',
          )
          set(
            validateParam(userArg, 'maxUploadSize', {
              typecast: front.typecastInteger,
              validators: [front.isPositiveInteger],
              defaultValue: undefined,
            }),
            'maxUploadSize',
          )
          set(
            validateParam(userArg, 'maxIssuesPerMinute', {
              typecast: front.typecastInteger,
              validators: [front.isPositiveInteger],
              defaultValue: undefined,
            }),
            'maxIssuesPerMinute',
          )
          set(
            validateParam(userArg, 'maxIssuesPerHour', {
              typecast: front.typecastInteger,
              validators: [front.isPositiveInteger],
              defaultValue: undefined,
            }),
            'maxIssuesPerHour',
          )
          set(
            validateParam(userArg, 'locked', {
              validators: [front.isBoolean],
              defaultValue: undefined,
            }),
            'locked',
          )
        }

        if (typeof userArg.password !== 'undefined') {
          sequelize.models.User.setPassword(user, userArg.password)
        }
        await user.saveSideEffects({ transaction })
        if (mail) await lib.sendEmail(mail)
      }
    })
    user.token = user.generateJWT()
    return res.json({ user: await user.toJson(user) })
  } catch(error) {
    next(error);
  }
})

router.put('/users/:username/unlist-content', auth.required, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const ret = await sequelize.transaction(async transaction => {
      const loggedInUser = await sequelize.models.User.findByPk(req.payload.id, { transaction })
      const msg = cant.updateSiteSettings(loggedInUser)
      if (msg) {
        throw new ValidationError([msg], 403)
      }
      const counts = await unlistUserContent(sequelize, req.user, transaction)
      return { count: counts.reduce((sum, count) => sum + count, 0), counts }
    })
    res.json(ret)
  } catch(error) {
    next(error)
  }
})

router.put('/users/:username/spammer', auth.required, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const ret = await sequelize.transaction(async transaction => {
      const { SignupBlacklistIp, User } = sequelize.models
      const loggedInUser = await User.findByPk(req.payload.id, { transaction })
      const msg = cant.updateSiteSettings(loggedInUser)
      if (msg) {
        throw new ValidationError([msg], 403)
      }
      const counts = await unlistUserContent(sequelize, req.user, transaction)
      await req.user.update({ locked: true }, { transaction })
      if (req.user.ip) {
        await SignupBlacklistIp.bulkCreate(
          [{ ip: req.user.ip }],
          { transaction, updateOnDuplicate: ['ip'] },
        )
      }
      return { count: counts.reduce((sum, count) => sum + count, 0), counts }
    })
    res.json(ret)
  } catch(error) {
    next(error)
  }
})

// Modify image of the currently logged in user.
// Backend for settings page on the web UI.
router.put('/users/:username/profile-picture', auth.required, async function(req, res, next) {
  try {
    let t0
    if (config.log.perf) {
      t0 = performance.now()
    }
    const sequelize = req.app.get('sequelize')
    const user = req.user
    const { Upload, User } = sequelize.models
    const loggedInUser = await User.findByPk(req.payload.id)
    const msg = cant.editUser(loggedInUser, user)
    if (msg) {
      throw new ValidationError([msg], 403)
    }
    const body = validateParam(req, 'body')
    const dataUrl = validateParam(body, 'bytes', { validators: [front.isString] })
    const [contentType, bytesOrig] = lib.parseDataUriBase64(dataUrl)
    if (!config.allowedImageContentTypes.has(contentType)) {
      throw new ValidationError([
        `content type is not allowed: "${contentType}". ` +
        `Allowed content types: ${config.allowedImageContentTypesArr.join(', ')}`
      ], 422)
    }
    const sizeOrig = bytesOrig.length
    if (sizeOrig > config.profilePictureMaxUploadSize) {
      throw new ValidationError([
        `image is too large: ${sizeOrig} bytes, maximum size is ${config.profilePictureMaxUploadSize}`
      ], 422)
    }
    let bytes
    try {
      bytes = await sharp(bytesOrig).resize(250, 250, { fit: 'fill' }).toBuffer()
    } catch(err) {
      throw new ValidationError(`Image conversion failed with: ${err.message}`)
    }
    user.image = `${config.profilePicturePath}/${user.id}`
    t0 = lib.logPerf(t0, 'PUT /users/:username/profile-picture before transaction')
    await sequelize.transaction(async (transaction) => {
      await Upload.upsertSideEffects(
        {
          bytes,
          contentType,
          path: `${config.profilePicturePathComponent}/${user.id}`,
          size: bytes.length,
          hash: hashToHex(bytes),
        },
        { transaction }
      )
      await user.saveSideEffects({ transaction })
    })
    t0 = lib.logPerf(t0, 'PUT /users/:username/profile-picture after transaction')
    return res.json({
      image: user.image,
      imageDataUrl: `data:${contentType};base64,${bytes.toString('base64')}`,
    })
  } catch(error) {
    next(error);
  }
})

// Follow

async function validateFollow(user, loggedInUser, isFollow) {
  if ((await loggedInUser.hasFollow(user)) === isFollow) {
    throw new ValidationError(
      [`User '${loggedInUser.username}' ${isFollow ? 'already follows' : 'does not follow'} user '${user.username}'`],
      403,
    )
  }
}

// Follow user.
router.post('/users/:username/follow', auth.required, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const { User } = sequelize.models
    const loggedInUser = await User.findByPk(req.payload.id)
    const user = req.user
    const msg = cant.followUser(loggedInUser, user)
    if (msg) {
      throw new ValidationError([msg], 403)
    }
    await validateFollow(user, loggedInUser, true)
    await loggedInUser.addFollowSideEffects(user)
    const newUser = await User.findOne({ where: { username: loggedInUser.username } })
    return res.json({ user: await user.toJson(newUser) })
  } catch(error) {
    next(error)
  }
})

// Unfollow user.
router.delete('/users/:username/follow', auth.required, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const { User } = sequelize.models
    const loggedInUser = await User.findByPk(req.payload.id)
    const user = req.user
    const msg = cant.followUser(loggedInUser, user)
    if (msg) {
      throw new ValidationError([msg], 403)
    }
    await validateFollow(user, loggedInUser, false)
    await loggedInUser.removeFollowSideEffects(req.user)
    const newUser = await User.findOne({ where: { username: user.username } })
    return res.json({ user: await req.user.toJson(newUser) })
  } catch(error) {
    next(error)
  }
})

module.exports = router