Source: Backend/routes/accounts.mjs

// jshint esversion: 6
/* global ml */
import express from "express";
import Sequelize from 'sequelize';
import User from '../models/user.mjs';
import Lobby from '../models/lobby.mjs';
import multer from 'multer';
import fs from 'fs';
import qs from "querystring";
import nodemailer from 'nodemailer';
import crypto from 'crypto';

const DATA_DIR = process.env.PUBLIC_DIR || "Frontend/public";

var storage = multer.diskStorage({
  destination: `${DATA_DIR}/images`,
  filename: function(req, file, cb) {
    let date_posted = Date.now().toString().concat('-');
    cb(null, date_posted.concat(file.originalname));
  }
});
var upload = multer({ storage: storage });

const accountRouter = express.Router();

accountRouter.use((req, res, next) => {
  res.set("Cache-Control", "no-cache");
  next();
});

/**
 * Checks to make sure the user is authenticated.
 * @param req
 * @param res
 * @param next
 */
function isAuthenticated(req, res, next) {
  res.loginRedirect();

  return next();
}

/**
 * DEFAULT
 */
accountRouter.get('/', function(req, res) {
  res.redirect('/'); //Redirects to the homepage.
});

/**
 * CREATE ACCOUNT GET/POST
 */
accountRouter.get('/create', function(req, res) {
  if(req.session.authenticated) {
    res.redirect('dashboard');
  } else {
    res.render('create_acct', {
      returnUrl: req.query.returnUrl ?
        `?returnUrl=${qs.escape(req.query.returnUrl || "")}` :
        ""
    });
  }
});

accountRouter.post('/create', upload.fields([{ name: 'avatar', maxCount: 1}]), async(req, res) => {

  let image_name = null;
  try {
    image_name = req.files.avatar[0].filename;
  } catch(e) {
    image_name = null;
  }

  let username = req.body.username;
  let email = req.body.email;

  User.sync().then(() => {
    User.findOne({
      where: {
        [Sequelize.Op.or]: [{username: req.body.username}, {email: req.body.email}]
      }
    }).then(function(user) {
      if(user) {
        res.render('create_acct', {
          isAuthenticated: true,
          returnUrl: req.query.returnUrl ?
            `?returnUrl=${qs.escape(req.query.returnUrl || "")}` :
            ""
        });
      } else {
        ml.logger.info(`Created user ${username}`, ml.tags.account);
        User.encryptPassword(req.body.password, (err, hash) => {
          User.create({
            username: username,
            email: email,
            password: hash,
            image_name: image_name
          });
          res.loginRedirect(req.query.returnUrl || "");
        });
      }
    });
  });
});

accountRouter.get('/edit', function(req, res) {
  if(res.loginRedirect()) {
    return;
  }

  res.render('edit_acct', {
    username: req.session.username,
    image: req.user.image_name || "../../img/profilepic.jpg"
  });
});

// I will refactor this in the future
accountRouter.post('/edit', upload.fields([{ name: 'avatar', maxCount: 1}]), function(req, res) { // eslint-disable-line
  if(res.loginRedirect()) {
    return;
  }

  var argument, file_name;
  var changing_avatar = false;
  try {
    file_name = req.files.avatar[0].filename;
    argument = req.body.email || req.body.password || req.files.avatar[0].filename;
    changing_avatar = true;
  } catch (e) {
    argument = req.body.email || req.body.password;
  }

  if (argument && req.session.username !== undefined) {
    User.encryptPassword(req.body.password, (err, hash) => {
      let values = {};
      if(req.body.email) {
        values.email = req.body.email;
        ml.logger.verbose(`Updating email for ${req.session.username} to ${req.body.email}`, ml.tags.account);
      }
      if(req.body.password) {
        values.password = hash;
        ml.logger.verbose(`Updating password for ${req.session.username}`, ml.tags.account);
      }
      if(changing_avatar === true) {
        values.image_name = file_name;
        let file_to_delete = `${DATA_DIR}/images/${req.user.image_name}`;
        fs.unlink(file_to_delete, () => {});
        ml.logger.verbose(`Update profie pic for ${req.session.username}`, ml.tags.account);
      }
      let selector = {
        where: { username: req.session.username }
      };
      ml.logger.info(`Updated account ${req.session.username}`, ml.tags.account);
      User.update(values, selector).then(function(result) {
        if(result) {
          res.redirect('dashboard');
        } else {
          res.redirect('edit_acct');
        }
      });
    });
  }
});


/**
 * LOGOUT
 */
accountRouter.get('/logout', isAuthenticated, function(req, res) {
  ml.logger.info(`${req.session && req.session.username} logged out`, ml.tags.account);
  req.session.destroy(function(err) {
    if(!err) {
      res.redirect('login');
    }
  });
});

/**
 * LOGIN GET/POST
 */
accountRouter.get('/login', function(req, res) {
  if(req.session.authenticated) {
    if(req.query.returnUrl) {
      res.redirect(req.query.returnUrl);
    } else {
      res.redirect('dashboard');
    }
  } else {
    res.render('login', {
      returnUrl: req.query.returnUrl ?
        `?returnUrl=${qs.escape(req.query.returnUrl || "")}` :
        ""
    });
  }
});

accountRouter.post('/login', function(req, res) {
  User.findOne({
    where: {
      username: req.body.username
    }
  }).then(function(user) {
    if(user) {
      User.comparePassword(req.body.password, user.password, (err, result) => {
        if(result) {
          ml.logger.info(`${req.body.username} logged in`, ml.tags.account);
          req.session.authenticated = true;
          req.session.username = user.username;
          req.session.userId = user.id;
          if(req.query.returnUrl) {
            res.redirect(req.query.returnUrl);
          } else {
            res.redirect('dashboard');
          }
        } else {
          ml.logger.verbose(`${req.body.username} failed to login because of invalid password`, ml.tags.account);
          res.render('login', {
            wrongPassword: true,
            returnUrl: req.query.returnUrl ?
              `?returnUrl=${qs.escape(req.query.returnUrl || "")}` :
              ""
          }); //Failed login by password.
        }
      });
    } else {
      ml.logger.verbose(`${req.body.username} failed to login because the user did not exist`, ml.tags.account);
      res.render('login', {
        wrongUsername: true,
        returnUrl: req.query.returnUrl ?
          `?returnUrl=${qs.escape(req.query.returnUrl || "")}` :
          ""
      }); //Failed login by username.
    }
  });
});

accountRouter.get('/view', function(req, res) {
  if(res.loginRedirect()) {
    return;
  }

  res.render('view_acct', {
    username: req.session.username,
    email: req.user.email,
    image: req.user.image_name || "../../img/profilepic.jpg"
  });
});

// Check if forgot password is enabled if not send an error message to the user
const checkForgotPassword = (res) => {
  if(!process.env.MAILER_EMAIL_ID || !process.env.MAILER_PASSWORD || !process.env.MAILER_SERVICE_PROVIDER) {
    res.render('forgot-password', {
      badError: new Error("Forgot password is not enabled")
    });

    return false;
  }

  return true;
};

accountRouter.get('/forgot-password', function(req, res) {
  if(!checkForgotPassword(res)) {
    return;
  }

  res.render('forgot-password');
});

/* eslint-disable complexity */
accountRouter.post('/forgot-password', async(req, res) => {
  if(!checkForgotPassword(res)) {
    return undefined;
  }
  
  const buf = crypto.randomBytes(20);
  var token = buf.toString('hex');
  let user = await User.findOne({
    where: {
      username: req.body.username
    }
  });
  if(!user) {
    ml.logger.verbose(`Forgot password failed because ${req.body.username} does not exist`, ml.tags.account);
    return res.render('forgot-password', {
      noUserExists: true
    });
  }
  user.resetPasswordToken = token;
  user.resetPasswordExpires = Date.now() + 3600000;
  user.save();

  let smtpTransport = nodemailer.createTransport({
    service: process.env.MAILER_SERVICE_PROVIDER,
    auth: {
      user: process.env.MAILER_EMAIL_ID,
      pass: process.env.MAILER_PASSWORD
    }
  });
  let mailOptions = {
    from: process.env.MAILER_EMAIL_ID,
    to: user.email,
    subject: 'MazeLike Password Reset',
    // eslint-disable-next-line prefer-template
    text: 'Greetings ' + req.body.username +
    ',\n\nPlease click the following link, or paste this into your browser to complete the process.\n\n' +
    ' http://' + req.headers.host + '/account/reset/' + token + ' \n\n' +
    'If you did not request this, please ignore this email and your password will remain unchanged.' +
    '\n\nThank you,\nMazeLike'
  };
  try {
    await smtpTransport.sendMail(mailOptions);
    ml.logger.info(`Sent forget password email to ${user.email} for ${user.username}`, ml.tags.account);
  } catch (err) {
    return res.render('forgot-password', {
      badError: err
    });
  }
  return res.render('forgot-password', {
    accountFound: true
  });
});

accountRouter.get('/reset/:token', async(req, res) => {
  let user = await User.findOne({
    where: {
      resetPasswordToken: req.params.token
    }
  });
  if(!user) {
    ml.logger.verbose(`Password reset token ${req.params.token} is invalid`, ml.tags.account);
    return res.render('forgot-password', {
      badError: 'Password reset token is invalid or has expired. '.concat(user)
    });
  }
  return res.render('reset_password', {
    token: req.params.token,
    username: user.username
  });
});

accountRouter.post('/reset/:token', async(req, res) => {
  let user = await User.findOne({
    where: {
      resetPasswordToken: req.params.token
    }
  });
  if(!user) {
    ml.logger.verbose(`Password reset token ${req.params.token} is invalid`, ml.tags.account);
    res.render('reset', {
      tokenError: 'Reset token is invalid'
    });
  }
  User.encryptPassword(req.body.password, (err, hash) => {
    let values = {
      password: hash
    };
    let selector = {
      where: {
        username: user.username
      }
    };
    ml.logger.info(`Reset password for ${user.username}`, ml.tags.account);
    User.update(values, selector).then((result) => {
      if(result) {
        res.render('reset_password', {
          successfulReset: 'Password reset was successful for user '.concat(user.username)
        });
      } else {
        res.render('reset/'.concat(req.params.token), {
          tokenError: 'Could not reset password for '.concat(user.username)
        });
      }
    });
  });
});
/* eslint-enable complexity */

accountRouter.get('/dashboard', async(req, res) => {
  if(res.loginRedirect()) {
    return;
  }
  let lobbies = await Lobby.findAll({
    where: {
      playerId: req.user.username
    }
  });
  res.render('dashboard', {
    username: req.session.username,
    image: req.user.image_name || "../../img/profilepic.jpg",
    lobbies
  });
});

export default accountRouter;