Fix login/register system
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 22 Mar 2019 18:19:09 +0000 (19:19 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 22 Mar 2019 18:19:09 +0000 (19:19 +0100)
client/src/App.vue
client/src/components/UpsertUser.vue
client/src/router.js
client/src/utils/ajax.js
server/app.js
server/config/parameters.js.dist
server/routes/users.js

index 4b8e447..c5008f2 100644 (file)
@@ -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')")
index 86f8558..7b92cf9 100644 (file)
@@ -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 = "";
index 82f0100..2030397 100644 (file)
@@ -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",
index 46edca0..0a50a10 100644 (file)
@@ -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));
index 13cf1db..f97d925 100644 (file)
@@ -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();
index 14c100a..5b4301a 100644 (file)
@@ -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
index 1d9b042..ebbfa1e 100644 (file)
@@ -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;