360 lines
10 KiB
JavaScript
Executable File
360 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.";
|
|
}
|
|
|
|
let user = results[0];
|
|
req.user = user;
|
|
if (!user.authenticated) {
|
|
throw "Accout not authenticated.";
|
|
}
|
|
|
|
if (!user.mailVerified) {
|
|
throw "Account mail not verified.";
|
|
}
|
|
|
|
return user;
|
|
});
|
|
}).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
|
|
};
|