From 1aeed627be63a298d3a093797c3728e3de30b464 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Fri, 22 Mar 2019 19:19:09 +0100
Subject: [PATCH] Fix login/register system

---
 client/src/App.vue                   |  2 +-
 client/src/components/UpsertUser.vue | 34 ++++++++++----------------
 client/src/router.js                 | 36 ++++++++++++++++++++++++++++
 client/src/utils/ajax.js             | 22 ++++++++++-------
 server/app.js                        |  3 ++-
 server/config/parameters.js.dist     |  2 +-
 server/routes/users.js               | 17 +++++--------
 7 files changed, 72 insertions(+), 44 deletions(-)

diff --git a/client/src/App.vue b/client/src/App.vue
index 4b8e447c..c5008f2e 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -26,7 +26,7 @@
                 | {{ st.tr["Problems"] }}
             #rightMenu
               .clickable(onClick="doClick('modalUser')")
-                | {{ !st.user.id ? "Login" : "Update" }}
+                | {{ st.user.id > 0 ? "Update" : "Login" }}
               .clickable(onClick="doClick('modalSettings')")
                 | {{ st.tr["Settings"] }}
               .clickable#flagContainer(onClick="doClick('modalLang')")
diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue
index 86f8558d..7b92cf9a 100644
--- a/client/src/components/UpsertUser.vue
+++ b/client/src/components/UpsertUser.vue
@@ -12,22 +12,22 @@ div
             label(for="username") Name
             input#username(type="text" v-model="user.name")
           fieldset
-            <label for="useremail">Email</label>
-            <input id="useremail" type="email" v-model="user.email"/>
+            label(for="useremail") Email
+            input#useremail(type="email" v-model="user.email")
           fieldset
-            <label for="notifyNew">Notify new moves &amp; games</label>
-            <input id="notifyNew" type="checkbox" v-model="user.notify"/>
+            label(for="notifyNew") Notify new moves &amp; games
+            input#notifyNew(type="checkbox" v-model="user.notify")
         div(v-show="stage=='Login'")
           fieldset
-            <label for="nameOrEmail">Name or Email</label>
-            <input id="nameOrEmail" type="text" v-model="nameOrEmail"/>
+            label(for="nameOrEmail") Name or Email
+            input#nameOrEmail(type="text" v-model="nameOrEmail")
       .button-group
-        button#submit(@click="onSubmit()")
+        button#submit(type="button" @click="onSubmit()")
           span {{ submitMessage }}
           i.material-icons send
         button(v-if="stage!='Update'" @click="toggleStage()")
           span {{ stage=="Login" ? "Register" : "Login" }}
-        button(v-if="stage=='Update'" onClick="location.replace('/logout')")
+        button(v-else onClick="location.replace('/logout')")
           span Logout
       #dialog(:style="{display: displayInfo}") {{ infoMsg }}
 </template>
@@ -40,9 +40,9 @@ export default {
   name: 'my-upsert-user',
   data: function() {
     return {
-      user: store.state.user, //initialized with global user object
+      user: store.state.user,
       nameOrEmail: "", //for login
-      stage: (!store.state.user.id ? "Login" : "Update"),
+      stage: (store.state.user.id > 0 ? "Update" : "Login"), //TODO?
       infoMsg: "",
       enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy
     };
@@ -124,25 +124,17 @@ export default {
       ajax(this.ajaxUrl(), this.ajaxMethod(),
         this.stage == "Login" ? { nameOrEmail: this.nameOrEmail } : this.user,
         res => {
-
-          console.log("receive login infos");
-          console.log(res);
-
           this.infoMsg = this.infoMessage();
           if (this.stage != "Update")
           {
             this.nameOrEmail = "";
             this.user["email"] = "";
-            this.user["name"] = "";
-            
-            debugger; //TODO: 2 passages ici au lieu d'1 lors du register
-            
+            // Update global object
+            this.user["name"] = res.name;
+            this.user["id"] = res.id;
             // Store our identifiers in local storage (by little anticipation...)
             localStorage["myid"] = res.id;
             localStorage["myname"] = res.name;
-            // Also in global object
-            this.st.user.id = res.id;
-            this.st.user.name = res.name;
           }
           setTimeout(() => {
             this.infoMsg = "";
diff --git a/client/src/router.js b/client/src/router.js
index 82f01009..2030397a 100644
--- a/client/src/router.js
+++ b/client/src/router.js
@@ -8,6 +8,9 @@ function loadView(view) {
 	return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
 }
 
+import { ajax } from "@/utils/ajax";
+import { store } from "@/store";
+
 export default new Router({
   routes: [
     {
@@ -25,6 +28,39 @@ export default new Router({
       name: "rules",
       component: loadView("Rules"),
     },
+    {
+      path: "/authenticate/:token",
+      name: "authenticate",
+      beforeEnter: (to, from, next) => {
+        ajax(
+          "/authenticate",
+          "GET",
+          {token: to.params["token"]},
+          (res) => {
+            store.state.user.id = res.id;
+            store.state.user.name = res.name;
+          }
+        );
+        next();
+      },
+      redirect: "/",
+    },
+    {
+      path: "/logout",
+      name: "logout",
+      beforeEnter: (to, from, next) => {
+        ajax(
+          "/logout",
+          "GET",
+          () => {
+            store.state.user.id = 0;
+            store.state.user.name = ""; //TODO: localStorage myId myname mysid ?
+          }
+        );
+        next();
+      },
+      redirect: "/",
+    },
 //    {
 //      path: "/about",
 //      name: "about",
diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js
index 46edca06..0a50a104 100644
--- a/client/src/utils/ajax.js
+++ b/client/src/utils/ajax.js
@@ -26,16 +26,17 @@ export function ajax(url, method, data, success, error)
 	xhr.onreadystatechange = function() {
 		if (this.readyState == 4 && this.status == 200)
 		{
+      let res_json = "";
 			try {
-				let res_json = JSON.parse(xhr.responseText);
-				if (!res_json.errmsg)
-					success(res_json);
-				else
-					error(res_json.errmsg);
-			} catch (e) {
+				res_json = JSON.parse(xhr.responseText);
+      } catch (e) {
 				// Plain text (e.g. for rules retrieval)
-				success(xhr.responseText);
-			}
+				return success(xhr.responseText);
+      }
+      if (!res_json.errmsg)
+        success(res_json);
+			else
+				error(res_json.errmsg);
 		}
 	};
 
@@ -46,7 +47,10 @@ export function ajax(url, method, data, success, error)
 	}
 	xhr.open(method, params.serverUrl + url, true);
 	xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
-	if (["POST","PUT"].includes(method))
+	// Next line because logout and authenticate set (cross-domain in dev mode) cookies
+  if (url.startsWith("/authenticate") || url.startsWith("/logout"))
+    xhr.withCredentials = true;
+  if (["POST","PUT"].includes(method))
 	{
 		xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
 		xhr.send(JSON.stringify(data));
diff --git a/server/app.js b/server/app.js
index 13cf1dbf..f97d925b 100644
--- a/server/app.js
+++ b/server/app.js
@@ -35,7 +35,8 @@ if (params.cors.enable)
 {
 	app.use(function(req, res, next) {
 		res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
-		res.header("Access-Control-Allow-Headers",
+		res.header("Access-Control-Allow-Credentials", true); //for cookies
+    res.header("Access-Control-Allow-Headers",
       "Origin, X-Requested-With, Content-Type, Accept");
 	  res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
     next();
diff --git a/server/config/parameters.js.dist b/server/config/parameters.js.dist
index 14c100a2..5b4301a6 100644
--- a/server/config/parameters.js.dist
+++ b/server/config/parameters.js.dist
@@ -9,7 +9,7 @@ module.exports =
 	// CORS: cross-origin resource sharing options
 	cors: {
 		enable: true,
-		allowedOrigin: "*",
+		allowedOrigin: "http://localhost:8080",
 	},
 
 	// Lifespan of a (login) cookie
diff --git a/server/routes/users.js b/server/routes/users.js
index 1d9b0423..ebbfa1e6 100644
--- a/server/routes/users.js
+++ b/server/routes/users.js
@@ -18,13 +18,9 @@ function setAndSendLoginToken(subject, to, res)
 		const body =
 			"Hello " + to.name + "!\n" +
 			"Access your account here: " +
-			params.siteURL + "/authenticate?token=" + token + "\\n" +
+			params.siteURL + "/#/authenticate/" + token + "\\n" +
 			"Token will expire in " + params.token.expire/(1000*60) + " minutes."
 		sendEmail(params.mail.noreply, to.email, subject, body, err => {
-			
-      console.log("send login infos ::");
-      console.log(to);
-      
       // "id" is generally the only info missing on client side,
 			// but the name is also unknown if log-in with the email.
 			res.json(err || {id: to.id, name: to.name});
@@ -64,8 +60,8 @@ router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => {
 	});
 });
 
-router.get('/authenticate', access.unlogged, (req,res) => {
-	UserModel.getOne("loginToken", req.query.token, (err,user) => {
+router.get('/authenticate', access.unlogged, access.ajax, (req,res) => {
+  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)
@@ -80,7 +76,7 @@ router.get('/authenticate', access.unlogged, (req,res) => {
 					secure: !!params.siteURL.match(/^https/),
 					maxAge: params.cookieExpire,
 				});
-				res.redirect("/");
+				res.json({name:user.name, id:user.id});
 			});
 		});
 	});
@@ -103,10 +99,9 @@ router.put('/update', access.logged, access.ajax, (req,res) => {
 	});
 });
 
-// Logout on server because the token cookie is httpOnly
-router.get('/logout', access.logged, (req,res) => {
+router.get('/logout', access.logged, access.ajax, (req,res) => {
 	res.clearCookie("token");
-	res.redirect('/');
+	res.json({});
 });
 
 module.exports = router;
-- 
2.44.0