+// TODO: decodeURIComponent() for GET/DELETE parameters
+
1) Finish problems tab
2) Integrate computer play into rules tab
3) Retrieve users system from old code
// User creation
exports.create = function(name, email, notify, callback)
{
- if (!notify)
- notify = false; //default
db.serialize(function() {
- db.run(
+ const query =
"INSERT INTO Users " +
"(name, email, notify) VALUES " +
- "(" + name + "," + email + "," + notify + ")");
+ "('" + name + "', '" + email + "', " + notify + ")";
+ db.run(query, callback); //TODO: need to get the inserted user (how ?)
});
}
{
const delimiter = (typeof value === "string" ? "'" : "");
db.serialize(function() {
- db.get(
+ const query =
"SELECT * FROM Users " +
- "WHERE " + by + " = " + delimiter + value + delimiter,
- callback);
+ "WHERE " + by + " = " + delimiter + value + delimiter;
+ db.get(query, cb);
});
}
exports.setLoginToken = function(token, uid, cb)
{
db.serialize(function() {
- db.run(
+ const query =
"UPDATE Users " +
"SET loginToken = " + token + " AND loginTime = " + Date.now() + " " +
- "WHERE id = " + uid);
+ "WHERE id = " + uid;
+ db.run(query, cb);
});
}
{
// Also empty the login token to invalidate future attempts
db.serialize(function() {
- db.get(
+ const querySessionTOken =
"SELECT sessionToken " +
"FROM Users " +
- "WHERE id = " + uid, (err,token) => {
- if (!!err)
- return cb(err);
- const newToken = token || TokenGen.generate(params.token.length);
- db.run(
- "UPDATE Users " +
- "SET loginToken = NULL " +
- (!token ? "AND sessionToken = " + newToken + " " : "") +
- "WHERE id = " + uid);
+ "WHERE id = " + uid;
+ db.get(querySessionToken, (err,token) => {
+ if (!!err)
+ return cb(err);
+ const newToken = token || TokenGen.generate(params.token.length);
+ const queryUpdate =
+ "UPDATE Users " +
+ "SET loginToken = NULL " +
+ (!token ? "AND sessionToken = " + newToken + " " : "") +
+ "WHERE id = " + uid;
+ db.run(queryUpdate);
cb(null, newToken);
});
});
exports.updateSettings = function(user, cb)
{
db.serialize(function() {
- db.run(
+ const query =
"UPDATE Users " +
"SET name = " + user.name +
" AND email = " + user.email +
" AND notify = " + user.notify + " " +
- "WHERE id = " + user._id);
+ "WHERE id = " + user._id;
+ db.run(query, cb);
});
}
exports.getByName = function(name, callback)
{
db.serialize(function() {
- db.get(
+ const query =
"SELECT * FROM Variants " +
- "WHERE name='" + name + "'",
- callback);
+ "WHERE name='" + name + "'";
+ db.get(query, callback);
});
}
exports.getAll = function(callback)
{
db.serialize(function() {
- db.all("SELECT * FROM Variants", callback);
+ 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', {
- props: ["initUser"], //to find the game in storage (assumption: it exists)
data: function() {
return {
- user: initUser, //initialized with prop value
- stage: (!initUser.email ? "Login" : "Update"),
+ user: user, //initialized with global user object
+ nameOrEmail: "", //for login
+ stage: (!user.email ? "Login" : "Update"),
infoMsg: "",
+ enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy
};
},
template: `
<div>
- <input id="modalUser" class="modal" type="checkbox"/>
+ <input id="modalUser" class="modal" type="checkbox"
+ @change="trySetEnterTime"/>
<div role="dialog">
<div class="card">
- <label class="modal-close" for="modalUser">
+ <label class="modal-close" for="modalUser"></label>
<h3>{{ stage }}</h3>
<form id="userForm" @submit.prevent="submit">
+ <div v-show="stage!='Login'">
+ <fieldset>
+ <label for="username">Name</label>
+ <input id="username" type="text" v-model="user.name"/>
+ </fieldset>
+ <fieldset>
+ <label for="useremail">Email</label>
+ <input id="useremail" type="email" v-model="user.email"/>
+ </fieldset>
+ <fieldset>
+ <label for="notifyNew">Notify new moves & games</label>
+ <input id="notifyNew" type="checkbox" v-model="user.notify"/>
+ </fieldset>
+ </div>
+ <div v-show="stage=='Login'">
+ <fieldset>
+ <label for="nameOrEmail">Name or Email</label>
+ <input id="nameOrEmail" type="text" v-model="nameOrEmail"/>
+ </fieldset>
+ </div>
<fieldset>
- <label for="useremail">Email</label>
- <input id="useremail" type="email" v-model="user.email"/>
- <fieldset>
- <label for="username">Name</label>
- <input id="username" type="text" v-model="user.name"/>
+ <button id="submit" @click.prevent="submit">
+ <span>{{ submitMessage }}</span>
+ <i class="material-icons">send</i>
+ </button>
</fieldset>
- <fieldset>
- <label for="notifyNew">Notify new moves & games</label>
- <input id="notifyNew" type="checkbox" v-model="user.notify"/>
- <button id="submit" @click.prevent="submit">
- <span>{{ submitMessage }}</span>
- <i class="material-icons">send</i>
- <p v-if="stage!='Update'">
- <button @click.prevent="toggleStage()">
+ </form>
+ <button v-if="stage!='Update'" @click.prevent="toggleStage()">
<span>{{ stage=="Login" ? "Register" : "Login" }}</span>
</button>
- <button>Logout</button>
- </p>
- <div id="dialog" :style="{display: displayInfo}">{{ infoMsg }}</div>
+ <button v-if="stage=='Update'">Logout</button>
+ <div id="dialog" :style="{display: displayInfo}">{{ infoMsg }}</div>
+ </div>
</div>
</div>
`,
},
},
methods: {
+ trySetEnterTime: function(event) {
+ if (!!event.target.checked)
+ this.enterTime = Date.now();
+ },
toggleStage: function() {
+ // Loop login <--> register (update is for logged-in users)
this.stage = (this.stage == "Login" ? "Register" : "Login");
},
ajaxUrl: function() {
}
},
submit: function() {
- // TODO: re-activate simple measures like this: (using time of click on modal)
-// const exitTime = new Date();
-// if (this.stage=="Register" && exitTime.getTime() - enterTime.getTime() < 5000)
-// return;
- if (!this.user.name.match(/[a-z0-9_]+/i))
- return alert("User name: only alphanumerics and underscore");
+ // Basic anti-bot strategy:
+ const exitTime = Date.now();
+ if (this.stage == "Register" && exitTime - this.enterTime < 5000)
+ return; //silently return, in (curious) case of it was legitimate
+ let error = undefined;
+ if (this.stage == 'Login')
+ {
+ const type = (this.nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
+ error = checkNameEmail({[type]: this.nameOrEmail});
+ }
+ else
+ error = checkNameEmail(this.user);
+ if (!!error)
+ return alert(error);
this.infoMsg = "Processing... Please wait";
ajax(this.ajaxUrl(), this.ajaxMethod(),
- this.stage == "Login" ? "PUT" : "POST", this.user,
+ this.stage == "Login" ? { nameOrEmail: this.nameOrEmail } : this.user,
res => {
this.infoMsg = this.infoMessage();
if (this.stage != "Update")
{
+ this.nameOrEmail = "";
this.user["email"] = "";
this.user["name"] = "";
}
this.infoMsg = "";
document.getElementById("modalUser").checked = false;
}, 2000);
+ },
+ err => {
+ this.infoMsg = "";
+ alert(err);
}
);
},
--- /dev/null
+function checkNameEmail(o)
+{
+ if (!!o.name)
+ {
+ if (o.name.length == 0)
+ return "Empty name";
+ if (!o.name.match(/^[\w]+$/))
+ return "Bad characters in name";
+ }
+ if (!!o.email)
+ {
+ if (o.email.length == 0)
+ return "Empty email";
+ if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+ return "Bad characters in email";
+ }
+}
+
+try { module.exports = checkNameEmail; } catch(e) { } //for server
// Append query params to URL
url += "/?" + toQueryString(data);
}
-
xhr.open(method, url, true);
xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
if (["POST","PUT"].includes(method))
setCookie("lang", e.target.value);
location.reload(); //to include the right .pug file
}
+
+// Shortcut for an often used click (on a modal)
+function doClick(elemId)
+{
+ document.getElementById(elemId).click(); //or ".checked = true"
+}
var TokenGen = require("../utils/tokenGenerator");
var access = require("../utils/access");
var params = require("../config/parameters");
+var checkNameEmail = require("../public/javascripts/shared/userCheck")
// to: object user
function setAndSendLoginToken(subject, to, res)
UserModel.setLoginToken(token, to._id, (err,ret) => {
access.checkRequest(res, err, ret, "Cannot set login token", () => {
const body =
- "Hello " + to.initials + "!\n" +
+ "Hello " + to.name + "!\n" +
"Access your account here: " +
params.siteURL + "/authenticate?token=" + token + "\\n" +
"Token will expire in " + params.token.expire/(1000*60) + " minutes."
// AJAX user life cycle...
router.post('/register', access.unlogged, access.ajax, (req,res) => {
- let name = decodeURIComponent(req.body.name);
- let email = decodeURIComponent(req.body.email);
- let error = checkObject({name:name, email:email}, "User");
- if (error.length > 0)
+ const name = req.body.name;
+ const email = req.body.email;
+ const notify = !!req.body.notify;
+ const error = checkNameEmail({name: name, email: email});
+ if (!!error)
return res.json({errmsg: error});
- UserModel.create(name, email, (err,user) => {
+ UserModel.create(name, email, notify, (err,user) => {
access.checkRequest(res, err, user, "Registration failed", () => {
setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
});
});
});
-router.put('/sendtoken', access.unlogged, access.ajax, (req,res) => {
- let email = decodeURIComponent(req.body.email);
- let error = checkObject({email:email}, "User");
- if (error.length > 0)
+router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => {
+ const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
+ const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
+ const error = checkNameEmail({[type]: nameOrEmail});
+ if (!!error)
return res.json({errmsg: error});
- UserModel.getOne("email", email, (err,user) => {
+ UserModel.getOne(type, nameOrEmail, (err,user) => {
access.checkRequest(res, err, user, "Unknown user", () => {
setAndSendLoginToken("Token for " + params.siteURL, user, res);
});
router.get('/authenticate', access.unlogged, (req,res) => {
UserModel.getByLoginToken(req.query.token, (err,user) => {
access.checkRequest(res, err, user, "Invalid token", () => {
- let tsNow = Date.now();
// If token older than params.tokenExpire, do nothing
if (Date.now() > user.loginTime + params.token.expire)
return res.json({errmsg: "Token expired"});
});
router.put('/settings', access.logged, access.ajax, (req,res) => {
- const user = JSON.parse(req.body.user);
- // TODO: either verify email + name, or re-apply the following logic:
- //let error = checkObject(user, "User");
- //if (error.length > 0)
- // return res.json({errmsg: error});
- user._id = req.user._id; //TODO:
+ let user = JSON.parse(req.body.user);
+ const error = checkNameEmail({name: user.name, email: user.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({});
-extends layout
+doctype html
+html
-block content
+body
h1= message
h2= error.status
pre #{error.stack}
.info-container
p vchess.club
img(src="/images/index/wildebeest.svg")
- #flagMenu.clickable(
- onClick="document.getElementById('modalLang').checked=true")
+ #flagMenu.clickable(onClick="doClick('modalLang')")
img(src="/images/flags/" + lang + ".svg")
- #userMenu.clickable(
- onClick="document.getElementById('modalUser').checked=true")
+ #userMenu.clickable(onClick="doClick('modalUser')")
.info-container
if !user.email
p
p
span Update
i.material-icons person
- #introductionMenu.clickable(
- onClick="document.getElementById('modalWelcome').checked=true")
+ #introductionMenu.clickable(onClick="doClick('modalWelcome')")
.info-container
p Introduction
.row
block css
body
-
include langNames
case lang
when "en"
include translations/es
when "fr"
include translations/fr
- include contactForm
include modalLang
+ include contactForm
main#VueElement
- my-upsert-user(:user="user" :stage="stage")
+ my-upsert-user()
block content
footer.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2.text-center
div
script(src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js")
else
script(src="https://cdn.jsdelivr.net/npm/vue")
+ script(src="/javascripts/shared/userCheck.js")
script(src="/javascripts/components/upsertUser.js")
script.
const translations = !{JSON.stringify(translations)};
var translations =
{
"Language": "Language",
- "Contact": "Contact",
+ "Contact form": "Contact form",
"Email": "Email",
"Subject": "Subject",
"Content": "Content",
"Pawns move diagonally": "Pawns move diagonally",
"In the shadow": "In the shadow",
"Move twice": "Move twice",
- "Head upside down": "Head upside down",
+ "Board upside down": "Board upside down",
// Variant page:
"New game": "New game",
"Pawns move diagonally": "Peones se mueven en diagonal",
"In the shadow": "En la sombra",
"Move twice": "Mover dos veces",
- "Head upside down": "Cabeza al revés",
+ "Board upside down": "Tablero al revés",
// Variant page:
"New game": "Nueva partida",
"Pawns move diagonally": "Les pions vont en diagonale",
"In the shadow": "Dans l'ombre",
"Move twice": "Jouer deux coups",
- "Head upside down": "La tête à l'envers",
+ "Board upside down": "Échiquier à l'envers",
// Variant page:
"New game": "Nouvelle partie",