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") × + 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") × + 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