Applied DRY to authentication mail.
Added user.restriction to indicate if an account username/password is correct, but the account is under restriction for various reasons. Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
2aeb733b2c
commit
814bac8b78
@ -286,6 +286,7 @@
|
||||
}
|
||||
|
||||
#loginStatus .status {
|
||||
white-space: pre-line;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
@ -1786,7 +1787,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.authenticated && user.mailVerified) {
|
||||
this.username = user.username;
|
||||
|
||||
if (!user.restriction) {
|
||||
this.loginStatus = null;
|
||||
this.mode = "memories";
|
||||
this.setActions();
|
||||
@ -1796,14 +1799,15 @@
|
||||
this.actions = [];
|
||||
this.mode = "login";
|
||||
|
||||
this.loginStatusTitle = user.restriction;
|
||||
if (!user.mailVerified) {
|
||||
this.loginStatusTitle = "Account not verified";
|
||||
this.loginStatus = "An email has been sent to " + user.mail + ". " +
|
||||
this.loginStatus = "An email has been sent to " + user.mail + ".\n\n" +
|
||||
"Click the link in that email to verify your email address.";
|
||||
} else if (!user.authenticated) {
|
||||
this.loginStatusTitle = "Account not authorized";
|
||||
this.loginStatus = "Your email address has been verified. Next, James needs to authorize your account. " +
|
||||
"He has received an email and will process the request as quickly as he can.";
|
||||
this.loginStatus =
|
||||
"The site admin needs to authorize your account before you can access the system.\n" +
|
||||
"\n" +
|
||||
"They have received an email and will process the request as quickly as possible.";
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -247,10 +247,13 @@ app.use(function(err, req, res, next) {
|
||||
/* Check authentication */
|
||||
app.use(basePath, function(req, res, next) {
|
||||
return users.getSessionUser(req).then(function(user) {
|
||||
if (user.restriction) {
|
||||
return res.status(401).send(user.restriction);
|
||||
}
|
||||
req.user = user;
|
||||
return next();
|
||||
}).catch(function(error) {
|
||||
return res.status(401).send(error);
|
||||
return res.status(403).send(error);
|
||||
});
|
||||
});
|
||||
|
||||
|
116
server/lib/mail.js
Normal file
116
server/lib/mail.js
Normal file
@ -0,0 +1,116 @@
|
||||
"use strict";
|
||||
|
||||
const config = require("config"),
|
||||
crypto = require("crypto"),
|
||||
hb = require("handlebars");
|
||||
|
||||
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")
|
||||
};
|
||||
|
||||
const sendVerifyMail = function(userDB, req, user) {
|
||||
return userDB.sequelize.query("DELETE FROM authentications WHERE userId=:id AND type='account-setup'", {
|
||||
replacements: {
|
||||
id: user.id
|
||||
}
|
||||
}).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 authentications " +
|
||||
"(userId,issued,key,type) " +
|
||||
"VALUES (:userId,CURRENT_TIMESTAMP,:key,'account-setup')", {
|
||||
replacements: {
|
||||
key: secret,
|
||||
userId: user.id
|
||||
}
|
||||
}).then(function() {
|
||||
return secret;
|
||||
}).catch(function(error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
});
|
||||
}).then(function(secret) {
|
||||
const transporter = req.app.get("transporter");
|
||||
if (!transporter) {
|
||||
console.log("Not sending VERIFY email; SMTP not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
let data = {
|
||||
username: user.displayName,
|
||||
mail: user.mail,
|
||||
secret: secret,
|
||||
url: req.protocol + "://" + req.hostname + req.app.get("basePath")
|
||||
}, envelope = {
|
||||
to: data.mail,
|
||||
from: config.get("smtp.sender"),
|
||||
subject: "Request to ketrenos.com create account for '" + data.username + "'",
|
||||
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);
|
||||
});
|
||||
}).catch(function(error) {
|
||||
console.log("Error creating account: ", error);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sendVerifyMail
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
const express = require("express"),
|
||||
config = require("config"),
|
||||
LdapAuth = require("ldapauth-fork"),
|
||||
crypto = require("crypto"),
|
||||
hb = require("handlebars");
|
||||
{ sendVerifyMail } = require("../lib/mail"),
|
||||
crypto = require("crypto");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -31,34 +31,6 @@ router.get("/", function(req, res/*, next*/) {
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
@ -76,20 +48,22 @@ function ldapPromise(username, password) {
|
||||
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;
|
||||
const user = {
|
||||
uid: req.query.m || req.body.m,
|
||||
displayName: req.query.n || req.body.n || "",
|
||||
password: req.query.p || req.body.p || "",
|
||||
mail: req.query.m || req.body.m,
|
||||
notes: req.query.w || req.body.w || ""
|
||||
};
|
||||
|
||||
if (!who || !password || !mail || !name) {
|
||||
return res.status(400).send("Missing who you know, name, password, and/or email");
|
||||
if (!user.uid || !user.password || !user.displayName || !user.notes) {
|
||||
return res.status(400).send("Missing email address, password, name, and/or who you know.");
|
||||
}
|
||||
|
||||
let query = "SELECT * FROM users WHERE uid=:username";
|
||||
return userDB.sequelize.query(query, {
|
||||
replacements: {
|
||||
username: mail
|
||||
},
|
||||
user.password = crypto.createHash('sha256').update(user.password).digest('base64');
|
||||
|
||||
return userDB.sequelize.query("SELECT * FROM users WHERE uid=:uid", {
|
||||
replacements: user,
|
||||
type: userDB.Sequelize.QueryTypes.SELECT,
|
||||
raw: true
|
||||
}).then(function(results) {
|
||||
@ -98,97 +72,25 @@ router.post("/create", function(req, res) {
|
||||
}
|
||||
|
||||
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);
|
||||
if (!re.exec(user.mail)) {
|
||||
console.log("Invalid email address: " + user.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
|
||||
}
|
||||
"(uid,displayName,password,mail,memberSince,authenticated,notes) " +
|
||||
"VALUES(:uid,:displayName,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", {
|
||||
replacements: user
|
||||
}).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 = req.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);
|
||||
res.status(200).send(user);
|
||||
return sendVerifyMail(userDB, req, user);
|
||||
});
|
||||
}).catch(function(error) {
|
||||
console.log("Error creating account: ", error);
|
||||
return res.status(401).send(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -224,18 +126,23 @@ const getSessionUser = function(req) {
|
||||
|
||||
let user = results[0];
|
||||
req.user = user;
|
||||
if (!user.authenticated) {
|
||||
throw "Accout not authenticated.";
|
||||
}
|
||||
|
||||
if (!user.mailVerified) {
|
||||
throw "Account mail not verified.";
|
||||
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) {
|
||||
if (!config.has("restrictions")) {
|
||||
/* If the user already has a restriction, or there are no album user restrictions,
|
||||
* return the user to the next promise */
|
||||
if (user.restriction || !config.has("restrictions")) {
|
||||
return user;
|
||||
}
|
||||
|
||||
@ -249,12 +156,19 @@ const getSessionUser = function(req) {
|
||||
}
|
||||
}
|
||||
console.log("Unauthorized (logged in) access by user: " + req.user.username);
|
||||
throw "Unauthorized access attempt to restricted album.";
|
||||
user.restriction = "Unauthorized access attempt to restricted album.";
|
||||
|
||||
return user;
|
||||
}).then(function(user) {
|
||||
/* If there are maintainers on this album, check if this user is a maintainer */
|
||||
if (config.has("maintainers")) {
|
||||
let maintainers = config.get("maintainers");
|
||||
if (maintainers.indexOf(user.username) != -1) {
|
||||
user.maintainer = true;
|
||||
if (user.restriction) {
|
||||
console.warn("User " + user.username + " is a maintainer AND has a restriction which will be ignored: " + user.restriction);
|
||||
delete user.restriction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,7 +176,7 @@ const getSessionUser = function(req) {
|
||||
}).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"
|
||||
"maintainer", "username", "displayName", "mailVerified", "authenticated", "name", "mail", "restriction"
|
||||
];
|
||||
for (let field in user) {
|
||||
if (allowed.indexOf(field) == -1) {
|
||||
|
133
util/resend.js
133
util/resend.js
@ -1,8 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const config = require("config"),
|
||||
crypto = require("crypto"),
|
||||
hb = require("handlebars");
|
||||
{ sendVerifyMail } = require("../server/lib/mail");
|
||||
|
||||
const basePath = "/" + config.get("basePath").replace(/^\/+/, "").replace(/\/+$/, "") + "/";
|
||||
|
||||
@ -36,128 +35,32 @@ if (config.has("admin.mail") &&
|
||||
console.log("SMTP disabled. To enable SMTP, configure admin.mail, smtp.host, and smtp.sender");
|
||||
}
|
||||
|
||||
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")
|
||||
};
|
||||
if (process.argv.length != 3) {
|
||||
console.log("usage: node resend UID");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
require("../server/db/users").then(function(db) {
|
||||
const userDB = db;
|
||||
let user = null;
|
||||
let id = parseInt(process.argv[2]);
|
||||
|
||||
return userDB.sequelize.query("SELECT * FROM users WHERE id=:id", {
|
||||
return db.sequelize.query("SELECT * FROM users WHERE id=:id", {
|
||||
replacements: {
|
||||
id: process.argv[2]
|
||||
id: id
|
||||
},
|
||||
type: userDB.Sequelize.QueryTypes.SELECT,
|
||||
type: db.Sequelize.QueryTypes.SELECT,
|
||||
raw: true
|
||||
}).then(function(results) {
|
||||
if (results.length != 1) {
|
||||
return ("User not found.");
|
||||
}).then(function(users) {
|
||||
if (users.length != 1) {
|
||||
console.log("User not found: " + id);
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
user = results[0];
|
||||
return users[0];
|
||||
}).then(function(user) {
|
||||
return sendVerifyMail(db, req, user);
|
||||
}).then(function() {
|
||||
return userDB.sequelize.query("DELETE FROM authentications WHERE userId=:id", {
|
||||
replacements: {
|
||||
id: user.id
|
||||
}
|
||||
});
|
||||
}).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 authentications " +
|
||||
"(userId,issued,key,type) VALUES " +
|
||||
"(:userId,CURRENT_TIMESTAMP,:key,'account-setup')", {
|
||||
replacements: {
|
||||
key: secret,
|
||||
userId: user.id
|
||||
}
|
||||
}).then(function() {
|
||||
return secret;
|
||||
}).catch(function(error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
});
|
||||
}).then(function(secret) {
|
||||
const transporter = req.app.get("transporter");
|
||||
if (!transporter) {
|
||||
console.log("Not sending VERIFY email; SMTP not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
let data = {
|
||||
username: user.displayName,
|
||||
mail: user.mail,
|
||||
secret: secret,
|
||||
url: req.protocol + "://" + req.hostname + req.app.get("basePath")
|
||||
}, envelope = {
|
||||
to: data.mail,
|
||||
from: config.get("smtp.sender"),
|
||||
subject: "Request to ketrenos.com create account for '" + data.username + "'",
|
||||
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() {
|
||||
process.exit(0);
|
||||
});
|
||||
process.exit(0);
|
||||
}).catch(function(error) {
|
||||
console.log("Error creating account: ", error);
|
||||
console.log("Error sending verification mail: ", error);
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user