'use strict' const mw = require('../middleware.js') const sanitize = require('mongo-sanitize') const User = require('../models.js').user const mail = require('../mail.js') const env = require('../env/env.js') const zxcvbn = require('zxcvbn') const moment = require('moment') const debug = require('debug')('tracman-routes-account') const router = require('express').Router() // Confirm email address router.get('/email/:token', mw.ensureAuth, async (req, res, next) => { // Check token if (req.user.emailToken === req.params.token) { try { // Set new email req.user.email = req.user.newEmail // Delete token and newEmail req.user.emailToken = undefined req.user.newEmail = undefined await req.user.save() // Report success req.flash('success', `Your email has been set to ${req.user.email}. `) res.redirect('/settings') } catch (err) { mw.throwErr(err, req) res.redirect('/settings') } // Invalid token } else { req.flash('danger', 'Email confirmation token is invalid. ') res.redirect('/settings') } }) // Set password router.route('/password') .all(mw.ensureAuth, (req, res, next) => { next() }) // Email user a token, proceed at /password/:token .get( async (req, res, next) => { // Create token for password change try { let [token, expires] = await req.user.createPassToken() // Figure out expiration time let expirationTimeString = (req.query.tz) ? moment(expires).utcOffset(req.query.tz).toDate().toLocaleTimeString(req.acceptsLanguages[0]) : moment(expires).toDate().toLocaleTimeString(req.acceptsLanguages[0]) + ' UTC' // Alert user to check email. req.flash('success', `An link has been sent to ${req.user.email}. \ Click on the link to complete your password change. \ This link will expire in one hour (${expirationTimeString}). ` ) // Confirm password change request by email. return await mail.send({ to: mail.to(req.user), from: mail.noReply, subject: 'Request to change your Tracman password', text: mail.text( `A request has been made to change your tracman password. \ If you did not initiate this request, please contact support at keith@tracman.org. \ \n\nTo change your password, follow this link:\n\ ${env.url}/account/password/${token}. \n\n\ This request will expire at ${expirationTimeString}. ` ), html: mail.html( `

A request has been made to change your tracman password. \ If you did not initiate this request, please contact support at \ keith@tracman.org.

\

To change your password, follow this link:\
\ ${env.url}/account/password/${token}.

\

This request will expire at ${expirationTimeString}.

` ) }) } catch (err) { mw.throwErr(err, req) } finally { res.redirect((req.user) ? '/settings' : '/login') } }) router.route('/password/:token') // Check token .all( async (req, res, next) => { debug('/account/password/:token .all() called') try { let user = await User .findOne({'auth.passToken': sanitize(req.params.token)}) .where('auth.passTokenExpires').gt(Date.now()) if (!user) { debug('Bad token') req.flash('danger', 'Password reset token is invalid or has expired. ') res.redirect((req.isAuthenticated) ? '/settings' : '/login') } else { debug('setting passwordUser') res.locals.passwordUser = user next() } } catch (err) { mw.throwErr(err, req) res.redirect('/password') } }) // Show password change form .get((req, res) => { debug('/account/password/:token .get() called') res.render('password') }) // Set new password .post( async (req, res, next) => { debug('/account/password/:token .post() called') // Validate password strength let zxcvbnResult = zxcvbn(req.body.password) if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days req.flash( 'danger', `That password could be cracked in ${zxcvbnResult.crack_times_display.online_no_throttling_10_per_second}! Come up with a more complex password that would take at least 10 days to crack. ` ) res.redirect(`/account/password/${req.params.token}`) } else { // Create hashed password and save to db try { await res.locals.passwordUser.generateHashedPassword(req.body.password) // User changed password if (req.user) { debug('User saved password') req.flash('success', 'Your password has been changed. ') res.redirect('/settings') // New user created password } else { debug('New user created password') req.flash('success', 'Password set. You can use it to log in now. ') res.redirect('/login') } } catch (err) { debug('Error creating hashed password and saving to db') mw.throwErr(err, req) res.redirect(`/account/password/${req.params.token}`) } } }) module.exports = router