Account creation technically plumpbed; UX isn't the best when setting up account
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
1dc3d74743
commit
389185a59c
134
server/app.js
134
server/app.js
@ -13,6 +13,7 @@ const express = require("express"),
|
||||
bodyParser = require("body-parser"),
|
||||
config = require("config"),
|
||||
session = require('express-session'),
|
||||
hb = require("handlebars"),
|
||||
SQLiteStore = require('connect-sqlite3')(session),
|
||||
scanner = require("./scanner");
|
||||
|
||||
@ -21,8 +22,14 @@ require("./console-line.js"); /* Monkey-patch console.log with line numbers */
|
||||
const picturesPath = config.get("picturesPath").replace(/\/$/, ""),
|
||||
serverConfig = config.get("server");
|
||||
|
||||
config.get("admin.mail");
|
||||
config.get("smtp.host");
|
||||
config.get("smtp.sender");
|
||||
|
||||
let basePath = config.get("basePath");
|
||||
|
||||
let photoDB = null, userDB = null
|
||||
|
||||
basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/";
|
||||
if (basePath == "//") {
|
||||
basePath = "/";
|
||||
@ -70,7 +77,123 @@ app.use(session({
|
||||
|
||||
const index = require("./routes/index");
|
||||
|
||||
const transporter = require("nodemailer").createTransport({
|
||||
host: config.get("smtp.host"),
|
||||
pool: true,
|
||||
port: config.has("smtp.port") ? config.get("smtp.port") : 25
|
||||
});
|
||||
|
||||
const templates = {
|
||||
"html": [
|
||||
"<p>The user {{displayName}} has verified their email address ({{mail}}).</p>",
|
||||
"",
|
||||
"<p>They indicated they know:</p>",
|
||||
"<pre>{{notes}}</pre>",
|
||||
"",
|
||||
"<p>To authenticate:</p>",
|
||||
"<p>echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db</p>",
|
||||
"",
|
||||
"<p>Sincerely,<br>",
|
||||
"James</p>"
|
||||
].join("\n"),
|
||||
"text": [
|
||||
"The user {{displayName}} has verified their email address ({{mail}}).",
|
||||
"",
|
||||
"They indicated they know:",
|
||||
"{{notes}}",
|
||||
"",
|
||||
"To authenticate:",
|
||||
"echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db",
|
||||
"",
|
||||
"Sincerely,",
|
||||
"James"
|
||||
].join("\n")
|
||||
};
|
||||
|
||||
/* Allow loading of the app w/out being logged in */
|
||||
app.use(basePath, function(req, res, next) {
|
||||
let match = req.url.match(/^\/([0-9a-f]+)$/);
|
||||
if (!match) {
|
||||
return next();
|
||||
}
|
||||
|
||||
let key = match[1];
|
||||
return userDB.sequelize.query("SELECT * FROM authentications WHERE key=:key", {
|
||||
replacements: {
|
||||
key: key
|
||||
},
|
||||
type: userDB.sequelize.QueryTypes.SELECT
|
||||
}).then(function(results) {
|
||||
let token;
|
||||
if (results.length == 0) {
|
||||
console.log("Invalid key. Ignoring.");
|
||||
return next();
|
||||
}
|
||||
|
||||
token = results[0];
|
||||
|
||||
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: token
|
||||
});
|
||||
}).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) {
|
||||
throw "DB mis-match between authentications and users table";
|
||||
}
|
||||
let user = results[0],
|
||||
envelope = {
|
||||
to: config.get("admin.mail"),
|
||||
from: config.get("smtp.sender"),
|
||||
subject: "VERIFIED: Account'" + user.displayName + "'",
|
||||
cc: "",
|
||||
bcc: "",
|
||||
text: hb.compile(templates.text)(user),
|
||||
html: hb.compile(templates.html)(user)
|
||||
};
|
||||
|
||||
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 res.redirect(308, basePath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
app.use(basePath, index);
|
||||
|
||||
app.use(basePath + "api/v1/users", require("./routes/users"));
|
||||
@ -125,11 +248,16 @@ app.set("port", serverConfig.port);
|
||||
|
||||
const server = require("http").createServer(app);
|
||||
|
||||
require("./db/photos").then(function(photoDB) {
|
||||
require("./db/photos").then(function(db) {
|
||||
photoDB = db;
|
||||
}).then(function() {
|
||||
return require("./db/users").then(function(db) {
|
||||
userDB = db;
|
||||
});
|
||||
}).then(function() {
|
||||
console.log("DB connected. Opening server.");
|
||||
server.listen(serverConfig.port);
|
||||
return photoDB;
|
||||
}).then(function(photoDB) {
|
||||
}).then(function() {
|
||||
console.log("Scanning.");
|
||||
scanner.init(photoDB);
|
||||
return scanner.scan();
|
||||
|
@ -38,6 +38,7 @@ function init() {
|
||||
authToken: Sequelize.STRING,
|
||||
authDate: Sequelize.DATE,
|
||||
authenticated: Sequelize.BOOLEAN,
|
||||
mailVerified: Sequelize.BOOLEAN,
|
||||
mail: Sequelize.STRING,
|
||||
memberSince: Sequelize.DATE,
|
||||
password: Sequelize.STRING, /* SHA hash of user supplied password for !isLDAP users */
|
||||
|
@ -37,21 +37,25 @@ router.get("/", function(req, res/*, next*/) {
|
||||
|
||||
const templates = {
|
||||
"html": [
|
||||
"<p>Dear {{name}},</p>",
|
||||
"<p>Dear {{username}},</p>",
|
||||
"",
|
||||
"<p>Welcome to HTML {{username}}.</p>",
|
||||
"<p>Welcome to <b>ketrenos.com</b>. Before you can access the system, you must authenticate",
|
||||
"your email address ({{mail}}).</p>",
|
||||
"",
|
||||
"<p>Your secret is: <b>{{secret}}</b>.</p>",
|
||||
"<p>To do so, simply access this link, which contains your authentication ",
|
||||
"token: <a href=\"{{url}}{{secret}}\">{{secret}}</a></p>",
|
||||
"",
|
||||
"<p>Sincerely,</p>",
|
||||
"<p>James</p>"
|
||||
].join("\n"),
|
||||
"text": [
|
||||
"Dear {{name}},",
|
||||
"Dear {{username}},",
|
||||
"",
|
||||
"Welcome to TEXT {{username}}.",
|
||||
"Welcome to ketrenos.com. Before you can access the system, you must authenticate",
|
||||
"your email address ({{mail}}).",
|
||||
"",
|
||||
"Your secret is: {{secret}}.",
|
||||
"To do so, simply access this link, which contains your authentication ",
|
||||
"token: {{url}}{{secret}}",
|
||||
"",
|
||||
"Sincerely,",
|
||||
"James"
|
||||
@ -75,7 +79,7 @@ function ldapPromise(username, password) {
|
||||
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 || username,
|
||||
name = req.query.n || req.body.n || "",
|
||||
mail = req.query.m || req.body.m;
|
||||
|
||||
if (!who || !password || !mail || !name) {
|
||||
@ -93,13 +97,21 @@ router.post("/create", function(req, res) {
|
||||
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,}))$/,
|
||||
secret = "magic cookie";
|
||||
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)", {
|
||||
@ -122,17 +134,18 @@ router.post("/create", function(req, res) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
});
|
||||
}).spread(function(results, metadata) {
|
||||
}).then(function() {
|
||||
let data = {
|
||||
username: name,
|
||||
mail: mail,
|
||||
secret: secret
|
||||
secret: secret,
|
||||
url: req.protocol + "://" + req.hostname + req.app.get("basePath")
|
||||
}, envelope = {
|
||||
to: mail,
|
||||
from: config.get("smtp.sender"),
|
||||
subject: "Request to create account for " + name,
|
||||
subject: "Request to ketrenos.com create account for '" + name + "'",
|
||||
cc: "",
|
||||
bcc: "",
|
||||
bcc: config.get("admin.mail"),
|
||||
text: hb.compile(templates.text)(data),
|
||||
html: hb.compile(templates.html)(data)
|
||||
};
|
||||
@ -208,7 +221,7 @@ router.post("/login", function(req, res) {
|
||||
});
|
||||
}).then(function(user) {
|
||||
if (!user) {
|
||||
console.log(username + " not found.");
|
||||
console.log(username + " not found (or invalid password.)");
|
||||
req.session.user = {};
|
||||
return res.status(401).send("Invalid login credentials");
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ let photoDB = null;
|
||||
|
||||
const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/";
|
||||
|
||||
let processQueue = [], triedClean = [], lastScan = new Date("1900-01-01");
|
||||
let processQueue = [], triedClean = [], lastScan = new Date("1800-01-01");
|
||||
|
||||
//const rawExtension = /\.(nef|orf)$/i, extensions = [ "jpg", "jpeg", "png", "gif", "nef", "orf" ];
|
||||
|
||||
@ -574,7 +574,8 @@ function findOrCreateDBAlbum(transaction, album) {
|
||||
}
|
||||
|
||||
function findOrUpdateDBAsset(transaction, asset) {
|
||||
let query = "SELECT id,DATETIME(scanned) AS scanned,DATETIME(modified) AS modified FROM photos WHERE albumId=:albumId AND filename=:filename";
|
||||
let query = "SELECT id,DATETIME(scanned) AS scanned,DATETIME(modified) AS modified FROM photos " +
|
||||
"WHERE albumId=:albumId AND filename=:filename";
|
||||
if (!asset.album || !asset.album.id) {
|
||||
let error = "Asset being processed without an album";
|
||||
console.error(error);
|
||||
@ -651,16 +652,6 @@ function doScan() {
|
||||
return Promise.resolve("scanning");
|
||||
}
|
||||
|
||||
return photoDB.sequelize.query("SELECT max(scanned) AS scanned FROM photos", {
|
||||
type: photoDB.sequelize.QueryTypes.SELECT
|
||||
}).then(function(results) {
|
||||
if (results[0].scanned == null) {
|
||||
lastScan = new Date("1800-01-01");
|
||||
} else {
|
||||
lastScan = new Date(results[0].scanned);
|
||||
}
|
||||
console.log("Updating any asset newer than " + moment(lastScan).format());
|
||||
}).then(function() {
|
||||
return scanDir(null, picturesPath).spread(function(albums, assets) {
|
||||
console.log("Found " + assets.length + " assets in " + albums.length + " albums after " +
|
||||
((Date.now() - now) / 1000) + "s");
|
||||
@ -690,10 +681,9 @@ function doScan() {
|
||||
return photoDB.sequelize.transaction(function(transaction) {
|
||||
return Promise.map(assets, function(asset) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
/* If both mtime and ctime of the asset are older than the lastScan,
|
||||
* skip this asset. */
|
||||
/* If both mtime and ctime of the asset are older than the lastScan, skip it
|
||||
* Can only do this after a full scan has occurred */
|
||||
if (lastScan != null && asset.stats.mtime < lastScan && asset.stats.ctime < lastScan) {
|
||||
updateScanned.push(asset.id);
|
||||
return resolve(asset);
|
||||
}
|
||||
|
||||
@ -701,12 +691,9 @@ function doScan() {
|
||||
if (!asset.scanned) {
|
||||
newEntries++;
|
||||
}
|
||||
console.log(typeof asset.scanned);
|
||||
if (!asset.scanned || asset.scanned < asset.stats.mtime || !asset.modified) {
|
||||
console.log("Process asset: " + asset.scanned);
|
||||
needsProcessing.push(asset);
|
||||
} else {
|
||||
console.log("Update scanned tag: " + asset.scanned);
|
||||
updateScanned.push(asset.id);
|
||||
}
|
||||
return asset;
|
||||
@ -756,6 +743,15 @@ function doScan() {
|
||||
});
|
||||
}).then(function() {
|
||||
console.log("Total time to initialize DB and all scans: " + ((Date.now() - initialized) / 1000) + "s");
|
||||
return photoDB.sequelize.query("SELECT max(scanned) AS scanned FROM photos", {
|
||||
type: photoDB.sequelize.QueryTypes.SELECT
|
||||
}).then(function(results) {
|
||||
if (results[0].scanned == null) {
|
||||
lastScan = new Date("1800-01-01");
|
||||
} else {
|
||||
lastScan = new Date(results[0].scanned);
|
||||
}
|
||||
console.log("Updating any asset newer than " + moment(lastScan).format());
|
||||
});
|
||||
}).then(function() {
|
||||
scanning = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user