From 866842c3c310524c034922870234120ed2a16cbf Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 20 Feb 2020 13:20:16 +0100
Subject: [PATCH] Revise server code + a few fixes in trnalsations and
 ComputerGame

---
 client/src/components/ComputerGame.vue |  10 +-
 client/src/components/UpsertUser.vue   |   2 +-
 client/src/data/challengeCheck.js      |   4 +-
 client/src/data/problemCheck.js        |   2 +-
 client/src/data/userCheck.js           |   4 +-
 client/src/translations/en.js          |  29 ++--
 client/src/translations/es.js          |  29 ++--
 client/src/translations/fr.js          |  35 +++--
 client/src/utils/scoring.js            |   2 +-
 client/src/views/Hall.vue              |  46 ++++---
 client/src/views/Problems.vue          |  15 ++-
 client/src/views/Rules.vue             |  12 +-
 server/app.js                          |  15 +--
 server/models/Challenge.js             |  44 ++----
 server/models/Game.js                  | 175 ++++++++++++------------
 server/models/News.js                  |  12 +-
 server/models/Problem.js               |  46 ++-----
 server/models/User.js                  |  63 ++++-----
 server/routes/challenges.js            |  87 ++++++------
 server/routes/games.js                 | 100 ++++++--------
 server/routes/messages.js              |  16 +--
 server/routes/news.js                  |  60 ++++-----
 server/routes/problems.js              |  69 +++++-----
 server/routes/users.js                 | 178 ++++++++++++-------------
 server/routes/variants.js              |   7 +-
 server/utils/access.js                 |  22 +--
 server/utils/mailer.js                 |  14 +-
 server/utils/tokenGenerator.js         |   7 +-
 28 files changed, 498 insertions(+), 607 deletions(-)

diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue
index b1d2b370..a5e03b31 100644
--- a/client/src/components/ComputerGame.vue
+++ b/client/src/components/ComputerGame.vue
@@ -35,12 +35,6 @@ export default {
     "gameInfo.fen": function() {
       this.launchGame();
     },
-    "gameInfo.score": function(newScore) {
-      if (newScore != "*") {
-        this.game.score = newScore; //user action
-        if (!this.compThink) this.$emit("game-stopped"); //otherwise wait for comp
-      }
-    }
   },
   created: function() {
     // Computer moves web worker logic:
@@ -63,7 +57,7 @@ export default {
         let moveIdx = 0;
         let self = this;
         (function executeMove() {
-          self.$refs["basegame"].play(compMove[moveIdx++]);
+          self.$refs["basegame"].play(compMove[moveIdx++], "received");
           if (moveIdx >= compMove.length) {
             self.compThink = false;
             if (self.game.score != "*")
@@ -117,7 +111,7 @@ export default {
     gameOver: function(score, scoreMsg) {
       this.game.score = score;
       this.game.scoreMsg = scoreMsg;
-      this.$emit("game-over", score); //bubble up to Rules.vue
+      if (!this.compThink) this.$emit("game-stopped"); //otherwise wait for comp
     }
   }
 };
diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue
index eb1b6c22..eb8adffc 100644
--- a/client/src/components/UpsertUser.vue
+++ b/client/src/components/UpsertUser.vue
@@ -151,7 +151,7 @@ export default {
         error = checkNameEmail({ [type]: this.nameOrEmail });
       } else error = checkNameEmail(this.st.user);
       if (error) {
-        alert(error);
+        alert(this.st.tr[error]);
         return;
       }
       this.infoMsg = "Processing... Please wait";
diff --git a/client/src/data/challengeCheck.js b/client/src/data/challengeCheck.js
index 789952f8..a535b886 100644
--- a/client/src/data/challengeCheck.js
+++ b/client/src/data/challengeCheck.js
@@ -10,12 +10,12 @@ export function checkChallenge(c) {
   // Basic alphanumeric check for opponent name
   if (c.to) {
     // NOTE: slightly redundant (see data/userCheck.js)
-    if (!c.to.match(/^[\w]+$/)) return "Wrong characters in opponent name";
+    if (!c.to.match(/^[\w]+$/)) return "Name: alphanumerics and underscore";
   }
 
   // Allow custom FEN (and check it) only for individual challenges
   if (c.fen.length > 0 && !!c.to) {
-    if (!V.IsGoodFen(c.fen)) return "Bad FEN string";
+    if (!V.IsGoodFen(c.fen)) return "Errors in FEN";
   } else c.fen = "";
 
   return "";
diff --git a/client/src/data/problemCheck.js b/client/src/data/problemCheck.js
index 652daaed..d0978cbc 100644
--- a/client/src/data/problemCheck.js
+++ b/client/src/data/problemCheck.js
@@ -2,7 +2,7 @@ export function checkProblem(p) {
   const vid = parseInt(p.vid);
   if (isNaN(vid) || vid <= 0) return "Please select a variant";
 
-  if (!V.IsGoodFen(p.fen)) return "Bad FEN string";
+  if (!V.IsGoodFen(p.fen)) return "Errors in FEN";
 
   return "";
 }
diff --git a/client/src/data/userCheck.js b/client/src/data/userCheck.js
index 745d6d6e..0dccf1fb 100644
--- a/client/src/data/userCheck.js
+++ b/client/src/data/userCheck.js
@@ -1,12 +1,12 @@
 export function checkNameEmail(o) {
   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.name.match(/^[\w]+$/)) return "Name: alphanumerics and underscore";
   }
 
   if (typeof o.email === "string") {
     if (o.email.length == 0) return "Empty email";
-    if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) return "Bad characters in email";
+    if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/)) return "Invalid email";
   }
 
   return "";
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 0b2d7892..70f8969d 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -5,9 +5,9 @@ export const translations = {
   All: "All",
   Analyse: "Analyse",
   "Any player": "Any player",
+  Apply: "Apply",
   "Are you sure?": "Are you sure?",
   "Authentication successful!": "Authentication successful!",
-  Apply: "Apply",
   "Back to list": "Back to list",
   "Black to move": "Black to move",
   "Black surrender": "Black surrender",
@@ -20,13 +20,11 @@ export const translations = {
   Challenge: "Challenge",
   "Challenge declined": "Challenge declined",
   "Chat here": "Chat here",
-  "Connection token sent. Check your emails!":
-    "Connection token sent. Check your emails!",
+  "Connection token sent. Check your emails!": "Connection token sent. Check your emails!",
   Contact: "Contact",
   "Correspondance challenges": "Correspondance challenges",
   "Correspondance games": "Correspondance games",
-  "Database error: stop private browsing, or update your browser":
-    "Database error: stop private browsing, or update your browser",
+  "Database error: stop private browsing, or update your browser": "Database error: stop private browsing, or update your browser",
   Delete: "Delete",
   Download: "Download",
   Draw: "Draw",
@@ -35,12 +33,14 @@ export const translations = {
   Email: "Email",
   "Email sent!": "Email sent!",
   "Empty message": "Empty message",
+  "Errors in FEN": "Errors in FEN",
   "Example game": "Example game",
   Go: "Go",
   green: "green",
   Hall: "Hall",
   "Highlight last move and checks?": "Highlight last move and checks?",
   Instructions: "Instructions",
+  "Invalid email": "Invalid email",
   "is not online": "is not online",
   Language: "Language",
   "Live challenges": "Live challenges",
@@ -49,14 +49,16 @@ export const translations = {
   Login: "Login",
   Logout: "Logout",
   "Logout successful!": "Logout successful!",
+  "Missing email": "Missing email",
+  "Missing name": "Missing name",
   "Modifications applied!": "Modifications applied!",
   "Move played:": "Move played:",
   "Mutual agreement": "Mutual agreement",
   "My games": "My games",
   "My problems": "My problems",
+  "Name: alphanumerics and underscore": "Name: alphanumerics and underscore",
   "Name or Email": "Name or Email",
-  "New connexion detected: tab now offline":
-    "New connexion detected: tab now offline",
+  "New connexion detected: tab now offline": "New connexion detected: tab now offline",
   "New correspondance game:": "New correspondance game:",
   "New game": "New game",
   "New problem": "New problem",
@@ -71,10 +73,8 @@ export const translations = {
   "Play sounds?": "Play sounds?",
   "Play with?": "Play with?",
   Players: "Players",
-  "Please log in to accept corr challenges":
-    "Please log in to accept corr challenges",
-  "Please log in to play correspondance games":
-    "Please log in to play correspondance games",
+  "Please log in to accept corr challenges": "Please log in to accept corr challenges",
+  "Please log in to play correspondance games": "Please log in to play correspondance games",
   "Please select a variant": "Please select a variant",
   Practice: "Practice",
   "Prefix?": "Prefix?",
@@ -82,8 +82,7 @@ export const translations = {
   Problems: "Problems",
   "participant(s):": "participant(s):",
   Register: "Register",
-  "Registration complete! Please check your emails":
-    "Registration complete! Please check your emails",
+  "Registration complete! Please check your emails": "Registration complete! Please check your emails",
   "Remove game?": "Remove game?",
   Resign: "Resign",
   "Resign the game?": "Resign the game?",
@@ -102,8 +101,7 @@ export const translations = {
   "Terminate game?": "Terminate game?",
   "Three repetitions": "Three repetitions",
   Time: "Time",
-  To: "To",
-  Unknown: "Unknown",
+  "Undetermined result": "Undetermined result",
   Update: "Update",
   "User name": "User name",
   Variant: "Variant",
@@ -115,6 +113,7 @@ export const translations = {
   "Who's there?": "Who's there?",
   With: "With",
   "Write news": "Write news",
+  "Wrong time control": "Wrong time control",
   "Your message": "Your message",
 
   // Variants boxes:
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 17438e50..3c79b660 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -9,7 +9,6 @@ export const translations = {
   "Are you sure?": "¿Está usted seguro?",
   "Authentication successful!": "¡Autenticación exitosa!",
   "Back to list": "Volver a la lista",
-  Black: "Negras",
   "Black to move": "Juegan las negras",
   "Black surrender": "Las negras abandonan",
   "Black win": "Las negras gagnan",
@@ -21,13 +20,11 @@ export const translations = {
   Challenge: "Desafiar",
   "Challenge declined": "Desafío rechazado",
   "Chat here": "Chat aquí",
-  "Connection token sent. Check your emails!":
-    "Token de conexión enviado. ¡Revisa tus correos!",
+  "Connection token sent. Check your emails!": "Token de conexión enviado. ¡Revisa tus correos!",
   Contact: "Contacto",
   "Correspondance challenges": "Desafíos por correspondencia",
   "Correspondance games": "Partidas por correspondencia",
-  "Database error: stop private browsing, or update your browser":
-    "Error de la base de datos: detener la navegación privada, o actualizar su navegador",
+  "Database error: stop private browsing, or update your browser": "Error de la base de datos: detener la navegación privada, o actualizar su navegador",
   Delete: "Borrar",
   Download: "Descargar",
   Draw: "Tablas",
@@ -36,12 +33,14 @@ export const translations = {
   Email: "Email",
   "Email sent!": "¡Email enviado!",
   "Empty message": "Mensaje vacio",
+  "Errors in FEN": "FEN errónea",
   "Example game": "Ejemplo de partida",
   Go: "Go",
   green: "verde",
   Hall: "Salón",
   "Highlight last move and checks?": "¿Resaltar el último movimiento y jaques?",
   Instructions: "Instrucciones",
+  "Invalid email": "Email inválido",
   "is not online": "no está en línea",
   Language: "Idioma",
   "Live challenges": "Desafíos en vivo",
@@ -50,14 +49,16 @@ export const translations = {
   Login: "Login",
   Logout: "Logout",
   "Logout successful!": "¡Desconexión exitosa!",
+  "Missing email": "Email falta",
+  "Missing name": "Nombre falta",
   "Modifications applied!": "¡Modificaciones aplicadas!",
   "Move played:": "Movimiento jugado:",
   "Mutual agreement": "Acuerdo mutuo",
   "My games": "Mis partidas",
   "My problems": "Mis problemas",
+  "Name: alphanumerics and underscore": "Nombre: alfanuméricos y underscore",
   "Name or Email": "Nombre o Email",
-  "New connexion detected: tab now offline":
-    "Nueva conexión detectada: pestaña ahora desconectada",
+  "New connexion detected: tab now offline": "Nueva conexión detectada: pestaña ahora desconectada",
   "New correspondance game:": "Nueva partida por correspondencia:",
   "New game": "Nueva partida",
   "New problem": "Nuevo problema",
@@ -72,10 +73,8 @@ export const translations = {
   "Play sounds?": "¿Permitir sonidos?",
   "Play with?": "¿Jugar con?",
   Players: "Jugadores",
-  "Please log in to accept corr challenges":
-    "Inicia sesión para aceptar los desafíos por correspondencia",
-  "Please log in to play correspondance games":
-    "Inicia sesión para jugar partidas por correspondancia",
+  "Please log in to accept corr challenges": "Inicia sesión para aceptar los desafíos por correspondencia",
+  "Please log in to play correspondance games": "Inicia sesión para jugar partidas por correspondancia",
   "Please select a variant": "Por favor seleccione una variante",
   Practice: "Práctica",
   "Prefix?": "¿Prefijo?",
@@ -83,8 +82,7 @@ export const translations = {
   Problems: "Problemas",
   "participant(s):": "participante(s):",
   Register: "Registrarse",
-  "Registration complete! Please check your emails":
-    "¡Registro completo! Por favor revise sus correos electrónicos",
+  "Registration complete! Please check your emails": "¡Registro completo! Por favor revise sus correos electrónicos",
   "Remove game?": "¿Eliminar la partida?",
   Resign: "Abandonar",
   "Resign the game?": "¿Abandonar la partida?",
@@ -103,20 +101,19 @@ export const translations = {
   "Terminate game?": "¿Terminar la partida?",
   "Three repetitions": "Tres repeticiones",
   Time: "Tiempo",
-  To: "A",
-  Unknown: "Desconocido",
+  "Undetermined result": "Resultado indeterminado",
   Update: "Actualización",
   "User name": "Nombre de usuario",
   Variant: "Variante",
   Variants: "Variantes",
   Versus: "Contra",
-  White: "Blancas",
   "White to move": "Juegan las blancas",
   "White surrender": "Las blancas abandonan",
   "White win": "Las blancas gagnan",
   "Who's there?": "¿Quién está ahí?",
   With: "Con",
   "Write news": "Escribir una news",
+  "Wrong time control": "Cadencia errónea",
   "Your message": "Tu mensaje",
 
   // Variants boxes:
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index e02cf5e9..33503d30 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -9,7 +9,6 @@ export const translations = {
   "Authentication successful!": "Authentification réussie !",
   "Are you sure?": "Étes vous sûr?",
   "Back to list": "Retour à la liste",
-  Black: "Noirs",
   "Black to move": "Trait aux noirs",
   "Black surrender": "Les noirs abandonnent",
   "Black win": "Les noirs gagnent",
@@ -21,29 +20,27 @@ export const translations = {
   Challenge: "Défier",
   "Challenge declined": "Défi refusé",
   "Chat here": "Chattez ici",
-  "Connection token sent. Check your emails!":
-    "Token de connection envoyé. Allez voir vos emails !",
+  "Connection token sent. Check your emails!": "Token de connection envoyé. Allez voir vos emails !",
   Contact: "Contact",
   "Correspondance challenges": "Défis par correspondance",
   "Correspondance games": "Parties par correspondance",
-  "Database error: stop private browsing, or update your browser":
-    "Erreur de base de données : arrêtez la navigation privée, ou mettez à jour votre navigateur",
+  "Database error: stop private browsing, or update your browser": "Erreur de base de données : arrêtez la navigation privée, ou mettez à jour votre navigateur",
   Delete: "Supprimer",
   Download: "Télécharger",
   Draw: "Nulle",
-  "Draw offer only in your turn":
-    "Proposition de nulle seulement sur votre temps",
+  "Draw offer only in your turn": "Proposition de nulle seulement sur votre temps",
   Edit: "Éditer",
   Email: "Email",
   "Email sent!": "Email envoyé !",
   "Empty message": "Message vide",
+  "Errors in FEN": "FEN erronée",
   "Example game": "Partie exemple",
   Go: "Go",
   green: "vert",
   Hall: "Salon",
-  "Highlight last move and checks?":
-    "Mettre en valeur le dernier coup et les échecs ?",
+  "Highlight last move and checks?": "Mettre en valeur le dernier coup et les échecs ?",
   Instructions: "Instructions",
+  "Invalid email": "Email invalide",
   "is not online": "n'est pas en ligne",
   Language: "Langue",
   "Live challenges": "Défis en direct",
@@ -52,14 +49,16 @@ export const translations = {
   Login: "Login",
   Logout: "Logout",
   "Logout successful!": "Déconnection réussie !",
+  "Missing email": "Email manquant",
+  "Missing name": "Nom manquant",
   "Modifications applied!": "Modifications effectuées !",
   "Move played:": "Coup joué :",
   "Mutual agreement": "Accord mutuel",
   "My games": "Mes parties",
   "My problems": "Mes problèmes",
+  "Name: alphanumerics and underscore": "Nom: alphanumériques et underscore",
   "Name or Email": "Nom ou Email",
-  "New connexion detected: tab now offline":
-    "Nouvelle connexion détectée : onglet désormais hors ligne",
+  "New connexion detected: tab now offline": "Nouvelle connexion détectée : onglet désormais hors ligne",
   "New correspondance game:": "Nouvelle partie par corespondance :",
   "New game": "Nouvelle partie",
   "New problem": "Nouveau problème",
@@ -74,10 +73,8 @@ export const translations = {
   "Play sounds?": "Jouer les sons ?",
   "Play with?": "Jouer avec ?",
   Players: "Joueurs",
-  "Please log in to accept corr challenges":
-    "Identifiez vous pour accepter des défis par correspondance",
-  "Please log in to play correspondance games":
-    "Identifiez vous pour jouer des parties par correspondance",
+  "Please log in to accept corr challenges": "Identifiez vous pour accepter des défis par correspondance",
+  "Please log in to play correspondance games": "Identifiez vous pour jouer des parties par correspondance",
   "Please select a variant": "Sélectionnez une variante SVP",
   Practice: "Pratiquer",
   "Prefix?": "Préfixe ?",
@@ -85,8 +82,7 @@ export const translations = {
   Problems: "Problèmes",
   "participant(s):": "participant(s) :",
   Register: "S'enregistrer",
-  "Registration complete! Please check your emails":
-    "Enregistrement terminé ! Allez voir vos emails",
+  "Registration complete! Please check your emails": "Enregistrement terminé ! Allez voir vos emails",
   "Remove game?": "Supprimer la partie ?",
   Resign: "Abandonner",
   "Resign the game?": "Abandonner la partie ?",
@@ -105,20 +101,19 @@ export const translations = {
   "Terminate game?": "Stopper la partie ?",
   "Three repetitions": "Triple répétition",
   Time: "Temps",
-  To: "À",
-  Unknown: "Inconnu",
+  "Undetermined result": "Résultat indéterminé",
   Update: "Mise à jour",
   "User name": "Nom d'utilisateur",
   Variant: "Variante",
   Variants: "Variantes",
   Versus: "Contre",
-  White: "Blancs",
   "White to move": "Trait aux blancs",
   "White surrender": "Les blancs abandonnent",
   "White win": "Les blancs gagnent",
   "Who's there?": "Qui est là ?",
   With: "Avec",
   "Write news": "Écrire une news",
+  "Wrong time control": "Cadence erronée",
   "Your message": "Votre message",
 
   // Variants boxes:
diff --git a/client/src/utils/scoring.js b/client/src/utils/scoring.js
index 9c6d25fa..c659b7d1 100644
--- a/client/src/utils/scoring.js
+++ b/client/src/utils/scoring.js
@@ -12,7 +12,7 @@ export function getScoreMessage(score) {
       eogMessage = "Draw";
       break;
     case "?":
-      eogMessage = "Unknown";
+      eogMessage = "Undetermined result";
       break;
   }
   return eogMessage;
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index 6cc58e97..82bd769b 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -563,28 +563,32 @@ export default {
         case "newgame": {
           // NOTE: it may be live or correspondance
           const game = data.data;
-          let locGame = this.games.find(g => g.id == game.id);
-          if (!locGame) {
-            let newGame = game;
-            newGame.type = this.classifyObject(game);
-            newGame.vname = this.getVname(game.vid);
-            if (!game.score)
-              //if new game from Hall
-              newGame.score = "*";
-            newGame.rids = [game.rid];
-            delete newGame["rid"];
-            this.games.push(newGame);
-            if (
-              (newGame.type == "live" && this.gdisplay == "corr") ||
-              (newGame.type == "corr" && this.gdisplay == "live")
-            ) {
-              document
-                .getElementById("btnG" + newGame.type)
-                .classList.add("somethingnew");
+          // Ignore games where I play (corr games)
+          if (game.players.every(p => p.id != this.st.user.id))
+          {
+            let locGame = this.games.find(g => g.id == game.id);
+            if (!locGame) {
+              let newGame = game;
+              newGame.type = this.classifyObject(game);
+              newGame.vname = this.getVname(game.vid);
+              if (!game.score)
+                //if new game from Hall
+                newGame.score = "*";
+              newGame.rids = [game.rid];
+              delete newGame["rid"];
+              this.games.push(newGame);
+              if (
+                (newGame.type == "live" && this.gdisplay == "corr") ||
+                (newGame.type == "corr" && this.gdisplay == "live")
+              ) {
+                document
+                  .getElementById("btnG" + newGame.type)
+                  .classList.add("somethingnew");
+              }
+            } else {
+              // Append rid (if not already in list)
+              if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid);
             }
-          } else {
-            // Append rid (if not already in list)
-            if (!locGame.rids.includes(game.rid)) locGame.rids.push(game.rid);
           }
           break;
         }
diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue
index e17bead7..3a700eab 100644
--- a/client/src/views/Problems.vue
+++ b/client/src/views/Problems.vue
@@ -8,7 +8,7 @@ main
     role="dialog"
     data-checkbox="modalNewprob"
   )
-    .card(@keyup.enter="sendProblem()")
+    .card
       label#closeNewprob.modal-close(for="modalNewprob")
       fieldset
         label(for="selectVariant") {{ st.tr["Variant"] }}
@@ -45,7 +45,7 @@ main
       button(@click="sendProblem()") {{ st.tr["Send"] }}
       #dialog.text-center {{ st.tr[infoMsg] }}
   .row(v-if="showOne")
-    .col-sm-12.col-md-10.col-md-offset-2
+    .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
       #topPage
         span.vname {{ curproblem.vname }}
         span.uname ({{ curproblem.uname }})
@@ -60,7 +60,7 @@ main
           @click="deleteProblem(curproblem)"
         )
           | {{ st.tr["Delete"] }}
-      p.clickable(
+      p.oneInstructions.clickable(
         v-html="parseHtml(curproblem.instruction)"
         @click="curproblem.showSolution=!curproblem.showSolution"
       )
@@ -273,7 +273,7 @@ export default {
     },
     displayProblem: function(p) {
       return (
-        (this.selectedVar == 0 || p.vid == this.selectedVar) &&
+        (!this.selectedVar || p.vid == this.selectedVar) &&
         ((this.onlyMines && p.uid == this.st.user.id) ||
           (!this.onlyMines && p.uid != this.st.user.id))
       );
@@ -293,7 +293,7 @@ export default {
     sendProblem: function() {
       const error = checkProblem(this.curproblem);
       if (error) {
-        alert(error);
+        alert(this.st.tr[error]);
         return;
       }
       const edit = this.curproblem.id > 0;
@@ -358,6 +358,11 @@ textarea
   & > *
     margin: 0
 
+p.oneInstructions
+  margin: 0
+  padding: 2px 5px
+  background-color: lightgreen
+
 #topPage
   span.vname
     font-weight: bold
diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue
index 8ac03a91..f864e9e4 100644
--- a/client/src/views/Rules.vue
+++ b/client/src/views/Rules.vue
@@ -29,9 +29,9 @@ main
         v-html="content"
       )
   ComputerGame(
+    ref="compgame"
     v-show="display=='computer'"
     :game-info="gameInfo"
-    @game-over="stopGame"
     @game-stopped="gameStopped"
   )
 </template>
@@ -54,8 +54,7 @@ export default {
       gameInfo: {
         vname: "",
         mode: "versus",
-        fen: "",
-        score: "*"
+        fen: ""
       }
     };
   },
@@ -119,12 +118,11 @@ export default {
       this.gameInProgress = true;
       this.display = "computer";
       this.gameInfo.mode = mode;
-      this.gameInfo.score = "*";
-      this.gameInfo.fen = V.GenRandInitFen();
+      this.$set(this.gameInfo, "fen", V.GenRandInitFen());
     },
     // user is willing to stop the game:
-    stopGame: function(score) {
-      this.gameInfo.score = score || "?";
+    stopGame: function() {
+      this.$refs["compgame"].gameOver("?", "Undetermined result");
     },
     // The game is effectively stopped:
     gameStopped: function() {
diff --git a/server/app.js b/server/app.js
index 4f9d167d..b8a6aec7 100644
--- a/server/app.js
+++ b/server/app.js
@@ -54,17 +54,12 @@ app.use(function(req, res, next) {
 
 // error handler
 app.use(function(err, req, res, next) {
-  // set locals, only providing error in development
-  res.locals.message = err.message;
-  res.locals.error = (req.app.get('env') === 'development' ? err : {});
-  // render the error page
   res.status(err.status || 500);
-  res.send(`
-    <!doctype html>
-    <h1>= message</h1>
-    <h2>= error.status</h2>
-    <pre>#{error.stack}</pre>
-  `);
+  res.send(
+    "<h1>" + err.message + "</h1>" +
+    "<h2>" + err.status + "</h2>" +
+    "<pre>" + err.stack + "</pre>"
+  );
 });
 
 module.exports = app;
diff --git a/server/models/Challenge.js b/server/models/Challenge.js
index 0a375c81..b7c20b88 100644
--- a/server/models/Challenge.js
+++ b/server/models/Challenge.js
@@ -16,18 +16,14 @@ const ChallengeModel =
 {
   checkChallenge: function(c)
   {
-    if (!c.vid.toString().match(/^[0-9]+$/))
-      return "Wrong variant ID";
-    if (!c.cadence.match(/^[0-9dhms +]+$/))
-      return "Wrong characters in time control";
-    if (!c.fen.match(/^[a-zA-Z0-9, /-]*$/))
-      return "Bad FEN string";
-    if (!!c.to)
-      return UserModel.checkNameEmail({name: c.to});
-    return "";
+    return (
+      c.vid.toString().match(/^[0-9]+$/) &&
+      c.cadence.match(/^[0-9dhms +]+$/) &&
+      c.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
+      (!c.to || UserModel.checkNameEmail({name: c.to}))
+    );
   },
 
-  // fen cannot be undefined
   create: function(c, cb)
   {
     db.serialize(function() {
@@ -38,26 +34,12 @@ const ChallengeModel =
         "(" + Date.now() + "," + c.uid + "," + (!!c.to ? c.to + "," : "") +
           c.vid + ",'" + c.fen + "','" + c.cadence + "')";
       db.run(query, function(err) {
-        return cb(err, {cid: this.lastID});
+        cb(err, {cid: this.lastID});
       });
     });
   },
 
-  getOne: function(id, cb)
-  {
-    db.serialize(function() {
-      const query =
-        "SELECT * " +
-        "FROM Challenges " +
-        "WHERE id = " + id;
-      db.get(query, (err,challenge) => {
-        return cb(err, challenge);
-      });
-    });
-  },
-
-  // All challenges except where target is defined and not me,
-  // and I'm not the sender.
+  // All challenges related to user with ID uid
   getByUser: function(uid, cb)
   {
     db.serialize(function() {
@@ -68,7 +50,7 @@ const ChallengeModel =
           " OR uid = " + uid +
           " OR target = " + uid;
       db.all(query, (err,challenges) => {
-        return cb(err, challenges);
+        cb(err, challenges);
       });
     });
   },
@@ -83,7 +65,7 @@ const ChallengeModel =
     });
   },
 
-  safeRemove: function(id, uid, cb)
+  safeRemove: function(id, uid)
   {
     db.serialize(function() {
       const query =
@@ -91,10 +73,8 @@ const ChallengeModel =
         "FROM Challenges " +
         "WHERE id = " + id + " AND uid = " + uid;
       db.get(query, (err,chall) => {
-        if (!chall)
-          return cb({errmsg: "Not your challenge"});
-        ChallengeModel.remove(id);
-        cb(null);
+        if (!err && chall)
+          ChallengeModel.remove(id);
       });
     });
   },
diff --git a/server/models/Game.js b/server/models/Game.js
index fb249a7c..ae91ac94 100644
--- a/server/models/Game.js
+++ b/server/models/Game.js
@@ -34,17 +34,13 @@ const UserModel = require("./User");
 const GameModel =
 {
   checkGameInfo: function(g) {
-    if (!g.vid.toString().match(/^[0-9]+$/))
-      return "Wrong variant ID";
-    if (!g.cadence.match(/^[0-9dhms +]+$/))
-      return "Wrong characters in time control";
-    if (!g.fen.match(/^[a-zA-Z0-9, /-]*$/))
-      return "Bad FEN string";
-    if (g.players.length != 2)
-      return "Need exactly 2 players";
-    if (g.players.some(p => !p.id.toString().match(/^[0-9]+$/)))
-      return "Wrong characters in player ID";
-    return "";
+    return (
+      g.vid.toString().match(/^[0-9]+$/) &&
+      g.cadence.match(/^[0-9dhms +]+$/) &&
+      g.fen.match(/^[a-zA-Z0-9, /-]*$/) &&
+      g.players.length == 2 &&
+      g.players.every(p => p.id.toString().match(/^[0-9]+$/))
+    );
   },
 
   create: function(vid, fen, cadence, players, cb)
@@ -56,16 +52,19 @@ const GameModel =
         "VALUES " +
         "(" + vid + ",'" + fen + "','" + fen + "','*','" + cadence + "'," + Date.now() + ",'')";
       db.run(query, function(err) {
-        if (!!err)
-          return cb(err);
-        players.forEach((p,idx) => {
-          const color = (idx==0 ? "w" : "b");
-          query =
-            "INSERT INTO Players VALUES " +
-            "(" + this.lastID + "," + p.id + ",'" + color + "')";
-          db.run(query);
-        });
-        cb(null, {gid: this.lastID});
+        if (err)
+          cb(err)
+        else
+        {
+          players.forEach((p,idx) => {
+            const color = (idx==0 ? "w" : "b");
+            query =
+              "INSERT INTO Players VALUES " +
+              "(" + this.lastID + "," + p.id + ",'" + color + "')";
+            db.run(query);
+          });
+          cb(null, {gid: this.lastID});
+        }
       });
     });
   },
@@ -73,10 +72,9 @@ const GameModel =
   // TODO: some queries here could be async
   getOne: function(id, light, cb)
   {
+    // NOTE: ignoring errors (shouldn't happen at this stage)
     db.serialize(function() {
       let query =
-        // NOTE: g.scoreMsg can be NULL
-        // (in this case score = "*" and no reason to look at it)
         "SELECT g.id, g.vid, g.fen, g.fenStart, g.cadence, g.created, g.score, " +
           "g.scoreMsg, g.drawOffer, v.name AS vname " +
         "FROM Games g " +
@@ -84,8 +82,6 @@ const GameModel =
         "  ON g.vid = v.id " +
         "WHERE g.id = " + id;
       db.get(query, (err,gameInfo) => {
-        if (!!err)
-          return cb(err);
         query =
           "SELECT p.uid, p.color, u.name " +
           "FROM Players p " +
@@ -93,41 +89,39 @@ const GameModel =
           "  ON p.uid = u.id " +
           "WHERE p.gid = " + id;
         db.all(query, (err2,players) => {
-          if (!!err2)
-            return cb(err2);
           if (light)
           {
             const game = Object.assign({},
               gameInfo,
               {players: players}
             );
-            return cb(null, game);
+            cb(null, game);
           }
-          query =
-            "SELECT squares, played, idx " +
-            "FROM Moves " +
-            "WHERE gid = " + id;
-          db.all(query, (err3,moves) => {
-            if (!!err3)
-              return cb(err3);
+          else
+          {
+            // Full game requested:
             query =
-              "SELECT msg, name, added " +
-              "FROM Chats " +
+              "SELECT squares, played, idx " +
+              "FROM Moves " +
               "WHERE gid = " + id;
-            db.all(query, (err4,chats) => {
-              if (!!err4)
-                return cb(err4);
-              const game = Object.assign({},
-                gameInfo,
-                {
-                  players: players,
-                  moves: moves,
-                  chats: chats,
-                }
-              );
-              return cb(null, game);
+            db.all(query, (err3,moves) => {
+              query =
+                "SELECT msg, name, added " +
+                "FROM Chats " +
+                "WHERE gid = " + id;
+              db.all(query, (err4,chats) => {
+                const game = Object.assign({},
+                  gameInfo,
+                  {
+                    players: players,
+                    moves: moves,
+                    chats: chats,
+                  }
+                );
+                cb(null, game);
+              });
             });
-          });
+          }
         });
       });
     });
@@ -156,21 +150,22 @@ const GameModel =
           (excluded ? " = 0" : " > 0");
       }
       db.all(query, (err,gameIds) => {
-        if (!!err || gameIds.length == 0)
-          return cb(err, []);
-        let gameArray = [];
-        let kounter = 0;
-        for (let i=0; i<gameIds.length; i++)
+        if (err || gameIds.length == 0)
+          cb(err, []);
+        else
         {
-          GameModel.getOne(gameIds[i]["gid"], true, (err2,game) => {
-            if (!!err2)
-              return cb(err2);
-            gameArray.push(game);
-            kounter++; //TODO: let's hope this is atomic?!
-            // Call callback function only when gameArray is complete:
-            if (kounter == gameIds.length)
-              return cb(null, gameArray);
-          });
+          let gameArray = [];
+          let gCounter = 0;
+          for (let i=0; i<gameIds.length; i++)
+          {
+            GameModel.getOne(gameIds[i]["gid"], true, (err2,game) => {
+              gameArray.push(game);
+              gCounter++; //TODO: let's hope this is atomic?!
+              // Call callback function only when gameArray is complete:
+              if (gCounter == gameIds.length)
+                cb(null, gameArray);
+            });
+          }
         }
       });
     });
@@ -192,27 +187,27 @@ const GameModel =
   checkGameUpdate: function(obj)
   {
     // Check all that is possible (required) in obj:
-    if (!!obj.move)
-    {
-      if (!obj.move.played.toString().match(/^[0-9]+$/))
-        return "Wrong move played time";
-      if (!obj.move.idx.toString().match(/^[0-9]+$/))
-        return "Wrong move index";
-    }
-    if (!!obj.drawOffer && !obj.drawOffer.match(/^[wbtn]$/))
-      return "Wrong draw offer format";
-    if (!!obj.fen && !obj.fen.match(/^[a-zA-Z0-9, /-]*$/))
-      return "Wrong FEN string";
-    if (!!obj.score && !obj.score.match(/^[012?*\/-]+$/))
-      return "Wrong characters in score";
-    if (!!obj.scoreMsg && !obj.scoreMsg.match(/^[a-zA-Z ]+$/))
-      return "Wrong characters in score message";
-    if (!!obj.chat)
-      return UserModel.checkNameEmail({name: obj.chat.name});
-    return "";
+    return (
+      (
+        !obj.move || (
+          obj.move.played.toString().match(/^[0-9]+$/) &&
+          obj.move.idx.toString().match(/^[0-9]+$/)
+        )
+      ) && (
+        !obj.drawOffer || obj.drawOffer.match(/^[wbtn]$/)
+      ) && (
+        !obj.fen || obj.fen.match(/^[a-zA-Z0-9, /-]*$/)
+      ) && (
+        !obj.score || obj.score.match(/^[012?*\/-]+$/)
+      ) && (
+        !obj.scoreMsg || obj.scoreMsg.match(/^[a-zA-Z ]+$/)
+      ) && (
+        !obj.chat || UserModel.checkNameEmail({name: obj.chat.name})
+      )
+    );
   },
 
-  // obj can have fields move, chat, fen, drawOffer and/or score
+  // obj can have fields move, chat, fen, drawOffer and/or score + message
   update: function(id, obj)
   {
     db.parallelize(function() {
@@ -220,21 +215,19 @@ const GameModel =
         "UPDATE Games " +
         "SET ";
       let modifs = "";
-      if (!!obj.message)
-        modifs += "message = message || ' ' || '" + obj.message + "',";
       // NOTE: if drawOffer is set, we should check that it's player's turn
       // A bit overcomplicated. Let's trust the client on that for now...
-      if (!!obj.drawOffer)
+      if (obj.drawOffer)
       {
         if (obj.drawOffer == "n") //Special "None" update
           obj.drawOffer = "";
         modifs += "drawOffer = '" + obj.drawOffer + "',";
       }
-      if (!!obj.fen)
+      if (obj.fen)
         modifs += "fen = '" + obj.fen + "',";
-      if (!!obj.score)
+      if (obj.score)
         modifs += "score = '" + obj.score + "',";
-      if (!!obj.scoreMsg)
+      if (obj.scoreMsg)
         modifs += "scoreMsg = '" + obj.scoreMsg + "',";
       modifs = modifs.slice(0,-1); //remove last comma
       if (modifs.length > 0)
@@ -242,7 +235,7 @@ const GameModel =
         query += modifs + " WHERE id = " + id;
         db.run(query);
       }
-      if (!!obj.move)
+      if (obj.move)
       {
         const m = obj.move;
         query =
@@ -250,7 +243,7 @@ const GameModel =
           "(" + id + ",?," + m.played + "," + m.idx + ")";
         db.run(query, JSON.stringify(m.squares));
       }
-      if (!!obj.chat)
+      if (obj.chat)
       {
         query =
           "INSERT INTO Chats (gid, msg, name, added) VALUES ("
diff --git a/server/models/News.js b/server/models/News.js
index 3f98fd67..12f4bf59 100644
--- a/server/models/News.js
+++ b/server/models/News.js
@@ -19,7 +19,7 @@ const NewsModel =
           "VALUES " +
         "(" + Date.now() + "," + uid + ",?)";
       db.run(query, content, function(err) {
-        return cb(err, {nid: this.lastID});
+        cb(err, {nid: this.lastID});
       });
     });
   },
@@ -33,29 +33,29 @@ const NewsModel =
         "WHERE id > " + cursor + " " +
         "LIMIT 10"; //TODO: 10 currently hard-coded
       db.all(query, (err,newsList) => {
-        return cb(err, newsList);
+        cb(err, newsList);
       });
     });
   },
 
-  update: function(news, cb)
+  update: function(news)
   {
     db.serialize(function() {
       let query =
         "UPDATE News " +
         "SET content = ? " +
         "WHERE id = " + news.id;
-      db.run(query, news.content, cb);
+      db.run(query, news.content);
     });
   },
 
-  remove: function(id, cb)
+  remove: function(id)
   {
     db.serialize(function() {
       const query =
         "DELETE FROM News " +
         "WHERE id = " + id;
-      db.run(query, cb);
+      db.run(query);
     });
   },
 }
diff --git a/server/models/Problem.js b/server/models/Problem.js
index 8460fec2..136fb649 100644
--- a/server/models/Problem.js
+++ b/server/models/Problem.js
@@ -15,13 +15,11 @@ const ProblemModel =
 {
   checkProblem: function(p)
   {
-    if (!p.id.toString().match(/^[0-9]+$/))
-      return "Wrong problem ID";
-    if (!p.vid.toString().match(/^[0-9]+$/))
-      return "Wrong variant ID";
-    if (!p.fen.match(/^[a-zA-Z0-9, /-]*$/))
-      return "Bad FEN string";
-    return "";
+    return (
+      p.id.toString().match(/^[0-9]+$/) &&
+      p.vid.toString().match(/^[0-9]+$/) &&
+      p.fen.match(/^[a-zA-Z0-9, /-]*$/)
+    );
   },
 
   create: function(p, cb)
@@ -33,7 +31,7 @@ const ProblemModel =
           "VALUES " +
         "(" + Date.now() + "," + p.uid + "," + p.vid + ",'" + p.fen  + "',?,?)";
       db.run(query, [p.instruction,p.solution], function(err) {
-        return cb(err, {pid: this.lastID});
+        cb(err, {pid: this.lastID});
       });
     });
   },
@@ -45,7 +43,7 @@ const ProblemModel =
         "SELECT * " +
         "FROM Problems";
       db.all(query, (err,problems) => {
-        return cb(err, problems);
+        cb(err, problems);
       });
     });
   },
@@ -58,49 +56,33 @@ const ProblemModel =
         "FROM Problems " +
         "WHERE id = " + id;
       db.get(query, (err,problem) => {
-        return cb(err, problem);
+        cb(err, problem);
       });
     });
   },
 
-  update: function(prob, cb)
+  safeUpdate: function(prob, uid)
   {
     db.serialize(function() {
-      let query =
+      const query =
         "UPDATE Problems " +
         "SET " +
           "vid = " + prob.vid + "," +
           "fen = '" + prob.fen + "'," +
           "instruction = ?," +
           "solution = ? " +
-        "WHERE id = " + prob.id;
-      db.run(query, [prob.instruction,prob.solution], cb);
+        "WHERE id = " + prob.id + " AND uid = " + uid;
+      db.run(query, [prob.instruction,prob.solution]);
     });
   },
 
-  remove: function(id)
+  safeRemove: function(id, uid)
   {
     db.serialize(function() {
       const query =
         "DELETE FROM Problems " +
-        "WHERE id = " + id;
-      db.run(query);
-    });
-  },
-
-  safeRemove: function(id, uid, cb)
-  {
-    db.serialize(function() {
-      const query =
-        "SELECT 1 " +
-        "FROM Problems " +
         "WHERE id = " + id + " AND uid = " + uid;
-      db.get(query, (err,prob) => {
-        if (!prob)
-          return cb({errmsg: "Not your problem"});
-        ProblemModel.remove(id);
-        cb(null);
-      });
+      db.run(query);
     });
   },
 }
diff --git a/server/models/User.js b/server/models/User.js
index 6c0b1539..f3adb31d 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -19,40 +19,26 @@ const UserModel =
 {
   checkNameEmail: function(o)
   {
-    if (typeof o.name === "string")
-    {
-      if (o.name.length == 0)
-        return "Empty name";
-      if (!o.name.match(/^[\w]+$/))
-        return "Bad characters in name";
-    }
-    if (typeof o.email === "string")
-    {
-      if (o.email.length == 0)
-        return "Empty email";
-      if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
-        return "Bad characters in email";
-    }
-    return ""; //NOTE: not required, but more consistent... (?!)
+    return (
+      (!o.name || o.name.match(/^[\w]+$/)) &&
+      (!o.email || o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+    );
   },
 
-  // NOTE: parameters are already cleaned (in controller), thus no sanitization here
-  create: function(name, email, notify, callback)
+  create: function(name, email, notify, cb)
   {
     db.serialize(function() {
-      const insertQuery =
+      const query =
         "INSERT INTO Users " +
         "(name, email, notify, created) VALUES " +
-        "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
-      db.run(insertQuery, err => {
-        if (!!err)
-          return callback(err);
-        db.get("SELECT last_insert_rowid() AS rowid", callback);
+        "('" + name + "','" + email + "'," + notify + "," + Date.now() + ")";
+      db.run(query, function(err) {
+        cb(err, {uid: this.lastID});
       });
     });
   },
 
-  // Find one user (by id, name, email, or token)
+  // Find one user by id, name, email, or token
   getOne: function(by, value, cb)
   {
     const delimiter = (typeof value === "string" ? "'" : "");
@@ -78,14 +64,14 @@ const UserModel =
   /////////
   // MODIFY
 
-  setLoginToken: function(token, uid, cb)
+  setLoginToken: function(token, uid)
   {
     db.serialize(function() {
       const query =
         "UPDATE Users " +
-        "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
+        "SET loginToken = '" + token + "',loginTime = " + Date.now() + " " +
         "WHERE id = " + uid;
-      db.run(query, cb);
+      db.run(query);
     });
   },
 
@@ -94,28 +80,26 @@ const UserModel =
   // TODO: option would be to reset all tokens periodically, e.g. every 3 months
   trySetSessionToken: function(uid, cb)
   {
-    // Also empty the login token to invalidate future attempts
     db.serialize(function() {
-      const querySessionToken =
+      let query =
         "SELECT sessionToken " +
         "FROM Users " +
         "WHERE id = " + uid;
-      db.get(querySessionToken, (err,ret) => {
-        if (!!err)
-          return cb(err);
+      db.get(query, (err,ret) => {
         const token = ret.sessionToken || genToken(params.token.length);
-        const queryUpdate =
+        query =
           "UPDATE Users " +
+          // Also empty the login token to invalidate future attempts
           "SET loginToken = NULL" +
           (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
           "WHERE id = " + uid;
-        db.run(queryUpdate);
-        cb(null, token);
+        db.run(query);
+        cb(token);
       });
     });
   },
 
-  updateSettings: function(user, cb)
+  updateSettings: function(user)
   {
     db.serialize(function() {
       const query =
@@ -124,7 +108,7 @@ const UserModel =
         ", email = '" + user.email + "'" +
         ", notify = " + user.notify + " " +
         "WHERE id = " + user.id;
-      db.run(query, cb);
+      db.run(query);
     });
   },
 
@@ -142,9 +126,8 @@ const UserModel =
   tryNotify: function(id, message)
   {
     UserModel.getOne("id", id, (err,user) => {
-      if (!!err || !user.notify)
-        return; //NOTE: error is ignored here
-      UserModel.notify(user, message);
+      if (!err && user.notify)
+        UserModel.notify(user, message);
     });
   },
 
diff --git a/server/routes/challenges.js b/server/routes/challenges.js
index 4bbce8e2..efc69701 100644
--- a/server/routes/challenges.js
+++ b/server/routes/challenges.js
@@ -1,60 +1,63 @@
-// AJAX methods to get, create, update or delete a challenge
-
 let router = require("express").Router();
 const access = require("../utils/access");
 const ChallengeModel = require("../models/Challenge");
 const UserModel = require("../models/User"); //for name check
 const params = require("../config/parameters");
 
-router.get("/challenges", (req,res) => {
-  if (!req.query["uid"].match(/^[0-9]+$/))
-    res.json({errmsg: "Bad user ID"});
-  ChallengeModel.getByUser(req.query["uid"], (err,challenges) => {
-    res.json(err || {challenges:challenges});
-  });
-});
-
 router.post("/challenges", access.logged, access.ajax, (req,res) => {
-  const error = ChallengeModel.checkChallenge(req.body.chall);
-  if (!!error)
-    return res.json({errmsg:error});
-  let challenge =
+  if (ChallengeModel.checkChallenge(req.body.chall))
   {
-    fen: req.body.chall.fen,
-    cadence: req.body.chall.cadence,
-    vid: req.body.chall.vid,
-    uid: req.userId,
-    to: req.body.chall.to, //string: user name (may be empty)
-  };
-  const insertChallenge = () => {
-    ChallengeModel.create(challenge, (err,ret) => {
-      return res.json(err || {cid:ret.cid});
-    });
-  };
-  if (!!req.body.chall.to)
-  {
-    UserModel.getOne("name", challenge.to, (err,user) => {
-      if (!!err || !user)
-        return res.json(err || {errmsg: "Typo in player name"});
-      challenge.to = user.id; //ready now to insert challenge
+    let challenge =
+    {
+      fen: req.body.chall.fen,
+      cadence: req.body.chall.cadence,
+      vid: req.body.chall.vid,
+      uid: req.userId,
+      to: req.body.chall.to, //string: user name (may be empty)
+    };
+    const insertChallenge = () => {
+      ChallengeModel.create(challenge, (err,ret) => {
+        res.json(err || {cid:ret.cid});
+      });
+    };
+    if (req.body.chall.to)
+    {
+      UserModel.getOne("name", challenge.to, (err,user) => {
+        if (!!err || !user)
+          res.json(err || {errmsg: "Typo in player name"});
+        else
+        {
+          challenge.to = user.id; //ready now to insert challenge
+          insertChallenge();
+          if (user.notify)
+            UserModel.notify(
+              user,
+              "New challenge: " + params.siteURL + "/#/?disp=corr");
+        }
+      });
+    }
+    else
       insertChallenge();
-      if (user.notify)
-        UserModel.notify(
-          user,
-          "New challenge: " + params.siteURL + "/#/?disp=corr");
+  }
+});
+
+router.get("/challenges", access.ajax, (req,res) => {
+  const uid = req.query.uid;
+  if (uid.match(/^[0-9]+$/))
+  {
+    ChallengeModel.getByUser(uid, (err,challenges) => {
+      res.json(err || {challenges:challenges});
     });
   }
-  else
-    insertChallenge();
 });
 
 router.delete("/challenges", access.logged, access.ajax, (req,res) => {
   const cid = req.query.id;
-  if (!cid.match(/^[0-9]+$/))
-    res.json({errmsg: "Bad challenge ID"});
-  ChallengeModel.safeRemove(cid, req.userId, err => {
-    res.json(err || {}); //TODO: just "return err" because is empty if no errors
-  });
+  if (cid.match(/^[0-9]+$/))
+  {
+    ChallengeModel.safeRemove(cid, req.userId);
+    res.json({});
+  }
 });
 
 module.exports = router;
diff --git a/server/routes/games.js b/server/routes/games.js
index f130f787..8bd9131e 100644
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -2,100 +2,84 @@ let router = require("express").Router();
 const UserModel = require("../models/User");
 const ChallengeModel = require('../models/Challenge');
 const GameModel = require('../models/Game');
-const VariantModel = require('../models/Variant');
 const access = require("../utils/access");
 const params = require("../config/parameters");
 
 // From main hall, start game between players 0 and 1
 router.post("/games", access.logged, access.ajax, (req,res) => {
   const gameInfo = req.body.gameInfo;
-  if (!Array.isArray(gameInfo.players) ||
-    gameInfo.players.every(p => p.id != req.userId))
-  {
-    return res.json({errmsg: "Cannot start someone else's game"});
-  }
   const cid = req.body.cid;
-  // Check all entries of gameInfo + cid:
-  let error = GameModel.checkGameInfo(gameInfo);
-  if (!error)
-  {
-    if (!cid.toString().match(/^[0-9]+$/))
-      error = "Wrong challenge ID";
-  }
-  if (!!error)
-    return res.json({errmsg:error});
-  ChallengeModel.remove(cid);
-  GameModel.create(
-    gameInfo.vid, gameInfo.fen, gameInfo.cadence, gameInfo.players,
-    (err,ret) => {
-      access.checkRequest(res, err, ret, "Cannot create game", () => {
+  if (
+    Array.isArray(gameInfo.players) &&
+    gameInfo.players.some(p => p.id == req.userId) &&
+    cid.toString().match(/^[0-9]+$/) &&
+    GameModel.checkGameInfo(gameInfo)
+  ) {
+    ChallengeModel.remove(cid);
+    GameModel.create(
+      gameInfo.vid, gameInfo.fen, gameInfo.cadence, gameInfo.players,
+      (err,ret) => {
         const oppIdx = (gameInfo.players[0].id == req.userId ? 1 : 0);
         const oppId = gameInfo.players[oppIdx].id;
         UserModel.tryNotify(oppId,
           "Game started: " + params.siteURL + "/#/game/" + ret.gid);
         res.json({gameId: ret.gid});
-      });
-    }
-  );
+      }
+    );
+  }
 });
 
 router.get("/games", access.ajax, (req,res) => {
   const gameId = req.query["gid"];
-  if (!!gameId)
+  if (gameId)
   {
-    if (!gameId.match(/^[0-9]+$/))
-      return res.json({errmsg: "Wrong game ID"});
-    GameModel.getOne(gameId, false, (err,game) => {
-      access.checkRequest(res, err, game, "Game not found", () => {
+    if (gameId.match(/^[0-9]+$/))
+    {
+      GameModel.getOne(gameId, false, (err,game) => {
         res.json({game: game});
       });
-    });
+    }
   }
   else
   {
     // Get by (non-)user ID:
     const userId = req.query["uid"];
-    if (!userId.match(/^[0-9]+$/))
-      return res.json({errmsg: "Wrong user ID"});
-    const excluded = !!req.query["excluded"];
-    GameModel.getByUser(userId, excluded, (err,games) => {
-      if (!!err)
-        return res.json({errmsg: err.errmsg || err.toString()});
-      res.json({games: games});
-    });
+    if (userId.match(/^[0-9]+$/))
+    {
+      const excluded = !!req.query["excluded"];
+      GameModel.getByUser(userId, excluded, (err,games) => {
+        res.json({games: games});
+      });
+    }
   }
 });
 
-// New move + fen update + score, potentially
-// TODO: if newmove fail, takeback in GUI
+// New move + fen update + score + chats...
 router.put("/games", access.logged, access.ajax, (req,res) => {
   const gid = req.body.gid;
-  let error = "";
-  if (!gid.toString().match(/^[0-9]+$/))
-    error = "Wrong game ID";
   const obj = req.body.newObj;
-  error = GameModel.checkGameUpdate(obj);
-  if (!!error)
-    return res.json({errmsg: error});
-  GameModel.update(gid, obj); //no callback here (several operations)
-  if (!!obj.move || !!obj.score)
+  if (gid.toString().match(/^[0-9]+$/) && GameModel.checkGameUpdate(obj))
   {
-    // Notify opponent if he enabled notifications:
     GameModel.getPlayers(gid, (err,players) => {
-      if (!err)
+      if (players.some(p => p.id == req.userId))
       {
-        const oppid = (players[0].uid == req.userId
-          ? players[1].uid
-          : players[0].uid);
-        const messagePrefix = (!!obj.move
-          ? "New move in game: "
-          : "Game ended: ");
-        UserModel.tryNotify(oppid,
-          messagePrefix + params.siteURL + "/#/game/" + gid);
+        GameModel.update(gid, obj);
+        if (obj.move || obj.score)
+        {
+          // Notify opponent if he enabled notifications:
+          const oppid = players[0].uid == req.userId
+            ? players[1].uid
+            : players[0].uid;
+          const messagePrefix = obj.move
+            ? "New move in game: "
+            : "Game ended: ";
+          UserModel.tryNotify(oppid,
+            messagePrefix + params.siteURL + "/#/game/" + gid);
+        }
+        res.json({});
       }
     });
   }
-  res.json({}); //TODO: what if some update action fails?
 });
 
 module.exports = router;
diff --git a/server/routes/messages.js b/server/routes/messages.js
index 3f13db4d..9355ff92 100644
--- a/server/routes/messages.js
+++ b/server/routes/messages.js
@@ -1,23 +1,17 @@
-// Router for contact form sending
-
 let router = require("express").Router();
-const mailer = require(__dirname.replace("/routes", "/utils/mailer"));
+const access = require("../utils/access");
+const sendEmail = require(__dirname.replace("/routes", "/utils/mailer"));
 const params = require(__dirname.replace("/routes", "/config/parameters"));
 
 // Send a message through contact form
-router.post("/messages", (req,res,next) => {
-  if (!req.xhr)
-    return res.json({errmsg: "Unauthorized access"});
+router.post("/messages", access.ajax, (req,res) => {
   const from = req.body["email"];
   // Replace potential newline characters in subject
   const subject = req.body["subject"].replace(/\r?\n|\r/g, " ");
   const body = req.body["content"];
 
-  mailer(from, params.mail.contact, subject, body, err => {
-    if (!!err)
-      return res.json({errmsg:err});
-    // OK, everything fine
-    res.json({}); //ignored
+  sendEmail(from, params.mail.contact, subject, body, err => {
+    res.json(err || {});
   });
 });
 
diff --git a/server/routes/news.js b/server/routes/news.js
index dbd6d382..80b91299 100644
--- a/server/routes/news.js
+++ b/server/routes/news.js
@@ -1,50 +1,46 @@
-// AJAX methods to get, create, update or delete a problem
-
 let router = require("express").Router();
 const access = require("../utils/access");
 const NewsModel = require("../models/News");
 const sanitizeHtml = require('sanitize-html');
-const devs = [1]; //hard-coded list of developers, allowed to post news
+const devs = [1]; //hard-coded list of developers IDs, allowed to post news
 
-router.get("/news", (req,res) => {
-  const cursor = req.query["cursor"];
-  if (!cursor.match(/^[0-9]+$/))
-    return res.json({errmsg: "Bad cursor value"});
-  NewsModel.getNext(cursor, (err,newsList) => {
-    res.json(err || {newsList:newsList});
-  });
+router.post("/news", access.logged, access.ajax, (req,res) => {
+  if (devs.includes(req.userId))
+  {
+    const content = sanitizeHtml(req.body.news.content);
+    NewsModel.create(content, req.userId, (err,ret) => {
+      res.json(err || {id:ret.nid});
+    });
+  }
 });
 
-router.post("/news", access.logged, access.ajax, (req,res) => {
-  if (!devs.includes(req.userId))
-    return res.json({errmsg: "Not allowed to post"});
-  const content = sanitizeHtml(req.body.news.content);
-  NewsModel.create(content, req.userId, (err,ret) => {
-    return res.json(err || {id:ret.nid});
-  });
+router.get("/news", access.ajax, (req,res) => {
+  const cursor = req.query["cursor"];
+  if (cursor.match(/^[0-9]+$/))
+  {
+    NewsModel.getNext(cursor, (err,newsList) => {
+      res.json(err || {newsList:newsList});
+    });
+  }
 });
 
 router.put("/news", access.logged, access.ajax, (req,res) => {
-  if (!devs.includes(req.userId))
-    return res.json({errmsg: "Not allowed to edit"});
   let news = req.body.news;
-  if (!news.id.toString().match(/^[0-9]+$/))
-    res.json({errmsg: "Bad news ID"});
-  news.content = sanitizeHtml(news.content);
-  NewsModel.update(news, (err) => {
-    res.json(err || {});
-  });
+  if (devs.includes(req.userId) && news.id.toString().match(/^[0-9]+$/))
+  {
+    news.content = sanitizeHtml(news.content);
+    NewsModel.update(news);
+    res.json({});
+  }
 });
 
 router.delete("/news", access.logged, access.ajax, (req,res) => {
-  if (!devs.includes(req.userId))
-    return res.json({errmsg: "Not allowed to delete"});
   const nid = req.query.id;
-  if (!nid.toString().match(/^[0-9]+$/))
-    res.json({errmsg: "Bad news ID"});
-  NewsModel.remove(nid, err => {
-    res.json(err || {});
-  });
+  if (devs.includes(req.userId) && nid.toString().match(/^[0-9]+$/))
+  {
+    NewsModel.remove(nid);
+    res.json({});
+  }
 });
 
 module.exports = router;
diff --git a/server/routes/problems.js b/server/routes/problems.js
index 02088357..64c173a1 100644
--- a/server/routes/problems.js
+++ b/server/routes/problems.js
@@ -1,20 +1,33 @@
-// AJAX methods to get, create, update or delete a problem
-
 let router = require("express").Router();
 const access = require("../utils/access");
 const ProblemModel = require("../models/Problem");
 const sanitizeHtml = require('sanitize-html');
 
+router.post("/problems", access.logged, access.ajax, (req,res) => {
+  if (ProblemModel.checkProblem(req.body.prob))
+  {
+    const problem =
+    {
+      vid: req.body.prob.vid,
+      fen: req.body.prob.fen,
+      uid: req.userId,
+      instruction: sanitizeHtml(req.body.prob.instruction),
+      solution: sanitizeHtml(req.body.prob.solution),
+    };
+    ProblemModel.create(problem, (err,ret) => {
+      res.json(err || {id:ret.pid});
+    });
+  }
+  else
+    res.json({});
+});
+
 router.get("/problems", (req,res) => {
   const probId = req.query["pid"];
-  if (!!probId)
+  if (probId && probId.match(/^[0-9]+$/))
   {
-    if (!probId.match(/^[0-9]+$/))
-      return res.json({errmsg: "Wrong problem ID"});
     ProblemModel.getOne(req.query["pid"], (err,problem) => {
-      access.checkRequest(res, err, problem, "Problem not found", () => {
-        res.json({problem: problem});
-      });
+      res.json(err || {problem: problem});
     });
   }
   else
@@ -25,42 +38,22 @@ router.get("/problems", (req,res) => {
   }
 });
 
-router.post("/problems", access.logged, access.ajax, (req,res) => {
-  const error = ProblemModel.checkProblem(req.body.prob);
-  if (!!error)
-    return res.json({errmsg:error});
-  const problem =
-  {
-    vid: req.body.prob.vid,
-    fen: req.body.prob.fen,
-    uid: req.userId,
-    instruction: sanitizeHtml(req.body.prob.instruction),
-    solution: sanitizeHtml(req.body.prob.solution),
-  };
-  ProblemModel.create(problem, (err,ret) => {
-    return res.json(err || {id:ret.pid});
-  });
-});
-
 router.put("/problems", access.logged, access.ajax, (req,res) => {
   let obj = req.body.prob;
-  const error = ProblemModel.checkProblem(obj);
-  if (!!error)
-    return res.json({errmsg: error});
-  obj.instruction = sanitizeHtml(obj.instruction);
-  obj.solution = sanitizeHtml(obj.solution);
-  ProblemModel.update(obj, (err) => {
-    res.json(err || {});
-  });
+  if (ProblemModel.checkProblem(obj))
+  {
+    obj.instruction = sanitizeHtml(obj.instruction);
+    obj.solution = sanitizeHtml(obj.solution);
+    ProblemModel.safeUpdate(obj, req.userId);
+  }
+  res.json({});
 });
 
 router.delete("/problems", access.logged, access.ajax, (req,res) => {
   const pid = req.query.id;
-  if (!pid.toString().match(/^[0-9]+$/))
-    res.json({errmsg: "Bad problem ID"});
-  ProblemModel.safeRemove(pid, req.userId, err => {
-    res.json(err || {});
-  });
+  if (pid.toString().match(/^[0-9]+$/))
+    ProblemModel.safeRemove(pid, req.userId);
+  res.json({});
 });
 
 module.exports = router;
diff --git a/server/routes/users.js b/server/routes/users.js
index 0302b1c0..f9f1d86f 100644
--- a/server/routes/users.js
+++ b/server/routes/users.js
@@ -1,5 +1,3 @@
-// AJAX methods to get, create, update or delete a user
-
 let router = require("express").Router();
 const UserModel = require('../models/User');
 const sendEmail = require('../utils/mailer');
@@ -7,10 +5,33 @@ const genToken = require("../utils/tokenGenerator");
 const access = require("../utils/access");
 const params = require("../config/parameters");
 
+router.post('/register', access.unlogged, access.ajax, (req,res) => {
+  const name = req.body.name;
+  const email = req.body.email;
+  const notify = !!req.body.notify;
+  if (UserModel.checkNameEmail({name: name, email: email}))
+  {
+    UserModel.create(name, email, notify, (err,ret) => {
+      if (err)
+        res.json({errmsg: "User creation failed. Try again"});
+      else
+      {
+        const user = {
+          id: ret.uid,
+          name: name,
+          email: email,
+        };
+        setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
+        res.json({});
+      }
+    });
+  }
+});
+
 // NOTE: this method is safe because the sessionToken must be guessed
 router.get("/whoami", access.ajax, (req,res) => {
   const callback = (user) => {
-    return res.json({
+    res.json({
       name: user.name,
       email: user.email,
       id: user.id,
@@ -19,80 +40,72 @@ router.get("/whoami", access.ajax, (req,res) => {
   };
   const anonymous = {name:"", email:"", id:0, notify:false};
   if (!req.cookies.token)
-    return callback(anonymous);
-  if (!req.cookies.token.match(/^[a-z0-9]+$/))
-    return res.json({errmsg: "Bad token"});
-  UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
-    if (!!err || !user)
-      callback(anonymous);
-    else
-      callback(user);
-  });
+    callback(anonymous);
+  else if (req.cookies.token.match(/^[a-z0-9]+$/))
+  {
+    UserModel.getOne("sessionToken", req.cookies.token, (err, user) => {
+      callback(user || anonymous);
+    });
+  }
 });
 
 // NOTE: this method is safe because only IDs and names are returned
 router.get("/users", access.ajax, (req,res) => {
   const ids = req.query["ids"];
-  if (!!ids && !ids.match(/^([0-9]+,?)+$/)) //NOTE: slightly too permissive
-    return res.json({errmsg: "Bad IDs array"});
-  UserModel.getByIds(ids, (err,users) => {
-    if (!!err)
-      return res.json({errmsg: err.toString()});
-    return res.json({users:users});
-  });
+  if (ids.match(/^([0-9]+,?)+$/)) //NOTE: slightly too permissive
+  {
+    UserModel.getByIds(ids, (err,users) => {
+      res.json({users:users});
+    });
+  }
+});
+
+router.put('/update', access.logged, access.ajax, (req,res) => {
+  const name = req.body.name;
+  const email = req.body.email;
+  if (UserModel.checkNameEmail({name: name, email: email}));
+  {
+    const user = {
+      id: req.userId,
+      name: name,
+      email: email,
+      notify: !!req.body.notify,
+    };
+    UserModel.updateSettings(user);
+    res.json({});
+  }
 });
 
+// Authentication-related methods:
+
 // 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
   const token = genToken(params.token.length);
-  UserModel.setLoginToken(token, to.id, err => {
-    if (!!err)
-      return res.json({errmsg: err.toString()});
-    const body =
-      "Hello " + to.name + "!" + `
+  UserModel.setLoginToken(token, to.id);
+  const body =
+    "Hello " + to.name + "!" + `
 ` +
-      "Access your account here: " +
-      params.siteURL + "/#/authenticate/" + token + `
+    "Access your account here: " +
+    params.siteURL + "/#/authenticate/" + token + `
 ` +
-      "Token will expire in " + params.token.expire/(1000*60) + " minutes."
-    sendEmail(params.mail.noreply, to.email, subject, body, err => {
-      res.json(err || {});
-    });
-  });
+    "Token will expire in " + params.token.expire/(1000*60) + " minutes."
+  sendEmail(params.mail.noreply, to.email, subject, body);
 }
 
-router.post('/register', access.unlogged, access.ajax, (req,res) => {
-  const name = req.body.name;
-  const email = req.body.email;
-  const notify = !!req.body.notify;
-  const error = UserModel.checkNameEmail({name: name, email: email});
-  if (!!error)
-    return res.json({errmsg: error});
-  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('/sendtoken', access.unlogged, access.ajax, (req,res) => {
   const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
   const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
-  const error = UserModel.checkNameEmail({[type]: nameOrEmail});
-  if (!!error)
-    return res.json({errmsg: error});
-  UserModel.getOne(type, nameOrEmail, (err,user) => {
-    access.checkRequest(res, err, user, "Unknown user", () => {
-      setAndSendLoginToken("Token for " + params.siteURL, user, res);
+  if (UserModel.checkNameEmail({[type]: nameOrEmail}))
+  {
+    UserModel.getOne(type, nameOrEmail, (err,user) => {
+      access.checkRequest(res, err, user, "Unknown user", () => {
+        setAndSendLoginToken("Token for " + params.siteURL, user, res);
+        res.json({});
+      });
     });
-  });
+  }
 });
 
 router.get('/authenticate', access.unlogged, access.ajax, (req,res) => {
@@ -102,45 +115,28 @@ router.get('/authenticate', access.unlogged, access.ajax, (req,res) => {
     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) => {
-        if (!!err)
-          return res.json({errmsg: err.toString()});
-        // Set cookie
-        res.cookie("token", token, {
-          httpOnly: true,
-          secure: !!params.siteURL.match(/^https/),
-          maxAge: params.cookieExpire,
-        });
-        res.json({
-          id: user.id,
-          name: user.name,
-          email: user.email,
-          notify: user.notify,
+        res.json({errmsg: "Token expired"});
+      else
+      {
+        // Generate session token (if not exists) + destroy login token
+        UserModel.trySetSessionToken(user.id, (token) => {
+          res.cookie("token", token, {
+            httpOnly: true,
+            secure: !!params.siteURL.match(/^https/),
+            maxAge: params.cookieExpire,
+          });
+          res.json({
+            id: user.id,
+            name: user.name,
+            email: user.email,
+            notify: user.notify,
+          });
         });
-      });
+      }
     });
   });
 });
 
-router.put('/update', access.logged, access.ajax, (req,res) => {
-  const name = req.body.name;
-  const email = req.body.email;
-  const error = UserModel.checkNameEmail({name: name, email: email});
-  if (!!error)
-    return res.json({errmsg: error});
-  const user = {
-    id: req.userId,
-    name: name,
-    email: email,
-    notify: !!req.body.notify,
-  };
-  UserModel.updateSettings(user, err => {
-    res.json(err ? {errmsg: err.toString()} : {});
-  });
-});
-
 router.get('/logout', access.logged, access.ajax, (req,res) => {
   res.clearCookie("token");
   res.json({});
diff --git a/server/routes/variants.js b/server/routes/variants.js
index 8153c7a7..3f7417e3 100644
--- a/server/routes/variants.js
+++ b/server/routes/variants.js
@@ -1,15 +1,12 @@
 // Get variants list (always needed)
 
 let router = require("express").Router();
-const createError = require('http-errors');
 const VariantModel = require("../models/Variant");
 const access = require("../utils/access");
 
-router.get('/variants', access.ajax, function(req, res, next) {
+router.get('/variants', access.ajax, function(req, res) {
   VariantModel.getAll((err,variants) => {
-    if (!!err)
-      return next(err);
-    res.json({variantArray:variants});
+    res.json(err || {variantArray:variants});
   });
 });
 
diff --git a/server/utils/access.js b/server/utils/access.js
index d51c4b77..049e9eb6 100644
--- a/server/utils/access.js
+++ b/server/utils/access.js
@@ -6,8 +6,8 @@ module.exports =
   logged: function(req, res, next) {
     const callback = () => {
       if (!loggedIn)
-        return res.json({errmsg: "Not logged in"});
-      next();
+        res.json({errmsg: "Not logged in"});
+      else next();
     };
     let loggedIn = undefined;
     if (!req.cookies.token)
@@ -40,27 +40,27 @@ module.exports =
     // Just a quick heuristic, which should be enough
     const loggedIn = !!req.cookies.token;
     if (loggedIn)
-      return res.json({errmsg: "Already logged in"});
-    next();
+      res.json({errmsg: "Already logged in"});
+    else next();
   },
 
   // Prevent direct access to AJAX results
   ajax: function(req, res, next) {
     if (!req.xhr)
-      return res.json({errmsg: "Unauthorized access"});
-    next();
+      res.json({errmsg: "Unauthorized access"});
+    else next();
   },
 
   // Check for errors before callback (continue page loading). TODO: better name.
   checkRequest: function(res, err, out, msg, cb) {
-    if (!!err)
-      return res.json({errmsg: err.errmsg || err.toString()});
-    if (!out
+    if (err)
+      res.json({errmsg: err.errmsg || err.toString()});
+    else if (!out
       || (Array.isArray(out) && out.length == 0)
       || (typeof out === "object" && Object.keys(out).length == 0))
     {
-      return res.json({errmsg: msg});
+      res.json({errmsg: msg});
     }
-    cb();
+    else cb();
   },
 }
diff --git a/server/utils/mailer.js b/server/utils/mailer.js
index 60cc9f26..b0a0bace 100644
--- a/server/utils/mailer.js
+++ b/server/utils/mailer.js
@@ -10,10 +10,14 @@ module.exports = function(from, to, subject, body, cb)
     console.log("Subject: " + subject);
     console.log(body);
     if (!cb)
-      cb = (err) => { if (!!err) console.log(err); }
-    return cb();
+      cb = (err) => { if (err) console.log(err); }
+    cb();
+    return;
   }
-  else if (!cb)
+
+  // Production-only code from here:
+
+  if (!cb)
     cb = () => {}; //default: do nothing (TODO: log somewhere)
 
   // Create reusable transporter object using the default SMTP transport
@@ -38,10 +42,8 @@ module.exports = function(from, to, subject, body, cb)
 
   // Send mail with the defined transport object
   transporter.sendMail(mailOptions, (error, info) => {
-    if (!!error)
-      return cb(error);
     // Ignore info. Option:
     //console.log('Message sent: %s', info.messageId);
-    return cb();
+    cb(error);
   });
 }
diff --git a/server/utils/tokenGenerator.js b/server/utils/tokenGenerator.js
index 858bc3bf..2c21b4e5 100644
--- a/server/utils/tokenGenerator.js
+++ b/server/utils/tokenGenerator.js
@@ -3,11 +3,12 @@ function randString()
   return Math.random().toString(36).substr(2); // remove `0.`
 }
 
-module.exports = function(tlen)
+module.exports = function(tokenLength)
 {
   let res = "";
-  let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
+  // 10 = min length of a rand() string
+  let nbRands = Math.ceil(tokenLength/10);
   for (let i = 0; i < nbRands; i++)
     res += randString();
-  return res.substr(0, tlen);
+  return res.substr(0, tokenLength);
 }
-- 
2.44.0