James Ketrenos 734fc13967 Chain login to use getSessionUser to fill all the correct fields
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
2018-10-11 20:38:19 -07:00

358 lines
10 KiB
JavaScript
Executable File

"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 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/");
return getSessionUser(req).then(function(user) {
return res.status(200).send(user);
}).catch(function(error) {
console.log("User not logged in: " + error);
return res.status(200).send({});
});
});
const templates = {
"html": [
"<p>Hello {{username}},</p>",
"",
"<p>Welcome to <b>ketrenos.com</b>. You are almost done creating your account. ",
"Before you can access the system, you must verify your email address.</p>",
"",
"<p>To do so, simply access this link:</p>",
"<p><a href=\"{{url}}{{secret}}\">VERIFY {{mail}} ADDRESS</a></p>",
"",
"<p>Sincerely,</p>",
"<p>James</p>"
].join("\n"),
"text": [
"Hello {{username}},",
"",
"Welcome to ketrenos.com. You are almost done creating your account. ",
"Before you can access the system, you must verify your email address.",
"",
"To do so, simply access this link:",
"",
"{{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) {
console.log("/users/create");
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,
raw: true
}).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);
throw "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) {
req.session.userId = metadata.lastID;
return userDB.sequelize.query("INSERT INTO authentications " +
"(userId,issued,key,type) VALUES " +
"(:userId,CURRENT_TIMESTAMP,:key,'account-setup')", {
replacements: {
key: secret,
userId: req.session.userId
}
}).catch(function(error) {
console.log(error);
throw error;
});
}).then(function() {
const transporter = app.get("transporter");
if (!transporter) {
console.log("Not sending VERIFY email; SMTP not configured.");
return;
}
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() {
return getSessionUser(req).then(function(user) {
return res.status(200).send(user);
});
});
}).catch(function(error) {
console.log("Error creating account: ", error);
return res.status(401).send(error);
});
});
const getSessionUser = function(req) {
return Promise.resolve().then(function() {
if (!req.session || !req.session.userId) {
throw "Unauthorized. You must be logged in.";
}
if (req.session.userId == "LDAP") {
if (req.session.ldapUser) {
return req.session.ldapUser;
}
req.session.userId = null;
req.session.ldapUser = null;
throw "Invalid LDAP session";
}
let query = "SELECT " +
"uid AS username,displayName,mailVerified,authenticated,memberSince AS name,mail " +
"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.";
}
req.user = results[0];
if (!req.user.authenticated) {
throw "Accout not authenticated.";
}
if (!req.user.mailVerified) {
throw "Account mail not verified.";
}
});
}).then(function(user) {
if (!config.has("restrictions")) {
return user;
}
let allowed = config.get("restrictions");
if (!Array.isArray(allowed)) {
allowed = [ allowed ];
}
for (let i = 0; i < allowed.length; i++) {
if (allowed[i] == user.username) {
return user;
}
}
console.log("Unauthorized (logged in) access by user: " + req.user.username);
throw "Unauthorized access attempt to restricted album.";
}).then(function(user) {
if (config.has("maintainers")) {
let maintainers = config.get("maintainers");
if (maintainers.indexOf(user.username) != -1) {
user.maintainer = true;
}
}
return user;
}).then(function(user) {
/* Strip out any fields that shouldn't be there. The allowed fields are: */
let allowed = [
"maintainer", "username", "displayName", "mailVerified", "authenticated", "name", "mail"
];
for (let field in user) {
if (allowed.indexOf(field) == -1) {
delete user[field];
}
}
return user;
});
}
router.post("/login", function(req, res) {
console.log("/users/login");
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 the user up in the site-specific user database */
return ldapPromise(username, password).then(function(ldap) {
let user = {};
user.name = ldap.displayName;
user.username = ldap.uid;
user.id = "LDAP";
user.mail = ldap.mail;
user.authenticated = 1;
user.mailVerified = 1;
req.session.userId = "LDAP";
req.session.ldapUser = user;
return user;
}).catch(function() {
console.log("User not found in LDAP. Looking up in DB.");
let query = "SELECT " +
"id,mailVerified,authenticated,uid AS username,displayName AS name,mail " +
"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;
}
let user = users[0];
req.session.userId = user.id;
return user;
});
}).then(function(user) {
if (!user) {
console.log(username + " not found (or invalid password.)");
req.session.userId = null;
return res.status(401).send("Invalid login credentials");
}
let message = "Logged in as " + user.name + " (" + 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);
});
});
});
router.get("/logout", function(req, res) {
console.log("/users/logout");
if (req.session && req.session.userId) {
if (req.session.userId == "LDAP") {
req.session.ldapUser = null;
}
req.session.userId = null;
}
res.status(200).send({});
});
module.exports = {
router,
getSessionUser
};