const fs = require("fs"); const User = require("../models/user.js"); const asyncHandler = require("express-async-handler"); const { body, validationResult } = require("express-validator"); const { default: mongoose } = require("mongoose"); const bcrypt = require("bcryptjs"); const passport = require("passport"); const multer = require("multer"); const storage = multer.memoryStorage(); const upload = multer({ storage: storage, limits: { fileSize: 1840000 }, }); exports.new_get = (req, res, next) => { res.render("newuser", { errors: false }); }; exports.new_post = [ // this goes first as it replaces bodyparser to some extent upload.single("avatar"), // Validate and sanitize fields body("username", "Please enter a username!") .trim() .escape() .isLength({ min: 1 }) .custom(async (value) => { const found = await User.findOne({ username: value }).lean().exec(); if (found) throw new Error("User already exists!"); else return value; }), body("password", "Please enter a password!") .trim() .escape() .isLength({ min: 1 }), body("confirm", "Please confirm password!") .trim() .escape() .isLength({ min: 1 }) .custom((value, { req }) => { if (value !== req.body.password) throw new Error("Passwords don't match!"); else return value; }), // Process request after validation and sanitization. asyncHandler(async (req, res, next) => { // Extract the validation errors from a request. const errors = validationResult(req); // if there are validation errors if (!errors.isEmpty()) { // Render the creation form again with sanitized values/error messages. res.render("newuser", { errors: errors.array(), }); return; } if (!req.file) { const error = { msg: "Avatar not uploaded", }; // add to errors array errArr = [error]; // Render the creation form again with error message. res.render("newuser", { errors: errArr, }); return; } // else data is valid // create new user with hashed password const user = new User({ username: req.body.username, password: await bcrypt.hash(req.body.password, 10), _id: new mongoose.Types.ObjectId(), avatar: req.file.buffer, }); // save to mongo await user.save(); // saved. Redirect to login page res.redirect(`/user/login`); }), ]; exports.login_get = (req, res, next) => { res.render("login", { error: false }); }; exports.login_post = (req, res, next) => { passport.authenticate("local", (err, user, options) => { if (!user) { return res.render("login", { error: options.message }); } req.login(user, (error) => { if (error) { console.log(error); } return res.redirect("/"); }); })(req, res, next); }; exports.logout = (req, res, next) => { req.logout(() => { res.redirect("/"); }); }; exports.get = asyncHandler(async (req, res, next) => { const user = await User.findById(req.params.userID).exec(); res.render("user", { user: user, currentUser: req.user, }); }); exports.put_get = asyncHandler(async (req, res, next) => { const user = await User.findById(req.params.userID).lean().exec(); res.render("edituser", { user: user, errors: false, }); }); exports.put = [ // this goes first as it replaces bodyparser to some extent upload.single("avatar"), // Validate and sanitize fields // username cannot be changed for security/impersonation reasons body("oldpassword", "Please enter current password to authorize changes!") .trim() .escape() .isLength({ min: 1 }) .custom(async (value, { req }) => { const dbUser = await User.findById(req.params.userID).lean().exec(); const match = await bcrypt.compare(value, dbUser.password); if (!match) { return Promise.reject(); } }) .withMessage("Incorrect password!"), body("password", "Please enter new password!").optional().trim().escape(), // Process request after validation and sanitization. asyncHandler(async (req, res, next) => { // find existing user in DB const dbUser = await User.findById(req.params.userID).lean().exec(); // Extract the validation errors from a request. const errors = validationResult(req); // if there are validation errors if (!errors.isEmpty()) { // Render the creation form again with sanitized values/error messages. res.render("edituser", { errors: errors.array(), user: dbUser, }); return; } // else data is valid // ascertain which fields have been changed by the user let password = (image = false); let user = null; // check for password field if ( req.body.password && req.body.password != "" && req.body.password != "null" && req.body.password !== null ) { password = true; } // check for image if (req.file) image = true; // if there is a password if (password) { // if there is an image if (image) { // make new user with new avatar and password user = new User({ username: dbUser.username, password: await bcrypt.hash(req.body.password, 10), _id: dbUser._id, avatar: req.file.buffer, }); } // if there is not an image else { // make new user with new password only user = new User({ username: dbUser.username, password: await bcrypt.hash(req.body.password, 10), _id: dbUser._id, avatar: dbUser.avatar, }); } } // if there is not a password but there is an image else if (image) { // make new user with new image only user = new User({ username: dbUser.username, password: dbUser.password, _id: dbUser._id, avatar: req.file.buffer, }); } // if nothing was passed, user stays null // update user in DB to new user if needed if (user != null) { await User.findByIdAndUpdate(dbUser._id, user, {}); // saved. logout and redirect to log in again req.logout((err) => { if (err) { return next(err); } res.redirect("/user/login"); }); return; } // if nothing updated, redirect to edit page with error const error = { msg: "Nothing updated", }; // add to errors array errArr = [error]; // render error res.render("edituser", { user: dbUser, errors: errArr, }); return; }), ]; exports.delete = asyncHandler(async (req, res, next) => { await User.findByIdAndDelete(req.params.userID); req.logout((err) => { if (err) { return next(err); } res.redirect("/user/login"); }); });