Commit 73ef53d0 authored by Rosanny Sihombing's avatar Rosanny Sihombing
Browse files

add user registration & update user db

parent a9719a6e
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
...@@ -5,10 +5,73 @@ var methods = { ...@@ -5,10 +5,73 @@ var methods = {
currentDate: function() { currentDate: function() {
console.log('Current Date is: ' + new Date().toISOString().slice(0, 10)); 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) { checkUserEmail: function(email, callback) {
var user; 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) { if (err) {
throw err; throw err;
} }
...@@ -22,7 +85,7 @@ var methods = { ...@@ -22,7 +85,7 @@ var methods = {
}, },
checkUserToken: function(token, callback) { checkUserToken: function(token, callback) {
var user; 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) { if (err) {
throw err; throw err;
} }
...@@ -40,8 +103,8 @@ var methods = { ...@@ -40,8 +103,8 @@ var methods = {
callback(err); callback(err);
}) })
}, },
updatePassword: function(hash, email, callback) { updateCredential: function(data, callback) {
dbconn.user.query('UPDATE user SET password = "'+hash+'" WHERE email = "' +email+'"', function (err, rows, fields) { dbconn.user.query('UPDATE credential SET ? WHERE user_id = ' +data.user_id, data, function (err, rows, fields) {
if (err) throw err; if (err) throw err;
callback(err); callback(err);
}) })
......
const fs = require('fs'); const fs = require('fs')
const SamlStrategy = require('passport-saml').Strategy; const SamlStrategy = require('passport-saml').Strategy
const dbconn = require('./dbconn'); const dbconn = require('./dbconn')
const methods = require('./methods'); const methods = require('./methods')
// pwd encryption // pwd encryption
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs')
const saltRounds = 10; const saltRounds = 10
// forgot pwd // forgot pwd
const async = require('async'); const async = require('async')
const crypto = require('crypto'); const crypto = require('crypto')
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer')
module.exports = function (app, config, passport) { module.exports = function (app, config, passport) {
...@@ -40,29 +40,62 @@ module.exports = function (app, config, passport) { ...@@ -40,29 +40,62 @@ module.exports = function (app, config, passport) {
validateInResponseTo: false, validateInResponseTo: false,
disableRequestedAuthnContext: true disableRequestedAuthnContext: true
}, },
function (profile, done) { function (profile, done) {
return done(null, return done(null, {
{ id: profile.nameID,
id: profile.nameID, idFormat: profile.nameIDFormat,
idFormat: profile.nameIDFormat, email: profile.email,
email: profile.email, firstName: profile.givenName,
firstName: profile.givenName, lastName: profile.sn
lastName: profile.sn
});
}); });
});
passport.use(samlStrategy); passport.use(samlStrategy);
// ============================
/* // ============= SAML ==============
app.all('/', function(req, res){ app.post(config.passport.saml.path,
req.flash('test', 'it worked'); passport.authenticate(config.passport.strategy,
res.redirect('/test') {
}); failureRedirect: '/',
app.all('/test', function(req, res){ failureFlash: true
res.send(JSON.stringify(req.flash('test'))); }),
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) { app.get('/', function (req, res) {
res.redirect('/profile') res.redirect('/profile')
}); });
...@@ -75,22 +108,38 @@ module.exports = function (app, config, passport) { ...@@ -75,22 +108,38 @@ module.exports = function (app, config, passport) {
}) })
); );
app.post(config.passport.saml.path, app.get('/logout', function (req, res) {
passport.authenticate(config.passport.strategy, if (req.user == null) {
{ return res.redirect('/');
failureRedirect: '/',
failureFlash: true
}),
function (req, res) {
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) { app.get('/profile', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
res.render('profile', { methods.getUserByEmail(req.user.email, function(data, err){
user: req.user // useful for view engine, useless for HTML if (!err) {
}); res.render('profile', {
user: data,
email: req.user.email
});
}
})
} else { } else {
res.redirect('/login'); res.redirect('/login');
} }
...@@ -162,6 +211,7 @@ module.exports = function (app, config, passport) { ...@@ -162,6 +211,7 @@ module.exports = function (app, config, passport) {
app.get('/security', function (req, res) { app.get('/security', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
console.log(req.user)
res.render('security', { res.render('security', {
user: req.user // useful for view engine, useless for HTML user: req.user // useful for view engine, useless for HTML
}); });
...@@ -198,59 +248,67 @@ module.exports = function (app, config, passport) { ...@@ -198,59 +248,67 @@ module.exports = function (app, config, passport) {
res.redirect('/login'); res.redirect('/login');
} }
}); });
// todo: user registration with captcha
app.post('/changePwd', function (req, res) { app.post('/changePwd', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
var currPwd = req.body.inputCurrPwd var currPwd = req.body.inputCurrPwd
var newPwd = req.body.inputNewPwd var newPwd = req.body.inputNewPwd
var retypePwd = req.body.inputConfirm 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 methods.getUserIdByEmail(req.user.email, function(userId, err) {
bcrypt.compare(currPwd, userPwd, function(err, isMatch) { if (!err) {
if (err) { // Load hashed passwd from DB
res.redirect('/500') dbconn.user.query('SELECT password FROM credential WHERE user_id='+userId, function (err, rows, fields) {
throw err if (err) {
} res.redirect('/500')
else if (!isMatch) { throw err
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')
})
});
});
} }
} 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'); res.redirect('/login');
} }
}); });
...@@ -259,41 +317,11 @@ module.exports = function (app, config, passport) { ...@@ -259,41 +317,11 @@ module.exports = function (app, config, passport) {
res.render('forgotPwd', { res.render('forgotPwd', {
user: req.user 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) { app.post('/forgotPwd', function(req, res, next) {
//methods.currentDate(); //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 emailAddress = req.body.inputEmail;
var emailContent = "Hi there,\n\n"+ 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"+ "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) { ...@@ -318,10 +346,12 @@ module.exports = function (app, config, passport) {
"This password reset is only valid for 1 hour.\n\n"+ "This password reset is only valid for 1 hour.\n\n"+
"Thanks,\nM4_LAB Team" "Thanks,\nM4_LAB Team"
user.resetPasswordToken = token; var credentialData = {
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour user_id: user.id,
resetPasswordToken: token,
methods.updateUser(user, function(err) { resetPasswordExpires: Date.now() + 3600000 // 1 hour
}
methods.updateCredential(credentialData, function(err) {
done(err, token, user); done(err, token, user);
}); });
} }
...@@ -365,10 +395,15 @@ module.exports = function (app, config, passport) { ...@@ -365,10 +395,15 @@ module.exports = function (app, config, passport) {
app.post('/reset/:token', function(req, res) { app.post('/reset/:token', function(req, res) {
methods.checkUserToken(req.params.token, function(err, user){ methods.checkUserToken(req.params.token, function(err, user){
if (user) { if (user) {
// update password // encrypt password
bcrypt.genSalt(saltRounds, function(err, salt) { bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(req.params.inputNewPwd, salt, function(err, hash) { bcrypt.hash(req.body.inputNewPwd, salt, function(err, hash) {
methods.updatePassword(hash, user.email, function(err){ var credentialData = {
password: hash,
user_id: user.user_id
}
// update password
methods.updateCredential(credentialData, function(err){
if (err) { if (err) {
req.flash('error', "Database error: Password cannot be modified.") req.flash('error', "Database error: Password cannot be modified.")
throw err throw err
...@@ -390,36 +425,60 @@ module.exports = function (app, config, passport) { ...@@ -390,36 +425,60 @@ module.exports = function (app, config, passport) {
res.redirect('/login') res.redirect('/login')
}); });
app.get('/logout', function (req, res) { // todo: user registration with captcha
if (req.user == null) { app.get('/registration', function(req, res) {
return res.redirect('/'); 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)
} }
// encrypt password
req.user.nameID = req.user.id; bcrypt.genSalt(saltRounds, function(err, salt) {
req.user.nameIDFormat = req.user.idFormat; bcrypt.hash(req.body.inputPassword, salt, function(err, hash) {
return samlStrategy.logout(req, function(err, uri) { // create account
req.logout(); var newAccount = {
profile: userData,
if ( req.session ) { password: hash
req.session.destroy((err) => { }
if(err) { methods.registerNewUser(newAccount, function(err){
return console.log(err); if (err) {
req.flash('error', "Failed");
} }
}); else {
} req.flash('success', 'Your account has been created. Please log in.');
}
return res.redirect(uri); res.redirect('/registration');
})
});
}); });
})
}); app.get('/email/:email', function(req, res) {
methods.checkUserEmail(req.params.email, function(err, user){
// to generate Service Provider's XML metadata if (!err) {
app.get('/saml/metadata', if (user) {
function(req, res) { res.send(false)
res.type('application/xml'); }
var spMetadata = samlStrategy.generateServiceProviderMetadata(fs.readFileSync(__dirname + '/cert/cert.pem', 'utf8')); else {
res.status(200).send(spMetadata); res.send(true)
} }
); }
})
})
}; };
\ No newline at end of file
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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment