"use strict"; const express = require("express"), config = require("config"), { sendVerifyMail, sendPasswordChangedMail, sendVerifiedMail } = require("../lib/mail"), crypto = require("crypto"); const router = express.Router(); const autoAuthenticate = 1; let userDB; require("../db/users.js").then(function(db) { userDB = db; }); router.get("/", function(req, res/*, next*/) { console.log("/users/"); return getSessionUser(req).then((user) => { return res.status(200).send(user); }).catch((error) => { console.log("User not logged in: " + error); return res.status(200).send({}); }); }); router.put("/password", function(req, res) { console.log("/users/password"); const changes = { currentPassword: req.query.c || req.body.c, newPassword: req.query.n || req.body.n }; if (!changes.currentPassword || !changes.newPassword) { return res.status(400).send("Missing current password and/or new password."); } if (changes.currentPassword == changes.newPassword) { return res.status(400).send("Attempt to set new password to current password."); } return getSessionUser(req).then(function(user) { return userDB.sequelize.query("SELECT id FROM users " + "WHERE uid=:username AND password=:password", { replacements: { username: user.username, password: crypto.createHash('sha256').update(changes.currentPassword).digest('base64') }, type: userDB.Sequelize.QueryTypes.SELECT, raw: true }).then(function(users) { if (users.length != 1) { return null; } return user; }); }).then(function(user) { if (!user) { console.log("Invalid password"); /* Invalid password */ res.status(401).send("Invalid password"); return null; } return userDB.sequelize.query("UPDATE users SET password=:password WHERE uid=:username", { replacements: { username: user.username, password: crypto.createHash('sha256').update(changes.newPassword).digest('base64') } }).then(function() { console.log("Password changed for user " + user.username + " to '" + changes.newPassword + "'."); res.status(200).send(user); user.id = req.session.userId; return sendPasswordChangedMail(userDB, req, user); }); }); }); router.get("/csrf", (req, res) => { console.log("/users/csrf"); res.json({ csrfToken: req.csrfToken() }); }); router.post("/signup", function(req, res) { console.log("/users/signup"); const user = { uid: req.body.email, familyName: req.body.familyName, firstName: req.body.firstName, password: req.body.password, email: req.body.email, }; if (!user.uid || !user.email || !user.password || !user.familyName || !user.firstName) { return res.status(400).send({ message: `Missing email address, password, and/or name.` }); } user.password = crypto.createHash('sha256') .update(user.password).digest('base64'); user.md5 = crypto.createHash('md5') .update(user.email).digest('base64'); return userDB.sequelize.query("SELECT * FROM users WHERE uid=:uid", { replacements: user, type: userDB.Sequelize.QueryTypes.SELECT, raw: true }).then(async function(results) { if (results.length != 0 && results[0].mailVerified) { return res.status(400).send({ message: `Email address already used.` }); } let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (!re.exec(user.email)) { const error = `Invalid email address: ${user.email}.`; console.log(error); return res.status(401).send({ message: error }); } try { if (results.length != 0) { await userDB.sequelize.query("UPDATE users " + "SET mailVerified=0"); req.session.userId = results[0].id; } else { let [, metadata] = await userDB.sequelize.query("INSERT INTO users " + "(uid,firstName,familyName,password,email,memberSince," + "authenticated,md5) " + `VALUES(:uid,:firstName,:familyName,:password,` + `:email,CURRENT_TIMESTAMP,${autoAuthenticate},:md5)`, { replacements: user }); req.session.userId = metadata.lastID; } return getSessionUser(req).then(function(user) { res.status(200).send(user); user.id = req.session.userId; return sendVerifyMail(userDB, req, user); }); } catch (error) { console.error(error); } }); }); const getSessionUser = function(req) { return Promise.resolve().then(function() { if (!req.session || !req.session.userId) { throw "Unauthorized. You must be logged in."; } let query = "SELECT " + "uid AS username,firstName,familyName,mailVerified,authenticated,memberSince,email,md5 " + "FROM users WHERE id=:id"; return userDB.sequelize.query(query, { replacements: { id: req.session.userId }, type: userDB.Sequelize.QueryTypes.SELECT, raw: true }).then(function(results) { if (results.length != 1) { throw "Invalid account."; } let user = results[0]; if (!user.mailVerified) { user.restriction = user.restriction || "Email address not verified."; return user; } if (!user.authenticated) { user.restriction = user.restriction || "Accout not authorized."; return user; } return user; }); }).then(function(user) { /* Strip out any fields that shouldn't be there. The allowed fields are: */ let allowed = [ "maintainer", "username", "firstName", "familyName", "mailVerified", "authenticated", "name", "email", "restriction", "md5" ]; for (let field in user) { if (allowed.indexOf(field) == -1) { delete user[field]; } } return user; }); } router.post("/verify-email", async (req, res) => { console.log("/users/verify-email"); const key = req.body.token; let results = await userDB.sequelize.query( "SELECT * FROM authentications WHERE key=:key", { replacements: { key }, type: userDB.sequelize.QueryTypes.SELECT }); let token; if (results.length == 0) { console.log("Invalid key. Ignoring."); return res.status(400).send({ message: `Invalid authentication token.` }); } token = results[0]; console.log(token); console.log("Matched token: " + JSON.stringify(token, null, 2)); switch (token.type) { case "account-setup": return userDB.sequelize.query( "UPDATE users SET mailVerified=1 WHERE id=:userId", { replacements: token }) .then(function () { return userDB.sequelize.query( "DELETE FROM authentications WHERE key=:key", { replacements: { key } }); }) .then(function () { return userDB.sequelize.query( "SELECT * FROM users WHERE id=:userId", { replacements: token, type: userDB.sequelize.QueryTypes.SELECT }); }) .then(function (results) { if (results.length == 0) { return res.status(500).send({ message: `Internal authentication error.` }); } return results[0]; }) .then((user) => { sendVerifiedMail(userDB, req, user); req.session.userId = user.id; }).then(function (user) { return getSessionUser(req).then(function (user) { return res.status(200).send(user); }); }); } }); router.post("/signin", function(req, res) { console.log("/users/signin"); let { email, password } = req.body; if (!email || !password) { return res.status(400).send({ message: `Missing email and/or password` }); } console.log("Looking up user in DB."); let query = "SELECT " + "id,mailVerified,authenticated," + "uid AS username," + "familyName,firstName,email " + "FROM users WHERE uid=:username AND password=:password"; return userDB.sequelize.query(query, { replacements: { username: email, password: crypto.createHash('sha256').update(password).digest('base64') }, type: userDB.Sequelize.QueryTypes.SELECT }) .then(function(users) { if (users.length != 1) { return null; } let user = users[0]; req.session.userId = user.id; return user; }) .then(function(user) { if (!user) { console.log(email + " not found (or invalid password.)"); req.session.userId = null; return res.status(401).send({ message: `Invalid sign in credentials` }); } let message = "Logged in as " + user.email + " (" + user.id + ")"; if (!user.mailVerified) { console.log(message + ", who is not verified email."); } else if (!user.authenticated) { console.log(message + ", who is not authenticated."); } else { console.log(message); } return getSessionUser(req).then(function(user) { return res.status(200).send(user); }); }) .catch(function(error) { console.log(error); return res.status(403).send(error); }); }); router.post("/signout", (req, res) => { console.log("/users/signout"); if (req.session && req.session.userId) { req.session.userId = null; } res.status(200).send({}); }); module.exports = router;