var logger = require('morgan');
var sassMiddleware = require('node-sass-middleware');
var favicon = require('serve-favicon');
+var UserModel = require(path.join(__dirname, "models", "User"));
var app = express();
// Before showing any page, check + save credentials
app.use(function(req, res, next) {
- req.loggedIn = false;
- res.locals.user = { name: "" };
+ req.userId = 0; //means "anonymous"
+ res.locals.user = { name: "" }; //"anonymous"
if (!req.cookies.token)
return next();
UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
if (!!user)
{
- req.loggedIn = true;
+ req.userId = user.id;
res.locals.user = {
- _id: user._id,
name: user.name,
email: user.email,
notify: user.notify,
};
}
+ else
+ {
+ // Token in cookies presumably wrong: erase it
+ res.clearCookie("token");
+ }
next();
});
});
-var Parameters = { };
+const Parameters =
+{
+ // For mail sending. NOTE: *no trailing slash*
+ siteURL: "http://localhost:3000",
-// For mail sending. NOTE: *no trailing slash*
-Parameters.siteURL = "http://localhost:3000";
+ // To know in which environment the code run
+ env: process.env.NODE_ENV || 'development',
-// Lifespan of a (login) cookie
-Parameters.cookieExpire = 183*24*3600*1000; //6 months in milliseconds
+ // Lifespan of a (login) cookie
+ cookieExpire: 183*24*3600*1000, //6 months in milliseconds
-// Characters in a login token, and period of validity (in milliseconds)
-Parameters.token = {
- length: 16,
- expire: 1000*60*30, //30 minutes in milliseconds
-};
+ // Characters in a login token, and period of validity (in milliseconds)
+ token: {
+ length: 16,
+ expire: 1000*60*30, //30 minutes in milliseconds
+ },
-// Email settings
-Parameters.mail = {
- host: "mail_host_address",
- port: 465, //if secure; otherwise use 587
- secure: true, //...or false
- user: "mail_user_name",
- pass: "mail_password",
- contact: "some_contact_email",
+ // Email settings
+ mail: {
+ host: "mail_host_address",
+ port: 465, //if secure; otherwise use 587
+ secure: true, //...or false
+ user: "mail_user_name",
+ pass: "mail_password",
+ noreply: "some_noreply_email",
+ contact: "some_contact_email",
+ },
};
module.exports = Parameters;
/*
* Structure:
- * _id: problem number (int)
+ * id: problem number (int)
* uid: user id (int)
* vid: variant id (int)
* added: timestamp
"INSERT INTO Problems (added, vid, fen, instructions, solution) VALUES " +
"(" +
Date.now() + "," +
- variant._id + "," +
+ variant.id + "," +
fen + "," +
instructions + "," +
solution +
var db = require("../utils/database");
var maild = require("../utils/mailer.js");
var TokenGen = require("../utils/tokenGenerator");
+var params = require("../config/parameters");
/*
* Structure:
exports.create = function(name, email, notify, callback)
{
db.serialize(function() {
- const query =
+ const insertQuery =
"INSERT INTO Users " +
"(name, email, notify) VALUES " +
"('" + name + "', '" + email + "', " + notify + ")";
- db.run(query, callback); //TODO: need to get the inserted user (how ?)
+ db.run(insertQuery, err => {
+ if (!!err)
+ return callback(err);
+ db.get("SELECT last_insert_rowid() AS rowid", callback);
+ });
});
}
const delimiter = (typeof value === "string" ? "'" : "");
db.serialize(function() {
const query =
- "SELECT * FROM Users " +
+ "SELECT * " +
+ "FROM Users " +
"WHERE " + by + " = " + delimiter + value + delimiter;
db.get(query, cb);
});
db.serialize(function() {
const query =
"UPDATE Users " +
- "SET loginToken = " + token + " AND loginTime = " + Date.now() + " " +
+ "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
"WHERE id = " + uid;
db.run(query, cb);
});
{
// Also empty the login token to invalidate future attempts
db.serialize(function() {
- const querySessionTOken =
+ const querySessionToken =
"SELECT sessionToken " +
"FROM Users " +
"WHERE id = " + uid;
- db.get(querySessionToken, (err,token) => {
+ db.get(querySessionToken, (err,ret) => {
if (!!err)
return cb(err);
- const newToken = token || TokenGen.generate(params.token.length);
+ const token = ret.sessionToken || TokenGen.generate(params.token.length);
const queryUpdate =
"UPDATE Users " +
- "SET loginToken = NULL " +
- (!token ? "AND sessionToken = " + newToken + " " : "") +
+ "SET loginToken = NULL" +
+ (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
"WHERE id = " + uid;
db.run(queryUpdate);
- cb(null, newToken);
+ cb(null, token);
});
});
}
db.serialize(function() {
const query =
"UPDATE Users " +
- "SET name = " + user.name +
- " AND email = " + user.email +
- " AND notify = " + user.notify + " " +
- "WHERE id = " + user._id;
+ "SET name = '" + user.name + "'" +
+ ", email = '" + user.email + "'" +
+ ", notify = " + user.notify + " " +
+ "WHERE id = " + user.id;
db.run(query, cb);
});
}
/*
* Structure:
- * _id: integer
+ * id: integer
* name: varchar
* description: varchar
*/
{
db.serialize(function() {
const query =
- "SELECT * FROM Variants " +
+ "SELECT * " +
+ "FROM Variants " +
"WHERE name='" + name + "'";
db.get(query, callback);
});
exports.getAll = function(callback)
{
db.serialize(function() {
- const query = "SELECT * FROM Variants";
+ const query =
+ "SELECT * " +
+ "FROM Variants";
db.all(query, callback);
});
}
// Logic to login, or create / update a user (and also logout)
-Vue.component('my-upsert-user', {
+vv = Vue.component('my-upsert-user', {
data: function() {
return {
user: user, //initialized with global user object
<div class="card">
<label class="modal-close" for="modalUser"></label>
<h3>{{ stage }}</h3>
- <form id="userForm" @submit.prevent="submit">
+ <form id="userForm" @submit.prevent="onSubmit()">
<div v-show="stage!='Login'">
<fieldset>
<label for="username">Name</label>
<input id="nameOrEmail" type="text" v-model="nameOrEmail"/>
</fieldset>
</div>
- <fieldset>
- <button id="submit" @click.prevent="submit">
- <span>{{ submitMessage }}</span>
- <i class="material-icons">send</i>
- </button>
- </fieldset>
</form>
- <button v-if="stage!='Update'" @click.prevent="toggleStage()">
- <span>{{ stage=="Login" ? "Register" : "Login" }}</span>
- </button>
- <button v-if="stage=='Update'">Logout</button>
+ <div class="button-group">
+ <button id="submit" @click="onSubmit()">
+ <span>{{ submitMessage }}</span>
+ <i class="material-icons">send</i>
+ </button>
+ <button v-if="stage!='Update'" @click="toggleStage()">
+ <span>{{ stage=="Login" ? "Register" : "Login" }}</span>
+ </button>
+ <button v-if="stage=='Update'" onClick="location.replace('/logout')">
+ <span>Logout</span>
+ </button>
+ </div>
<div id="dialog" :style="{display: displayInfo}">{{ infoMsg }}</div>
</div>
</div>
return "Modifications applied!";
}
},
- submit: function() {
+ onSubmit: function() {
// Basic anti-bot strategy:
const exitTime = Date.now();
if (this.stage == "Register" && exitTime - this.enterTime < 5000)
}
setTimeout(() => {
this.infoMsg = "";
+ if (this.stage == "Register")
+ this.stage = "Login";
document.getElementById("modalUser").checked = false;
}, 2000);
},
function checkNameEmail(o)
{
- if (!!o.name)
+ if (typeof o.name === "string")
{
if (o.name.length == 0)
return "Empty name";
if (!o.name.match(/^[\w]+$/))
return "Bad characters in name";
}
- if (!!o.email)
+ if (typeof o.email === "string")
{
if (o.email.length == 0)
return "Empty email";
const s = sanitizeUserInput(req.body["fen"], req.body["instructions"], req.body["solution"]);
if (typeof s === "string")
return res.json({errmsg: s});
- ProblemModel.update(pid, req.user._id, fen, instructions, solution);
+ ProblemModel.update(pid, req.userId, fen, instructions, solution);
res.json({});
});
// Delete a problem
router.delete("/problems/:id([0-9]+)", access.logged, access.ajax, (req,res) => {
const pid = req.params["id"]; //problem ID
- ProblemModel.delete(pid, req.user._id);
+ ProblemModel.delete(pid, req.userId);
res.json({});
});
var params = require("../config/parameters");
var checkNameEmail = require("../public/javascripts/shared/userCheck")
-// to: object user
+// to: object user (to who we send an email)
function setAndSendLoginToken(subject, to, res)
{
// Set login token and send welcome(back) email with auth link
- let token = TokenGen.generate(params.token.length);
- UserModel.setLoginToken(token, to._id, (err,ret) => {
- access.checkRequest(res, err, ret, "Cannot set login token", () => {
- const body =
- "Hello " + to.name + "!\n" +
- "Access your account here: " +
- params.siteURL + "/authenticate?token=" + token + "\\n" +
- "Token will expire in " + params.token.expire/(1000*60) + " minutes."
- sendEmail(params.mail.from, to.email, subject, body, err => {
- res.json(err || {});
- });
+ const token = TokenGen.generate(params.token.length);
+ UserModel.setLoginToken(token, to.id, err => {
+ if (!!err)
+ return res.json({errmsg: err.toString()});
+ const body =
+ "Hello " + to.name + "!\n" +
+ "Access your account here: " +
+ params.siteURL + "/authenticate?token=" + token + "\\n" +
+ "Token will expire in " + params.token.expire/(1000*60) + " minutes."
+ sendEmail(params.mail.noreply, to.email, subject, body, err => {
+ res.json(err || {});
});
});
}
const error = checkNameEmail({name: name, email: email});
if (!!error)
return res.json({errmsg: error});
- UserModel.create(name, email, notify, (err,user) => {
- access.checkRequest(res, err, user, "Registration failed", () => {
- setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
- });
+ UserModel.create(name, email, notify, (err,uid) => {
+ if (!!err)
+ return res.json({errmsg: err.toString()});
+ const user = {
+ id: uid["rowid"],
+ name: name,
+ email: email,
+ };
+ setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
});
});
});
router.get('/authenticate', access.unlogged, (req,res) => {
- UserModel.getByLoginToken(req.query.token, (err,user) => {
+ UserModel.getOne("loginToken", req.query.token, (err,user) => {
access.checkRequest(res, err, user, "Invalid token", () => {
// If token older than params.tokenExpire, do nothing
if (Date.now() > user.loginTime + params.token.expire)
return res.json({errmsg: "Token expired"});
// Generate session token (if not exists) + destroy login token
- UserModel.trySetSessionToken(user._id, (err,token) => {
+ UserModel.trySetSessionToken(user.id, (err,token) => {
if (!!err)
- return res.json(err);
+ return res.json({errmsg: err.toString()});
// Set cookie
res.cookie("token", token, {
httpOnly: true,
- secure: true,
- maxAge: params.cookieExpire
+ secure: !!params.siteURL.match(/^https/),
+ maxAge: params.cookieExpire,
});
res.redirect("/");
});
});
});
-router.put('/settings', access.logged, access.ajax, (req,res) => {
- let user = JSON.parse(req.body.user);
- const error = checkNameEmail({name: user.name, email: user.email});
+router.put('/update', access.logged, access.ajax, (req,res) => {
+ const name = req.body.name;
+ const email = req.body.email;
+ const error = checkNameEmail({name: name, email: email});
if (!!error)
return res.json({errmsg: error});
- user.notify = !!user.notify; //in case of...
- user._id = res.locals.user._id; //in case of...
- UserModel.updateSettings(user, (err,ret) => {
- access.checkRequest(res, err, ret, "Settings update failed", () => {
- res.json({});
- });
+ const user = {
+ id: req.userId,
+ name: name,
+ email: email,
+ notify: !!req.body.notify,
+ };
+ UserModel.updateSettings(user, err => {
+ res.json(err ? {errmsg: err.toString()} : {});
});
});
-// Logout on server because the token cookie is secured + http-only
+// Logout on server because the token cookie is httpOnly
router.get('/logout', access.logged, (req,res) => {
res.clearCookie("token");
res.redirect('/');
// Prevent access to "users pages"
Access.logged = function(req, res, next)
{
- if (!req.loggedIn)
+ if (req.userId == 0)
return res.redirect("/");
next();
};
// Prevent access to "anonymous pages"
Access.unlogged = function(req, res, next)
{
- if (!!req.loggedIn)
+ if (req.userId > 0)
return res.redirect("/");
next();
};
Access.checkRequest = function(res, err, out, msg, cb)
{
if (!!err)
- return res.json(err);
+ return res.json({errmsg: err.errmsg || err.toString()});
if (!out
|| (Array.isArray(out) && out.length == 0)
|| (typeof out === "object" && Object.keys(out).length == 0))
module.exports = function(from, to, subject, body, cb)
{
+ // Avoid the actual sending in development mode
+ if (params.env === 'development')
+ {
+ console.log("New mail: from " + from + " / to " + to);
+ console.log("Subject: " + subject);
+ let msgText = body.split('\\n');
+ msgText.forEach(msg => { console.log(msg); });
+ return cb();
+ }
+
// Create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: params.mail.host,
text: body,
};
- // Avoid the actual sending in development mode
- const env = process.env.NODE_ENV || 'development';
- if ('development' === env)
- {
- console.log("New mail: from " + from + " / to " + to);
- console.log("Subject: " + subject);
- let msgText = body.split('\\n');
- msgText.forEach(msg => { console.log(msg); });
- return cb();
- }
-
// Send mail with the defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (!!error)