const config = require('config'); const express = require('express'); const fileUpload = require('express-fileupload'); const pug = require("pug"); const session = require("express-session"); const SequelizeSessionStore = require('connect-session-sequelize')(session.Store); const bodyParser = require("body-parser"); const path = require("path"); const passport = require('passport'); const LocalStrategy = require('passport-local').Strategy; const fs = require('fs'); module.exports = function () { var server = {}; server.run = (db) => { console.log("Starting Server") return new Promise((resolve, reject) => { const app = express(); app.use(fileUpload()); app.set("view engine", "pug"); app.set("views", path.resolve("server/views")); // compile pug views below so that they are rendered quicker var pugPages = {}; var modelDir = path.join(__dirname, './views'); fs.readdirSync(modelDir) .filter(file => { return file.endsWith(".pug"); }) .forEach(file => { const name = file.split(".pug")[0]; pugPages[name] = pug.compileFile(path.join(modelDir, file)) }); // Set up middleware to make use of the compiled pugjs views app.use((request, response, next) => { // This is cheating... response.display = (view, options) => { options.domain = config.get("server.domain"); options.urlEndpoint = request.originalUrl; options.query = request.query; // Set a default pageTitle... options.pageTitle = options.pageTitle ? options.pageTitle : "Mantra"; options.errorMessage = (error) => { // TODO: Make error human readable... return error.message; } if(config.get("server.compiled-render")) { if(pugPages[view]) { response.send(pugPages[view](options)) } else { response.send(pugPages["404"](options)) } } else { response.render(view, options); } } next(); }) app.use('/static', express.static("server/static")); // Session related stuff... function extendDefaultFields(defaults, session) { return { data: defaults.data, expires: session.cookie && session.cookie.expires ? session.cookie.expires : defaults.expires, // 157680000 userId: session.passport.user }; } var sessionStore = new SequelizeSessionStore({ db: db.sequelize, table: 'session', extendDefaultFields: extendDefaultFields // TODO: Define expiry and clean up... }); app.use(session({ store: sessionStore, secret: config.get("server.secret"), resave: false, saveUninitialized: false, cookie: config.get("server.cookie") })); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); passport.use(new LocalStrategy( function(username, password, done) { username = username != null ? username.trim().toLowerCase() : ""; password = password != null ? password : ""; return db.User.findOne({ include: [ { association: db.User.Passwords }, { association: db.User.UserEmails, required: true, where: { emailAddress: { [db.Sequelize.Op.like]: username } } }, { association: db.User.UserRoles, // required: true, include: [ { association: db.UserRole.Role, // required: true, // where: { // type: { // [db.Sequelize.Op.or]: roles // } // } } ] } ] }).then( user => { // Validate Password if(user) { if (!user.passwords[0].validPassword(password)) { return done(null, false, { username: username, message: 'Invalid username or password.' }); } else { return done(null, user); } } else { console.error("User Doesn't Exist: ", username); return done(null, false, { username: username, message: 'Invalid username or password.' }); } }, err => { console.error("Authentication Failed: " + err); return done(null, false, { message: 'Incorrect username or password.' }); } ).catch(error => { console.log("Passport Error: ", error); return done(error); }) } )); passport.serializeUser(function(user, done) { done(null, user.id); }); passport.deserializeUser(function(id, done) { // TODO: Add memberships and things like that... return db.User.findByPk(id, { include: [ { association: db.User.UserRoles, // required: true, include: [ { association: db.UserRole.Role, // required: true, // where: { // type: { // [db.Sequelize.Op.or]: roles // } // } } ] }, { association: db.User.IndividualEntityUser, include: [ { association: db.IndividualEntityUser.EntityUser, include: [ { association: db.EntityUser.Entity } ] } ] } ] }) .then(user => { return done(null, user); }).catch(error => { return done(error); }) }); app.use(passport.initialize()); app.use(passport.session()); sessionStore.sync(); // TODO: check that user has an individualEntityUser.... var router = require('./router/index.js')({ app: app, express: express, db: db, passport: passport }); app.use('/', router); // 404 pages... app.use((request, response, next) => { if(!response.headersSent) { response.status(404); if (request.accepts('html')) { response.display("404", { user: request.user, pageTitle: "Page Not Found - Mantra" }) return; } if (request.accepts('application/json')) { response.json({ message: "URL not accessible" }); return; } response.type('txt').send('Resource Not found'); } }); // 500 pages... app.use((error, request, response, next) => { console.error(`${response.getHeader("id")} - `, error); if(error && !response.headersSent) { response.status(500); // TODO: Check if production and then show stack... if (request.accepts('html')) { response.display("error", { user: request.user, pageTitle: "Internal Error - Mantra", error: error }) return; } if (request.xhr) { response.json({ message: "Internal Error, Please check your tires.", error: error }); return; } response.type('txt').send('Internal server error'); } }); const port = process.env.PORT || config.get("server.port"); app.listen(port); resolve(port); }); } return server; };