From 73ef53d0f98edb2f0fcfe694f6fd6b0fcf866170 Mon Sep 17 00:00:00 2001
From: Rosanny <rosanny.sihombing@hft-stuttgart.de>
Date: Tue, 10 Dec 2019 18:50:43 +0100
Subject: [PATCH] add user registration & update user db

---
 public/js/registration.js |  37 ++++
 routes/methods.js         |  73 +++++++-
 routes/routes.js          | 361 ++++++++++++++++++++++----------------
 views/registration.pug    |  91 ++++++++++
 4 files changed, 406 insertions(+), 156 deletions(-)
 create mode 100644 public/js/registration.js
 create mode 100644 views/registration.pug

diff --git a/public/js/registration.js b/public/js/registration.js
new file mode 100644
index 00000000..1033ce3f
--- /dev/null
+++ b/public/js/registration.js
@@ -0,0 +1,37 @@
+var isEmailValid = false
+var isPasswordValid = false
+
+// check if email already exist
+$('#inputEmail').change(function(){
+    var email = $('#inputEmail').val()
+    $.get("email/"+email, function(data) {
+        $('#emailWarning').empty()
+        isEmailValid = data
+        if(!isEmailValid) {
+            $('#emailWarning').html('M4_LAB account with this email address is already exist.')
+        }
+        switchSubmitButton()
+    })
+    .fail(function() {
+        console.log("cannot check email")
+    })
+});
+
+// check password
+$('#inputPassword').on('keyup', function () {
+    isPasswordValid = checkPasswordReq($('#inputPassword').val())
+    $('#passwordWarning').empty();
+    if (!isPasswordValid) {
+        $('#passwordWarning').html('Must be at least 8 characters')
+    }
+    switchSubmitButton()
+});
+
+function switchSubmitButton() {
+    if (isEmailValid && isPasswordValid) {
+        $('#submitBtn').prop('disabled', false)
+    }
+    else {
+        $('#submitBtn').prop('disabled', true)
+    }
+}
\ No newline at end of file
diff --git a/routes/methods.js b/routes/methods.js
index 953043fc..cdec75e0 100644
--- a/routes/methods.js
+++ b/routes/methods.js
@@ -5,10 +5,73 @@ var methods = {
 	currentDate: function() {
 		console.log('Current Date is: ' + new Date().toISOString().slice(0, 10));
     },
-    // ======================= user db =======================
+    // ===================== user db =====================
+    registerNewUser: function(data, callback) {
+        dbconn.user.beginTransaction(function(err) { // START TRANSACTION
+            if (err) {
+                throw err
+            }
+            // insert profile
+            dbconn.user.query('INSERT INTO user SET ?', data.profile, function (err, results, fields) {
+                if (err) {
+                  return dbconn.user.rollback(function() {
+                    throw err
+                  });
+                }
+                var newUserId = results.insertId
+                // set password
+                var credentialData = {
+                    user_id: newUserId,
+                    password: data.password
+                }
+                dbconn.user.query('INSERT INTO credential SET ?', credentialData, function (err, results, fields) {
+                    if (err) {
+                        return dbconn.user.rollback(function() {
+                            throw err
+                        });
+                    }
+                    // set default user-project-role
+                    var projectRoleData = {
+                        project_id: 1,  //M4_LAB
+                        role_id: 2,     // USER
+                        user_id: newUserId
+                    }
+                    dbconn.user.query('INSERT INTO user_project_role SET ?', projectRoleData, function (err, results, fields) {
+                        if (err) {
+                            return dbconn.user.rollback(function() {
+                                throw err
+                            });
+                        }
+                        // COMMIT
+                        dbconn.user.commit(function(err) {
+                            if (err) {
+                                return dbconn.user.rollback(function() {
+                                    throw err
+                                });
+                            }
+                        });
+                    })
+                });
+            });
+            callback(err)
+        })
+    },
+    getUserByEmail: function(email, callback) {
+        dbconn.user.query('SELECT title, firstname, lastname, industry, organisation, speciality FROM user WHERE email = "' +email+'"', function (err, rows, fields) {
+            if (err) {
+                throw err;
+            }
+            else {
+                if ( rows.length > 0) {
+                    user = rows[0];
+                }
+            }
+            callback(user, err);
+        });
+    },
 	checkUserEmail: function(email, callback) {
         var user;
-        dbconn.user.query('SELECT email FROM user WHERE email = "' +email+'"', function (err, rows, fields) {
+        dbconn.user.query('SELECT id, email FROM user WHERE email = "' +email+'"', function (err, rows, fields) {
             if (err) {
                 throw err;
             }
@@ -22,7 +85,7 @@ var methods = {
     },
     checkUserToken: function(token, callback) {
         var user;
-        dbconn.user.query('SELECT email FROM user WHERE resetPasswordToken = "'+token+'" and resetPasswordExpires > '+Date.now(), function (err, rows, fields) {
+        dbconn.user.query('SELECT user_id FROM credential WHERE resetPasswordToken = "'+token+'" and resetPasswordExpires > '+Date.now(), function (err, rows, fields) {
             if (err) {
                 throw err;
             }
@@ -40,8 +103,8 @@ var methods = {
             callback(err);
         })
     },
-    updatePassword: function(hash, email, callback) {
-		dbconn.user.query('UPDATE user SET password = "'+hash+'" WHERE email = "' +email+'"', function (err, rows, fields) {
+    updateCredential: function(data, callback) {
+        dbconn.user.query('UPDATE credential SET ? WHERE user_id = ' +data.user_id, data, function (err, rows, fields) {
             if (err) throw err;
             callback(err);
         })
diff --git a/routes/routes.js b/routes/routes.js
index 0d369f37..7077fc1d 100644
--- a/routes/routes.js
+++ b/routes/routes.js
@@ -1,14 +1,14 @@
-const fs = require('fs');
-const SamlStrategy = require('passport-saml').Strategy;
-const dbconn = require('./dbconn');
-const methods = require('./methods');
+const fs = require('fs')
+const SamlStrategy = require('passport-saml').Strategy
+const dbconn = require('./dbconn')
+const methods = require('./methods')
 // pwd encryption
-const bcrypt = require('bcryptjs');
-const saltRounds = 10;
+const bcrypt = require('bcryptjs')
+const saltRounds = 10
 // forgot pwd
-const async = require('async');
-const crypto = require('crypto');
-const nodemailer = require('nodemailer');
+const async = require('async')
+const crypto = require('crypto')
+const nodemailer = require('nodemailer')
 
 module.exports = function (app, config, passport) {
 
@@ -40,29 +40,62 @@ module.exports = function (app, config, passport) {
       
       validateInResponseTo: false,
       disableRequestedAuthnContext: true
-    },
-    function (profile, done) {
-      return done(null,
-        {
-          id: profile.nameID,
-          idFormat: profile.nameIDFormat,
-          email: profile.email,
-          firstName: profile.givenName,
-          lastName: profile.sn
-        });
+  },
+  function (profile, done) {
+    return done(null, {
+      id: profile.nameID,
+      idFormat: profile.nameIDFormat,
+      email: profile.email,
+      firstName: profile.givenName,
+      lastName: profile.sn
     });
+  });
   
   passport.use(samlStrategy);
-  // ============================
-/*
-  app.all('/', function(req, res){
-    req.flash('test', 'it worked');
-    res.redirect('/test')
-  });
-  app.all('/test', function(req, res){
-    res.send(JSON.stringify(req.flash('test')));
+
+  // ============= SAML ==============
+  app.post(config.passport.saml.path,
+    passport.authenticate(config.passport.strategy,
+      {
+        failureRedirect: '/',
+        failureFlash: true
+      }),
+    function (req, res) {
+      res.redirect('/');
+    }
+  );
+
+  // to generate Service Provider's XML metadata
+  app.get('/saml/metadata', 
+    function(req, res) {
+      res.type('application/xml');
+      var spMetadata = samlStrategy.generateServiceProviderMetadata(fs.readFileSync(__dirname + '/cert/cert.pem', 'utf8'));
+      res.status(200).send(spMetadata);
+    }
+  );
+
+  // ======== NODEMAILER ====================
+  var smtpTransport = nodemailer.createTransport({
+    host: config.mailer.host,
+    secureConnection: config.mailer.secureConnection,
+    port: config.mailer.port,
+    auth: {
+      user: config.mailer.authUser,
+      pass: config.mailer.authPass
+    },
+    tls: {
+        ciphers: config.mailer.tlsCiphers
+    } 
   });
-  */
+  
+  var mailOptions = {
+    to: "",
+    from: config.mailer.from,
+    subject: "",
+    text: ""
+  };
+  
+  // ======== APP ROUTES ====================
   app.get('/', function (req, res) {
     res.redirect('/profile')
   });
@@ -75,22 +108,38 @@ module.exports = function (app, config, passport) {
       })
   );
 
-  app.post(config.passport.saml.path,
-    passport.authenticate(config.passport.strategy,
-      {
-        failureRedirect: '/',
-        failureFlash: true
-      }),
-    function (req, res) {
-      res.redirect('/');
+  app.get('/logout', function (req, res) {
+    if (req.user == null) {
+      return res.redirect('/');
     }
-  );
+    
+    req.user.nameID = req.user.id;
+    req.user.nameIDFormat = req.user.idFormat;
+    return samlStrategy.logout(req, function(err, uri) {
+      req.logout();
+      
+      if ( req.session ) {
+        req.session.destroy((err) => {
+          if(err) {
+              return console.log(err);
+          }
+        });
+      }
+     
+      return res.redirect(uri);
+    });
+  });
 
   app.get('/profile', function (req, res) {
-    if (req.isAuthenticated()) {   
-      res.render('profile', {
-        user: req.user // useful for view engine, useless for HTML
-      });
+    if (req.isAuthenticated()) {
+      methods.getUserByEmail(req.user.email, function(data, err){
+        if (!err) {
+          res.render('profile', {
+            user: data,
+            email: req.user.email
+          });
+        }
+      })
     } else {
       res.redirect('/login');
     }
@@ -162,6 +211,7 @@ module.exports = function (app, config, passport) {
 
   app.get('/security', function (req, res) {
     if (req.isAuthenticated()) {
+      console.log(req.user)
       res.render('security', {
         user: req.user // useful for view engine, useless for HTML
       });
@@ -198,59 +248,67 @@ module.exports = function (app, config, passport) {
       res.redirect('/login');
     }
   });
-
-  // todo: user registration with captcha
   
   app.post('/changePwd', function (req, res) {
     if (req.isAuthenticated()) {
       var currPwd = req.body.inputCurrPwd
       var newPwd = req.body.inputNewPwd
       var retypePwd = req.body.inputConfirm
-      
-      // Load hashed passwd from DB.
-      dbconn.user.query('SELECT password FROM user WHERE email="'+req.user.email+'"', function (err, rows, fields) {
-        if (err) {
-          res.redirect('/500')
-          throw err
-        }
-        var userPwd = rows[0].password
 
-        // check if the password is correct
-        bcrypt.compare(currPwd, userPwd, function(err, isMatch) {
-          if (err) {
-            res.redirect('/500')
-            throw err
-          }
-          else if (!isMatch) {
-            req.flash('error', "Sorry, your password was incorrect. Please double-check your password.")
-            res.redirect('/security')
-          } else {
-            if ( newPwd != retypePwd ) {
-              req.flash('error', "Passwords do no match. Please make sure you re-type your new password correctly.")
-              res.redirect('/security')
-            }
-            else {
-              // update password
-              bcrypt.genSalt(saltRounds, function(err, salt) {
-                bcrypt.hash(newPwd, salt, function(err, hash) {
-                  methods.updatePassword(hash, req.user.email, function(err){
-                    if (err) {
-                      req.flash('error', "Database error: Password cannot be modified.")
-                      throw err
-                    }
-                    else {
-                      req.flash('success', "Pasword updated!")
-                      console.log('pasword updated!')
-                    }
-                    res.redirect('/security')
-                  })
-                });
-              });
+      methods.getUserIdByEmail(req.user.email, function(userId, err) {
+        if (!err) {
+          // Load hashed passwd from DB
+          dbconn.user.query('SELECT password FROM credential WHERE user_id='+userId, function (err, rows, fields) {
+            if (err) {
+              res.redirect('/500')
+              throw err
             }
-          }
+            var userPwd = rows[0].password
+
+            // check if the password is correct
+            bcrypt.compare(currPwd, userPwd, function(err, isMatch) {
+              if (err) {
+                res.redirect('/500')
+                throw err
+              }
+              else if (!isMatch) {
+                req.flash('error', "Sorry, your password was incorrect. Please double-check your password.")
+                res.redirect('/security')
+              }
+              else {
+                if ( newPwd != retypePwd ) {
+                  req.flash('error', "Passwords do no match. Please make sure you re-type your new password correctly.")
+                  res.redirect('/security')
+                }
+                else {
+                  // update password
+                  bcrypt.genSalt(saltRounds, function(err, salt) {
+                    bcrypt.hash(newPwd, salt, function(err, hash) {
+                      var credentialData = {
+                        password: hash,
+                        user_id: userId
+                      }
+                      methods.updateCredential(credentialData, function(err){
+                        if (err) {
+                          req.flash('error', "Database error: Password cannot be modified.")
+                          throw err
+                        }
+                        else {
+                          req.flash('success', "Pasword updated!")
+                          console.log('pasword updated!')
+                        }
+                        res.redirect('/security')
+                      })
+                    });
+                  });
+                }
+              }
+          }) 
         })
-      })
-    } else {
+        }
+      })  
+    }
+    else {
       res.redirect('/login');
     }
   });
@@ -259,41 +317,11 @@ module.exports = function (app, config, passport) {
     res.render('forgotPwd', {
       user: req.user
     });
-    /*
-      if email found: send generated email or instruction to reset password
-      if email not found: send notification, example: https://www.troyhunt.com/everything-you-ever-wanted-to-know/
-    */
   });
 
-  var smtpTransport = nodemailer.createTransport({
-    host: config.mailer.host,
-    secureConnection: config.mailer.secureConnection,
-    port: config.mailer.port,
-    auth: {
-      user: config.mailer.authUser,
-      pass: config.mailer.authPass
-    },
-    tls: {
-        ciphers: config.mailer.tlsCiphers
-    } 
-  });
-  var mailOptions = {
-    to: "",
-    from: config.mailer.from,
-    subject: "",
-    text: ""
-  };
-
   app.post('/forgotPwd', function(req, res, next) {
     //methods.currentDate();
-    /* do something: write down reset password procedure in Technical Req. Document
-      ref: https://meanstackdeveloper.in/implement-reset-password-functionality-in-node-js-express.html
-      https://medium.com/@terrychayes/adding-password-reset-functionality-to-a-react-app-with-a-node-backend-4681480195d4
-      http://sahatyalkabov.com/how-to-implement-password-reset-in-nodejs/
-    
-      if email found: send generated email or instruction to reset password
-      if email not found: send notification, example: https://www.troyhunt.com/everything-you-ever-wanted-to-know/
-    */
+
     var emailAddress = req.body.inputEmail;
     var emailContent = "Hi there,\n\n"+
       "we've received a request to reset your password. However, this email address is not on our database of registered users.\n\n"+
@@ -318,10 +346,12 @@ module.exports = function (app, config, passport) {
               "This password reset is only valid for 1 hour.\n\n"+
               "Thanks,\nM4_LAB Team"
             
-            user.resetPasswordToken = token;
-            user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
-
-            methods.updateUser(user, function(err) {
+            var credentialData = {
+              user_id: user.id,
+              resetPasswordToken: token,
+              resetPasswordExpires: Date.now() + 3600000 // 1 hour
+            }
+            methods.updateCredential(credentialData, function(err) {
               done(err, token, user);
             });
           }
@@ -365,10 +395,15 @@ module.exports = function (app, config, passport) {
   app.post('/reset/:token', function(req, res) {
     methods.checkUserToken(req.params.token, function(err, user){
       if (user) {
-        // update password
+        // encrypt password
         bcrypt.genSalt(saltRounds, function(err, salt) {
-          bcrypt.hash(req.params.inputNewPwd, salt, function(err, hash) {
-            methods.updatePassword(hash, user.email, function(err){
+          bcrypt.hash(req.body.inputNewPwd, salt, function(err, hash) {
+            var credentialData = {
+              password: hash,
+              user_id: user.user_id
+            }
+            // update password
+            methods.updateCredential(credentialData, function(err){
               if (err) {
                 req.flash('error', "Database error: Password cannot be modified.")
                 throw err
@@ -390,36 +425,60 @@ module.exports = function (app, config, passport) {
     res.redirect('/login')
   });
 
-  app.get('/logout', function (req, res) {
-    if (req.user == null) {
-      return res.redirect('/');
+  // todo: user registration with captcha
+  app.get('/registration', function(req, res) {
+    res.render('registration')
+  })
+
+  app.post('/registration', function(req, res) {
+    // TODO:
+    // create gitlab account?
+    // send email to activate profile?
+
+    // user data
+    var curDate = new Date()
+    var userData = {
+      title: req.body.inputTitle,
+      firstname: req.body.inputFirstname,
+      lastname: req.body.inputLastname,
+      email: req.body.inputEmail,
+      organisation: req.body.inputOrganisation,
+      industry: req.body.inputIndustry,
+      speciality: req.body.inputSpeciality,
+      createdDate: curDate.toISOString().slice(0,10)
     }
-    
-    req.user.nameID = req.user.id;
-    req.user.nameIDFormat = req.user.idFormat;
-    return samlStrategy.logout(req, function(err, uri) {
-      req.logout();
-      
-      if ( req.session ) {
-        req.session.destroy((err) => {
-          if(err) {
-              return console.log(err);
+    // encrypt password
+    bcrypt.genSalt(saltRounds, function(err, salt) {
+      bcrypt.hash(req.body.inputPassword, salt, function(err, hash) {
+        // create account
+        var newAccount = {
+          profile: userData,
+          password: hash
+        }
+        methods.registerNewUser(newAccount, function(err){
+          if (err) {
+            req.flash('error', "Failed");
           }
-        });
-      }
-     
-      return res.redirect(uri);
+          else {
+            req.flash('success', 'Your account has been created. Please log in.');
+          }
+          res.redirect('/registration');
+        })
+      });
     });
+  })
 
-  });
-
-  // to generate Service Provider's XML metadata
-  app.get('/saml/metadata', 
-    function(req, res) {
-      res.type('application/xml');
-      var spMetadata = samlStrategy.generateServiceProviderMetadata(fs.readFileSync(__dirname + '/cert/cert.pem', 'utf8'));
-      res.status(200).send(spMetadata);
-    }
-  );
+  app.get('/email/:email', function(req, res) {
+    methods.checkUserEmail(req.params.email, function(err, user){
+      if (!err) {
+        if (user) {
+          res.send(false)
+        }
+        else {
+          res.send(true)
+        }  
+      }
+    })
+  })
 
-};
+};
\ No newline at end of file
diff --git a/views/registration.pug b/views/registration.pug
new file mode 100644
index 00000000..9f843960
--- /dev/null
+++ b/views/registration.pug
@@ -0,0 +1,91 @@
+doctype html
+html(lang="en")
+  head
+    title= "Create New Account"
+    meta(charset="UTF-8")
+    meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no")
+    link(rel="stylesheet", type="text/css", href="https://transfer.hft-stuttgart.de/css/bootstrap/bootstrap.css")
+    link(rel="stylesheet", href="https://use.fontawesome.com/releases/v5.8.2/css/all.css", integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay", crossorigin="anonymous")
+    style.
+        .collapse {
+            display: none;
+        }
+        .collapse.in {
+            display: block;
+        }
+        .collapsing {
+            position: relative;
+            height: 0;
+            overflow: hidden;
+            -webkit-transition-timing-function: ease;
+            -o-transition-timing-function: ease;
+            transition-timing-function: ease;
+            -webkit-transition-duration: .35s;
+            -o-transition-duration: .35s;
+            transition-duration: .35s;
+            -webkit-transition-property: height,visibility;
+            -o-transition-property: height,visibility;
+            transition-property: height,visibility;
+        }
+        .warning {
+            color: red;
+            font-size: 11px;
+        }
+  body
+    div(class="container-fluid")
+        div(class="row")
+            div(class="col-md-6 offset-md-2")
+                h3(class="mb-3 font-weight-bold") Create New Account
+            div(class="col-md-6 offset-md-3")
+                if successes
+                    for success in successes
+                        div.alert.alert-success.alert-dismissible #{ success }
+                            a(class="close", href="#", data-dismiss="alert", aria-label="close") &times;
+                if errors
+                    for error, i in errors
+                        div.alert.alert-danger.alert-dismissible.fade.show #{ error }
+                            a(class="close", href="#", data-dismiss="alert", aria-label="close") &times;
+                form(method="POST")
+                    h5(class="mb-3 font-weight-bold") Login Data
+                    div(class='form-row')
+                        div(class='form-group col-md-6')
+                            input#inputEmail(name="inputEmail", type="email", class="form-control", placeholder="Email" required)
+                            span#emailWarning(class='warning')
+                        div(class="form-group col-md-6")
+                            input#inputPassword(name="inputPassword", type="password", class="form-control", data-toggle="password", placeholder="Password" required)
+                            span#passwordWarning(class='warning')
+                    h5(class="mb-3 font-weight-bold") Profile Data
+                    div(class="form-row")
+                        div(class='form-group col-md-4')
+                            select#inputTitle(name="inputTitle", class="form-control")
+                                option(value="Frau/Herr") Frau/Herr
+                                option(value="Frau") Frau
+                                option(value="Herr") Herr
+                                option(value="Dr.") Dr.
+                                option(value="Prof. Dr.") Prof. Dr.
+                        div(class='form-group col-md-4')
+                            input#inputFirstname(name="inputFirstname", type="text", class="form-control", placeholder="Vorname" required)
+                        div(class='form-group col-md-4')
+                            input#inputLastname(name="inputLastname", type="text", class="form-control", placeholder="Nachname" required)
+                    div(class="form-group")
+                        input#inputOrganisation(name="inputOrganisation", type="text", class="form-control", placeholder="Unternehmen")
+                    div(class="form-group")
+                        input#inputIndustry(name="inputIndustry", type="text", class="form-control", placeholder="Branche")
+                    div(class="form-group")
+                        input#inputSpeciality(name="inputSpeciality", type="text", class="form-control", placeholder="Fachgebiete")                    
+                    input#submitBtn(type="submit", class="btn btn-outline-dark btn-block", value="Submit" disabled)
+                br
+                p(class="text-center") Already have an account? <a href="https://transfer.hft-stuttgart.de/login">Login</a>.
+                    
+
+    // jQuery
+    script(src="https://code.jquery.com/jquery-3.3.1.min.js")
+    script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1", crossorigin="anonymous")
+    // Bootstrap
+    script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous")
+    // toggle password
+    script(src='https://unpkg.com/bootstrap-show-password@1.2.1/dist/bootstrap-show-password.min.js')
+    // M4_LAB
+    script(src="/js/generalFunction.js")
+    script(src="/js/registration.js")
+    script(src="https://transfer.hft-stuttgart.de/js/headfoot.js")
\ No newline at end of file
-- 
GitLab