"use strict"; const express = require("express"), config = require("config"), LdapAuth = require("ldapauth-fork"), crypto = require("crypto"), createTransport = require("nodemailer").createTransport, hb = require("handlebars"); const router = express.Router(); let userDB; let transporter = createTransport({ host: config.get("smtp.host"), pool: true, port: config.has("smtp.port") ? config.get("smtp.port") : 25 }); let ldap; if (config.has("ldap.url")) { ldap = new LdapAuth(config.get("ldap")); } else { ldap = null; } require("../db/users").then(function(db) { userDB = db; }); router.get("/", function(req, res/*, next*/) { console.log("/users"); if (req.session.user) { return res.status(200).send(req.session.user); } return res.status(200).send({}); }); const templates = { "html": [ "
Dear {{username}},
", "", "Welcome to ketrenos.com. Before you can access the system, you must authenticate", "your email address ({{mail}}).
", "", "To do so, simply access this link, which contains your authentication ", "token: {{secret}}
", "", "Sincerely,
", "James
" ].join("\n"), "text": [ "Dear {{username}},", "", "Welcome to ketrenos.com. Before you can access the system, you must authenticate", "your email address ({{mail}}).", "", "To do so, simply access this link, which contains your authentication ", "token: {{url}}{{secret}}", "", "Sincerely,", "James" ].join("\n") }; function ldapPromise(username, password) { if (!ldap) { return Promise.reject("LDAP not being used"); } return new Promise(function(resolve, reject) { ldap.authenticate(username.replace(/@.*$/, ""), password, function(error, user) { if (error) { return reject(error); } return resolve(user); }); }); } router.post("/create", function(req, res) { let who = req.query.w || req.body.w || "", password = req.query.p || req.body.p || "", name = req.query.n || req.body.n || "", mail = req.query.m || req.body.m; if (!who || !password || !mail || !name) { return res.status(400).send("Missing who you know, name, password, and/or email"); } let query = "SELECT * FROM users WHERE uid=:username"; return userDB.sequelize.query(query, { replacements: { username: mail }, type: userDB.Sequelize.QueryTypes.SELECT }).then(function(results) { if (results.length != 0) { return res.status(400).send("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(mail)) { console.log("Invalid email address: " + mail); return res.status(400).send("Invalid email address."); } }).then(function() { return new Promise(function(resolve, reject) { crypto.randomBytes(16, function(error, buffer) { if (error) { return reject(error); } return resolve(buffer.toString('hex')); }); }); }).then(function(secret) { return userDB.sequelize.query("INSERT INTO users " + "(uid,displayName,password,mail,memberSince,authenticated,notes) " + "VALUES(:username,:name,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", { replacements: { username: mail, name: name, password: crypto.createHash('sha256').update(password).digest('base64'), mail: mail, notes: who } }).spread(function(results, metadata) { return userDB.sequelize.query("INSERT INTO authentications " + "(userId,issued,key,type) VALUES " + "(:userId,CURRENT_TIMESTAMP,:key,'account-setup')", { replacements: { key: secret, userId: metadata.lastID } }).catch(function(error) { console.log(error); throw error; }); }).then(function() { let data = { username: name, mail: mail, secret: secret, url: req.protocol + "://" + req.hostname + req.app.get("basePath") }, envelope = { to: mail, from: config.get("smtp.sender"), subject: "Request to ketrenos.com create account for '" + name + "'", cc: "", bcc: config.get("admin.mail"), text: hb.compile(templates.text)(data), html: hb.compile(templates.html)(data) }; return new Promise(function (resolve, reject) { let attempts = 10; function send(envelope) { /* Rate limit to ten per second */ transporter.sendMail(envelope, function (error, info) { if (!error) { console.log('Message sent: ' + info.response); return resolve(); } if (attempts == 0) { console.log("Error sending email: ", error) return reject(error); } attempts--; console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error); setTimeout(send.bind(undefined, envelope), 100); }); } send(envelope); }); }).then(function() { req.session.user = { name: name, mail: mail, username: mail, authenticated: false, mailVerified: false }; return res.status(200).send(req.session.user); }); }); }); router.post("/login", function(req, res) { let username = req.query.u || req.body.u || "", password = req.query.p || req.body.p || ""; console.log("Login attempt"); if (!username || !password) { return res.status(400).send("Missing username and/or password"); } /* We use LDAP as the primary authenticator; if the user is not * found there, we look them up in the site-specific user database */ return ldapPromise(username, password).then(function(user) { user.authenticated = 1; user.mailVerified = 1; return user; }).catch(function() { console.log("User not found in LDAP. Looking up in DB."); let query = "SELECT * FROM users WHERE uid=:username AND password=:password"; return userDB.sequelize.query(query, { replacements: { username: username, password: crypto.createHash('sha256').update(password).digest('base64') }, type: userDB.Sequelize.QueryTypes.SELECT }).then(function(users) { if (users.length != 1) { return null; } return users[0]; }); }).then(function(user) { if (!user) { console.log(username + " not found (or invalid password.)"); req.session.user = {}; return res.status(401).send("Invalid login credentials"); } req.session.user = { name: user.displayName, mail: user.mail, username: user.uid, authenticated: user.authenticated, mailVerified: user.mailVerified }; if (!user.mailVerified) { console.log("Logged in as " + user.displayName + ", who is not verified email."); } else if (!user.authenticated) { console.log("Logged in as " + user.displayName + ", who is not authenticated."); } else { console.log("Logging in as " + user.displayName); } return res.status(200).send(req.session.user); }); }); router.get("/logout", function(req, res) { if (req.session && req.session.user) { req.session.user = {}; } res.status(200).send(req.session.user); }); module.exports = router;