import fs from 'fs' import async from 'async' import bcrypt from 'bcryptjs' import * as passportSaml from 'passport-saml' import { dbConnection } from '../config/dbconn' import { dbController } from '../controller/dbController' import { gitlabController } from '../controller/gitlabController' import { miscConst } from '../config/const' import { mailer } from '../config/mailer' import { User } from '../classes/user' import { Website } from '../classes/website' import { Repo } from '../classes/repo' const SamlStrategy = passportSaml.Strategy const saltRounds = 10 const salt = 64 // salt length const logoDir = 'public/upload/' const defaultLogo: any = 'public/default/logo.png' module.exports = function (app: any, config: any, passport: any, lang: string) { // =========== PASSPORT ======= passport.serializeUser(function (user: any, done: any) { done(null, user) }) passport.deserializeUser(function (user: any, done: any) { done(null, user) }) const 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: undefined, // Service Provider private key decryptionPvk: fs.readFileSync(__dirname + '/cert/key.pem', 'utf8'), // Service Provider Certificate privateKey: 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: any, done: any) { return done(null, { id: profile.nameID, idFormat: profile.nameIDFormat, email: profile.email, firstName: profile.givenName, lastName: profile.sn }) }) passport.use(samlStrategy) // ============= SAML ============== app.post(config.passport.saml.path, passport.authenticate(config.passport.strategy, { failureRedirect: '/account/', failureFlash: true }), function (req: any, res: any) { res.redirect('/account/') } ) // to generate Service Provider's XML metadata app.get('/saml/metadata', function (req: any, res: any) { res.type('application/xml') const spMetadata = samlStrategy.generateServiceProviderMetadata(fs.readFileSync(__dirname + '/cert/cert.pem', 'utf8')) res.status(200).send(spMetadata) } ) // ======== APP ROUTES - ACCOUNT ==================== async function getLoggedInUserData (email: string) { const user = await dbController.getUserByEmail(email) if (!user) { console.log('no user found') return null } else { const loggedInUser = new User( user.id, email, user.salutation, user.title, user.firstname, user.lastname, user.industry, user.organisation, user.speciality, user.m4lab_idp, user.verificationStatus ) const userGitlabId = await dbController.getGitlabId(loggedInUser.id) if (userGitlabId) { loggedInUser.setGitlabUserId(userGitlabId) } return loggedInUser } } app.get('/', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { console.error('user data is not found') res.status(500).render(lang + '/500', { error: 'Your data is not found. Please try again.' }) } else { res.render(lang + '/account/home', { user: loggedInUser }) } } }) app.get('/login', passport.authenticate(config.passport.strategy, { successRedirect: '/', failureRedirect: '/login' }) ) app.get('/logout', function (req: any, res: any) { 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: any, uri: any) { req.logout() if (req.session) { req.session.destroy((err: any) => { if (err) { return console.log(err) } }) } return res.redirect(uri) }) }) app.get('/profile', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { // null user res.redirect('/account/') } else { if (loggedInUser.getVerificationStatus() !== 1) { res.redirect('/account/') } else { res.render(lang + '/account/profile', { user: loggedInUser }) } } } }) app.get('/services', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { // null user res.redirect('/account/') } else { if (loggedInUser.getVerificationStatus() !== 1) { // unverified users res.redirect('/account/') } else { const gitlabReposArr = [] const gitlabPagesArr = [] if (loggedInUser.getGitlabUserId()) { // for users who have activated their gitlab account const userProjects = await gitlabController.getUserProjects(loggedInUser.getGitlabUserId()!) if (userProjects) { let project: any for (project in userProjects) { if (userProjects[project].tag_list.includes('website')) { const page = { projectInformation: new Website(loggedInUser.getGitlabUserId()!, userProjects[project].name, userProjects[project].description, userProjects[project].id, userProjects[project].avatar_url, userProjects[project].path_with_namespace), pipelineStatus: await gitlabController.getProjectPipelineLatestStatus(userProjects[project].id) } gitlabPagesArr.push(page) } else { const repo = new Repo(loggedInUser.getGitlabUserId()!, userProjects[project].name, userProjects[project].description, userProjects[project].id, userProjects[project].avatar_url, userProjects[project].path_with_namespace) gitlabReposArr.push(repo) } } } res.render(lang + '/account/services', { user: loggedInUser, gitlabRepos: gitlabReposArr, gitlabPages: gitlabPagesArr }) } else { // for users who have not activated their gitlab account yet const gitlabUser = await gitlabController.getUserByEmail(loggedInUser.getEmail()) if (!gitlabUser) { res.render(lang + '/account/services', { user: loggedInUser, gitlabRepos: null, gitlabPages: null }) } else { const gitlabActivationData = { user_id: loggedInUser.getId(), gitlab_userId: gitlabUser.id } dbController.addGitlabUser(gitlabActivationData, function (err: any) { if (err) { res.status(500).render(lang + '/500', { error: err }) } else { res.redirect('/account/services') } }) } } } } } }) app.get('/security', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { // null user res.redirect('/account/') } else { if (loggedInUser.getVerificationStatus() === 1 && loggedInUser.getIdpStatus() === 1) { res.render(lang + '/account/security', { user: loggedInUser }) } else { res.redirect('/account/') } } } }) app.post('/updateProfile', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { // null user res.redirect('/account/') } else { const userData = { salutation: req.body.inputSalutation, 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 } const result = await dbController.updateUserById(loggedInUser.getId(), userData) if (!result) { res.flash('error', 'Failed') } else { loggedInUser.updateProfile(userData.salutation, userData.title, userData.firstname, userData.lastname, userData.email, userData.organisation, userData.industry, userData.speciality) res.flash('success', 'Ihr Benutzerprofil wurde aktualisiert!') } res.redirect('/account/profile') } } }) app.post('/changePwd', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { // null user res.redirect('/account/') } else { const currPwd = req.body.inputCurrPwd const newPwd = req.body.inputNewPwd const retypePwd = req.body.inputConfirm dbConnection.user.query('SELECT password FROM credential WHERE user_id=' + loggedInUser.getId(), function (err: any, rows: any) { if (err) { console.error(err) res.status(500).render(lang + '/500', { error: err }) } const userPwd = rows[0].password // check if the password is correct bcrypt.compare(currPwd, userPwd, function (err, isMatch) { if (err) { console.error(err) res.status(500).render(lang + '/500', { error: err }) } else if (!isMatch) { res.flash('error', 'Das Passwort ist leider falsch. Bitte überprüfen Sie Ihre Eingabe.') res.redirect('/account/security') } else { if (newPwd !== retypePwd) { res.flash('error', 'Passwörter stimmen nicht überein. Bitte stellen Sie sicher, dass Sie das Passwort beide Male genau gleich eingeben.') res.redirect('/account/security') } else { // update password bcrypt.genSalt(saltRounds, function (err, salt) { bcrypt.hash(newPwd, salt, async function (err, hash) { const credentialData = { password: hash, user_id: loggedInUser.getId() } const result = await dbController.updateCredential(credentialData) if (!result) { console.log('Failed to reset password') res.flash('error', 'Datenbankfehler: Passwort kann nicht geändert werden.') } else { res.flash('success', 'Passwort aktualisiert!') // send notifiaction email mailer.options.to = loggedInUser.getEmail() mailer.options.subject = miscConst.updatePasswordMailSubject mailer.options.html = miscConst.updatePasswordMailContent + '
' + miscConst.mailSignature + '
' mailer.transporter.sendMail(mailer.options, function (err: any) { if (err) { console.log(err) } }) } res.redirect('/account/security') }) }) } } }) }) } } }) app.get('/resendVerificationEmail', async function (req: any, res: any) { if (!req.isAuthenticated) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { res.redirect('/login') } else { const token = await dbController.getVerificationTokenByUserId(loggedInUser.id) if (!token) { res.send(false) } else { // send email const emailSubject = 'Bitte bestätigen Sie Ihr M4_LAB Benutzerkonto' const emailContent = '
Lieber Nutzer,

' + '

vielen Dank für Ihre Anmeldung am Transferportal der HFT Stuttgart.
' + 'Um Ihre Anmeldung zu bestätigen, klicken Sie bitte diesen Link: ' + config.app.host + '/verifyAccount?token=' + token + '

' + 'Ohne Bestätigung Ihres Kontos müssen wir Ihr Konto leider nach 7 Tagen löschen.


' + miscConst.mailSignature + '
' mailer.options.to = loggedInUser.email mailer.options.subject = emailSubject mailer.options.html = emailContent mailer.transport.sendMail(mailer.options, function (err: any) { if (err) { console.log('cannot send email') throw err } }) res.send(true) } } } }) // ============= NEW GITLAB PAGES =========================== app.get('/newInformation', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { res.redirect('/login') } else { const gitlabUser = await gitlabController.getUserByEmail(loggedInUser.getEmail()) if (!gitlabUser) { // no user found res.redirect('/account/services') } else { res.render(lang + '/account/newInformation', { user: loggedInUser, gitlabUsername: gitlabUser.username }) } } } }) app.post('/newInformation', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { res.redirect('/login') } else { if (!req.body.name && !req.body.description) { res.flash('error', 'Bitte geben Sie die benötigten Daten ein') res.redirect('/account/newInformation') } else { const projectName = req.body.name.toLowerCase().replace(/\s/g, '-') const projectDesc = req.body.description const projectTemplate = req.body.template const newInformation = new Website(loggedInUser.getGitlabUserId()!, projectName, projectDesc) let newLogoFile = defaultLogo if (req.files) { newLogoFile = req.files.logo } async.waterfall([ function (callback: any) { // upload logo if (!req.files) { callback(null, newLogoFile) } else { newLogoFile.mv(logoDir + newLogoFile.name, function (err: any) { newLogoFile = logoDir + newLogoFile.name callback(err, newLogoFile) }) } }, async function (newLogoFile: any) { // create a new GitLab Page const newPages = await gitlabController.createNewPages(newInformation, newLogoFile, projectTemplate) if (newPages.status) { if (newPages.data.message.name === 'has already been taken') { res.flash('error', "Der Projektname '" + newInformation.getName() + "' ist bereits vergeben, bitte wählen Sie einen anderen Namen.") } else { res.flash('error', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut. ') } res.redirect('/account/newInformation') } else { res.flash('success', 'Ihre Webseite wurde erstellt, aber noch nicht veröffentlicht. Um Ihre Webseite endgültig zu veröffentlichen, ' + 'schließen Sie die Einrichtung gemäß unten stehender Anleitung ab.') res.redirect('/account/updateInformation?id=' + String(newPages.id)) } } ], function (err) { if (err != null) console.log(err) // remove logo if (req.files) { fs.unlink(newLogoFile, (err) => { if (err != null) console.log(err) }) } }) } } } }) app.get('/updateInformation', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { res.redirect('/login') } else { if (!req.query.id) { res.redirect('/account/services') } else { const project = await gitlabController.getProjectById(req.query.id) if (!project) { console.log(' ========= Error or no project found') res.redirect('/account/services') } else if (!project.owner) { console.log(' ========= Project cannot be accessed, since it does not have an owner') res.redirect('/account/services') } else if (project.owner.id !== loggedInUser.getGitlabUserId()) { console.log(' ========= Access denied: Not your project') res.redirect('/account/services') } else { const curInformation = new Website(loggedInUser.getGitlabUserId()!, project.name, project.description, req.query.id, project.avatar_url, project.path_with_namespace) res.render(lang + '/account/updateInformation', { user: loggedInUser, information: curInformation }) } } } } }) // update a website app.post('/updateInformation', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { res.redirect('/login') } else { if (!req.body.name && !req.body.description) { res.flash('error', 'Bitte geben Sie die benötigten Daten ein') res.redirect('/account/updateInformation') } else { const projectName = req.body.name.toLowerCase().replace(/\s/g, '-') const projectDesc = req.body.description const updatedInformation = new Website(loggedInUser.getGitlabUserId()!, projectName, projectDesc, req.query.id) let newLogoFile: any async.waterfall([ function (callback: any) { // upload logo if (!req.files) { callback(null, newLogoFile) } else { newLogoFile = req.files.logo newLogoFile.mv(logoDir + String(newLogoFile.name), function (err: any) { newLogoFile = logoDir + String(newLogoFile.name) callback(err, newLogoFile) }) } }, async function (newLogoFile: any) { // update gitlab page const updatedPages = await gitlabController.updateProject(updatedInformation, newLogoFile) if (updatedPages.status) { if (updatedPages.data.message.name === 'has already been taken') { res.flash('error', "Der Projektname '" + String(projectName) + "' ist bereits vergeben, bitte wählen Sie einen anderen Namen.") } else { res.flash('error', 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut. ') } } else { updatedInformation.setLogo(updatedPages.avatar_url) updatedInformation.setPath(updatedPages.path) res.flash('success', 'Ihre Website wurde aktualisiert') } res.redirect('/account/updateInformation?id=' + String(updatedInformation.getId())) } ], function (err) { if (err != null) console.log(err) if (newLogoFile) { // remove logo fs.unlink(newLogoFile, (err) => { if (err != null) console.log(err) }) } }) } } } }) app.delete('/deleteProject', async function (req: any, res: any) { if (!req.isAuthenticated()) { res.redirect('/login') } else { const loggedInUser = await getLoggedInUserData(req.user.email) if (loggedInUser == null) { res.redirect('/login') } else { const projectId = req.body.id if (projectId) { // check if the owner is valid const project = await gitlabController.getProjectById(projectId) if (!project) { console.log(' ========= Error or no project found') } else if (!project.owner) { console.log(' ========= Project cannot be accessed, since it does not have an owner') } else if (project.owner.id !== loggedInUser.getGitlabUserId()) { console.log(' ========= Access denied: Not your project') } else { const isDeleted = await gitlabController.deleteProjectById(projectId) if (!isDeleted) { res.flash('error', 'Project cannot be deleted. Please try again.') } } } res.redirect('/account/services') } } }) }