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 = {
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);
})
......
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
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;