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; // forgot pwd const async = require('async'); const crypto = require('crypto'); const nodemailer = require('nodemailer'); module.exports = function (app, config, passport) { // =========== PASSPORT ======= passport.serializeUser(function (user, done) { done(null, user); }); passport.deserializeUser(function (user, done) { done(null, user); }); var samlStrategy = new SamlStrategy({ // URL that goes from the Identity Provider -> Service Provider callbackUrl: config.passport.saml.path, // Base address to call logout requests logoutUrl: config.passport.saml.logoutUrl, entryPoint: config.passport.saml.entryPoint, issuer: config.passport.saml.issuer, identifierFormat: null, // Service Provider private key decryptionPvk: fs.readFileSync(__dirname + '/cert/key.pem', 'utf8'), // Service Provider Certificate privateCert: fs.readFileSync(__dirname + '/cert/key.pem', 'utf8'), // Identity Provider's public key cert: fs.readFileSync(__dirname + '/cert/cert_idp.pem', 'utf8'), 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 }); }); 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'))); }); */ app.get('/', function (req, res) { res.redirect('/profile') }); app.get('/login', passport.authenticate(config.passport.strategy, { successRedirect: '/', failureRedirect: '/login' }) ); app.post(config.passport.saml.path, passport.authenticate(config.passport.strategy, { failureRedirect: '/', failureFlash: true }), function (req, res) { res.redirect('/'); } ); app.get('/profile', function (req, res) { if (req.isAuthenticated()) { res.render('profile', { user: req.user // useful for view engine, useless for HTML }); } else { res.redirect('/login'); } }); app.get('/services', function (req, res) { if (req.isAuthenticated()) { async.waterfall([ // get userId by email from userdb function(done) { methods.getUserIdByEmail(req.user.email, function(userId, err) { if (!err) { done(err, userId) } }) }, // get user-project-role from userdb function(userId, done) { methods.getUserProjectRole(userId, function(userProjects, err) { if (!err) { done(err, userProjects) } }) }, // get all projects from projectdb function(userProjects, done) { methods.getAllProjects(function(projectsOverview, err) { if (!err) { done(err, userProjects, projectsOverview) } }) }, // create JSON object of projects and user status for front-end function(userProjects, projectsOverview, done) { var allProjects = [] // JSON object var userProjectId = [] // array of user's project_id for (var i = 0; i < userProjects.length; i++) { userProjectId.push(userProjects[i].project_id) } for (var i = 0; i < projectsOverview.length; i++) { // check if projectId is exist in userProjectId[] var status = "You cannot access this service" if (userProjectId.indexOf(projectsOverview[i].id) > -1) { status = "You can access this service" } // add data to JSON object allProjects.push({ id: projectsOverview[i].id, title: projectsOverview[i].title, summary: projectsOverview[i].onelinesummary, cp: projectsOverview[i].contact_email, userStatus: status }); } // render the page res.render('services', { user: req.user, project: allProjects }); } ]) } else { res.redirect('/login'); } }); app.get('/security', function (req, res) { if (req.isAuthenticated()) { res.render('security', { user: req.user // useful for view engine, useless for HTML }); } else { res.redirect('/login'); } }); app.post('/updateProfile', function (req, res) { 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, } if (req.isAuthenticated()) { if (userData.email) { dbconn.query('UPDATE user SET ? WHERE email = "' +userData.email+'"', userData, function (err, rows, fields) { //if (err) throw err; if (err) { req.flash('error', "Failed"); } else { req.flash('success', 'Profile updated!'); } res.redirect('/profile'); }) } } else { 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.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') }) }); }); } } }) }) } else { res.redirect('/login'); } }); app.get('/forgotPwd', function (req, res) { 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"+ "Thanks,\nM4_LAB Team"; var emailSubject = "Account Access Attempted"; async.waterfall([ function(done) { crypto.randomBytes(20, function(err, buf) { var token = buf.toString('hex'); done(err, token); }); }, function(token, done) { methods.checkUserEmail(emailAddress, function(err, user){ if (user) { console.log("email: user found"); emailSubject = "M4_LAB Password Reset"; emailContent = "Hi User,\n\n"+ "we've received a request to reset your password. If you didn't make the request, just ignore this email.\n\n"+ "Otherwise, you can reset your password using this link: http://" + req.headers.host + "/reset/" + token + "\n" + "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) { done(err, token, user); }); } else { done(err, null, null); } }); }, function(token, user, done) { mailOptions.to = emailAddress; mailOptions.subject = emailSubject; mailOptions.text = emailContent; smtpTransport.sendMail(mailOptions, function(err) { done(err, 'done'); }); } ], function(err) { if (err) { req.flash('error', 'An error occured. Please try again.'); } else { req.flash('success', 'An e-mail has been sent to ' + emailAddress + ' with further instructions.'); } res.redirect('/forgotPwd'); }); }); app.get('/reset/:token', function(req, res) { methods.checkUserToken(req.params.token, function(err, user){ //console.log(user); if (!user) { req.flash('error', 'Password reset token is invalid or has expired.'); res.redirect('/forgotPwd'); } else { res.render('reset'); } }); }); app.post('/reset/:token', function(req, res) { methods.checkUserToken(req.params.token, function(err, user){ if (user) { // update password bcrypt.genSalt(saltRounds, function(err, salt) { bcrypt.hash(req.params.inputNewPwd, salt, function(err, hash) { methods.updatePassword(hash, user.email, function(err){ if (err) { req.flash('error', "Database error: Password cannot be modified.") throw err } else { req.flash('success', "Your pasword has been updated.") console.log('pasword updated!') // todo: send confirmation email } }) }); }); } else { req.flash('error', "User not found.") } }); res.redirect('/login') }); 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); }); }); // 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); } ); };