litebb/controllers/user.controller.js

1const bcrypt = require("bcrypt"); 2 3const { promisify } = require("util"); 4const { Op } = require("sequelize"); 5const bcryptHashP = promisify(bcrypt.hash); 6const bcryptCompareP = promisify(bcrypt.compare); 7 8const { User, Post, Reply } = require("../models"); 9const { siteName, makeRandomId, displayDate } = require("../helpers"); 10 11const { sendEmail } = require("../email"); 12 13const loginForm = (req, res) => { 14 if (req.session && req.session.user) { 15 res.redirect("/"); 16 return; 17 } 18 if (req.query.next) { 19 req.session.nextUrl = req.query.next; 20 } 21 res.render("login", { title: "Log In" }); 22}; 23 24const signUpForm = (req, res) => { 25 if (req.session && req.session.user) { 26 res.redirect("/"); 27 return; 28 } 29 res.render("signup", { title: "Sign Up" }); 30}; 31 32const forgotPasswordForm = (req, res) => { 33 if (req.session && req.session.user) { 34 res.redirect("/"); 35 return; 36 } 37 res.render("forgot_password", { title: "Forgot Password" }); 38}; 39 40const userProfilePage = async (req, res, next) => { 41 try { 42 const userInfo = await User.findOne({ 43 where: { 44 username: req.params.username, 45 }, 46 attributes: [ 47 "id", 48 "username", 49 "email", 50 "avatar", 51 "role", 52 "createdAt", 53 "website", 54 ], 55 }); 56 57 if (!userInfo) { 58 const err = { 59 message: "User not found!", 60 status: 404, 61 }; 62 next(err); 63 return; 64 } 65 66 let sameUser = false; 67 if (req.session.user.id == userInfo.id) { 68 sameUser = true; 69 } 70 71 const promises = []; 72 73 // post count 74 promises.push( 75 Post.count({ 76 where: { createdByUser: userInfo.id }, 77 }), 78 ); 79 // reply count 80 promises.push( 81 Reply.count({ 82 where: { createdByUser: userInfo.id }, 83 }), 84 ); 85 86 const [postCount, replyCount] = await Promise.all(promises); 87 88 userInfo.createdAtFormatted = displayDate(userInfo.createdAt); 89 userInfo.roleFormatted = userInfo.role; 90 if (userInfo.role === "regular") { 91 userInfo.roleFormatted = "user"; 92 } 93 94 res.render("user_profile", { 95 title: `${userInfo.username}'s Profile`, 96 userInfo, 97 postCount, 98 replyCount, 99 sameUser, 100 }); 101 return; 102 } catch (e) { 103 next(e); 104 console.log(e); 105 return; 106 } 107}; 108 109const signupNewUser = async (req, res, next) => { 110 try { 111 const users = await User.findAll({ 112 where: { 113 username: req.xop.username, 114 }, 115 }); 116 117 if (users.length) { 118 req.flash("error", ["Username already in use"]); 119 res.render("signup", { 120 title: "Sign Up", 121 body: req.body, 122 flashes: req.flash(), 123 }); 124 return; 125 } 126 127 // we need to create the account and log the user in 128 const passwordHash = await bcryptHashP(req.body.password, 10); 129 130 const user = await User.create({ 131 username: req.xop.username, 132 passwordHash, 133 email: req.xop.email, 134 role: process.env.FIRST_RUN ? "admin" : "regular", 135 }); 136 137 // set session 138 req.session.user = { 139 id: user.id, 140 username: user.username, 141 }; 142 143 let redirectUrl = "/"; 144 145 if (req.session.nextUrl) { 146 redirectUrl = req.session.nextUrl; 147 delete req.session.nextUrl; 148 } 149 150 res.redirect(redirectUrl); 151 152 return; 153 } catch (e) { 154 next(e); 155 console.log(e); 156 return; 157 } 158}; 159 160const loginUser = async (req, res, next) => { 161 try { 162 const user = await User.findOne({ 163 where: { 164 username: req.xop.username, 165 }, 166 }); 167 168 if (!user) { 169 req.flash("error", ["User not found"]); 170 res.render("login", { 171 title: "Log In", 172 body: req.body, 173 flashes: req.flash(), 174 }); 175 return; 176 } 177 178 const passwordsMatch = await bcryptCompareP( 179 req.xop.password, 180 user.passwordHash, 181 ); 182 183 if (!passwordsMatch) { 184 req.flash("error", ["Invalid Password"]); 185 res.render("login", { 186 title: "Log In", 187 body: req.body, 188 flashes: req.flash(), 189 }); 190 return; 191 } 192 193 // set session and redirect to / 194 req.session.user = { 195 id: user.id, 196 username: user.username, 197 }; 198 199 let redirectUrl = "/"; 200 201 if (req.session.nextUrl) { 202 redirectUrl = req.session.nextUrl; 203 delete req.session.nextUrl; 204 } 205 206 res.redirect(redirectUrl); 207 return; 208 } catch (e) { 209 next(e); 210 console.log(e); 211 return; 212 } 213}; 214 215const logoutUser = (req, res, next) => { 216 req.session.destroy((err) => { 217 if (err) { 218 next(err); 219 return; 220 } 221 res.redirect("/"); 222 }); 223}; 224 225const sendForgotPasswordMail = async (req, res, next) => { 226 try { 227 const user = await User.findOne({ 228 where: { 229 email: req.xop.email, 230 }, 231 attributes: ["id", "email", "username", "resetPasswordToken"], 232 }); 233 234 if (!user) { 235 req.flash("error", ["Email not registered"]); 236 res.render("forgot_password", { 237 title: "Forgot Password", 238 body: req.body, 239 flashes: req.flash(), 240 }); 241 return; 242 } 243 244 const resetToken = makeRandomId(8); 245 user.resetPasswordToken = resetToken; 246 await user.save(); 247 248 const resetURL = `http://${req.headers.host}/reset-password?token=${resetToken}&email=${user.email}`; 249 250 let html = `<p>To reset your password, click <a href="${resetURL}">here</a></p>`; 251 html += `<p>Best,<br/>The ${siteName} admin</p>`; 252 253 let text = `Visit ${resetURL} to reset your password.`; 254 text += `Best, \r\nThe ${siteName} admin`; 255 256 const msg = { 257 to: user.email, 258 subject: `Reset password instructions: ${siteName}`, 259 text, 260 html, 261 }; 262 263 await sendEmail(msg); 264 req.flash("success", ["Check email for reset instructions"]); 265 res.render("forgot_password", { 266 title: "Forgot Password", 267 body: req.body, 268 flashes: req.flash(), 269 }); 270 return; 271 } catch (e) { 272 next(e); 273 console.log(e); 274 return; 275 } 276}; 277 278const getResetPasswordForm = async (req, res, next) => { 279 try { 280 const user = await User.findOne({ 281 where: { 282 email: req.xop.email, 283 resetPasswordToken: req.xop.token, 284 }, 285 attributes: ["id", "resetPasswordToken", "email", "username"], 286 }); 287 288 if (!user) { 289 req.flash("error", ["Email or reset token not valid"]); 290 res.render("forgot_password", { 291 title: "Forgot Password", 292 body: { email: req.xop.email }, 293 flashes: req.flash(), 294 }); 295 return; 296 } 297 298 const pageData = { 299 username: user.username, 300 email: req.xop.email, 301 token: req.xop.token, 302 }; 303 304 req.flash("success", [`Hi, ${user.username}!`]); 305 res.render("reset_password", { 306 title: "Reset Password", 307 body: pageData, 308 flashes: req.flash(), 309 }); 310 return; 311 } catch (e) { 312 next(e); 313 console.log(e); 314 return; 315 } 316}; 317 318const resetPassword = async (req, res, next) => { 319 try { 320 const user = await User.findOne({ 321 where: { 322 email: req.xop.email, 323 resetPasswordToken: req.xop.token, 324 }, 325 attributes: ["id", "passwordHash", "resetPasswordToken"], 326 }); 327 328 if (!user) { 329 req.flash("error", [`Email or reset token not found`]); 330 res.render("reset_password", { 331 title: "Reset Password", 332 body: req.body, 333 flashes: req.flash(), 334 }); 335 return; 336 } 337 338 const passwordHash = await bcryptHashP(req.body.password, 10); 339 user.passwordHash = passwordHash; 340 user.resetPasswordToken = ""; 341 await user.save(); 342 343 req.flash("success", ["Password updated"]); 344 res.render("login", { title: "Log In", flashes: req.flash() }); 345 return; 346 } catch (e) { 347 next(e); 348 console.log(e); 349 return; 350 } 351}; 352 353const changeUserPassword = async (req, res, next) => { 354 try { 355 const user = await User.findOne({ 356 where: { 357 id: req.session.user.id, 358 }, 359 attributes: ["id", "passwordHash", "username"], 360 }); 361 362 const passwordsMatch = await bcryptCompareP( 363 req.xop.password_old, 364 user.passwordHash, 365 ); 366 367 if (!passwordsMatch) { 368 req.flash("error", ["Current password is incorrect"]); 369 const url = req.header("Referer") || `/u/${user.username}`; 370 res.redirect(url); 371 return; 372 } 373 374 const passwordHash = await bcryptHashP(req.xop.password, 10); 375 user.passwordHash = passwordHash; 376 user.resetPasswordToken = ""; 377 await user.save(); 378 379 req.flash("success", ["Password updated"]); 380 res.redirect("/logout"); 381 return; 382 } catch (e) { 383 next(e); 384 console.log(e); 385 return; 386 } 387}; 388 389const updateUserProfile = async (req, res, next) => { 390 try { 391 const user = await User.findOne({ 392 where: { 393 id: req.session.user.id, 394 }, 395 attributes: ["id", "avatar", "website", "username"], 396 }); 397 398 user.website = req.xop.website; 399 user.avatar = req.xop.avatar; 400 await user.save(); 401 402 req.flash("success", ["Profile updated"]); 403 const url = req.header("Referer") || `/u/${user.username}`; 404 res.redirect(url); 405 return; 406 } catch (e) { 407 next(e); 408 console.log(e); 409 return; 410 } 411}; 412 413module.exports = { 414 loginForm, 415 signUpForm, 416 forgotPasswordForm, 417 userProfilePage, 418 419 signupNewUser, 420 loginUser, 421 logoutUser, 422 sendForgotPasswordMail, 423 getResetPasswordForm, 424 resetPassword, 425 changeUserPassword, 426 updateUserProfile, 427};