Base server logic
This commit is contained in:
		
							
								
								
									
										13
									
								
								controllers/admin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								controllers/admin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var express = require('express'); | ||||
| var router = express.Router(); | ||||
|  | ||||
| /** | ||||
|  * Admin | ||||
|  */ | ||||
| router.get('/', (req, res) => { | ||||
| 	res.send('OK'); | ||||
| }); | ||||
|  | ||||
| module.exports = router; | ||||
							
								
								
									
										73
									
								
								controllers/auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								controllers/auth.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| var express = require('express'); | ||||
| var router = express.Router(); | ||||
| var passport = require('passport'); | ||||
| var ExpressBrute = require('express-brute'); | ||||
| var ExpressBruteRedisStore = require('express-brute-redis'); | ||||
| var moment = require('moment'); | ||||
|  | ||||
| /** | ||||
|  * Setup Express-Brute | ||||
|  */ | ||||
| var EBstore = new ExpressBruteRedisStore({ | ||||
|     prefix: 'bf:', | ||||
|     client: red | ||||
| }); | ||||
| var bruteforce = new ExpressBrute(EBstore, { | ||||
| 	freeRetries: 5, | ||||
| 	minWait: 60 * 1000, | ||||
| 	maxWait: 5 * 60 * 1000, | ||||
| 	refreshTimeoutOnRequest: false, | ||||
| 	failCallback(req, res, next, nextValidRequestDate) { | ||||
| 		req.flash('alert', { | ||||
| 	      class: 'error', | ||||
| 	      title: 'Too many attempts!', | ||||
| 	      message:  "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.', | ||||
| 	      iconClass: 'fa-times' | ||||
| 	    }); | ||||
| 		res.redirect('/login'); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Login form | ||||
|  */ | ||||
| router.get('/login', function(req, res, next) { | ||||
| 	res.render('auth/login', { | ||||
| 		usr: res.locals.usr | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| router.post('/login', bruteforce.prevent, function(req, res, next) { | ||||
| 		passport.authenticate('local', function(err, user, info) { | ||||
|  | ||||
| 			if (err) { return next(err); } | ||||
|  | ||||
| 			if (!user) { | ||||
| 				req.flash('alert', { | ||||
| 					class: 'error', | ||||
| 					title: 'Invalid login', | ||||
| 					message:  "The email or password is invalid.", | ||||
| 					iconClass: 'fa-times' | ||||
| 				}); | ||||
| 				return res.redirect('/login'); | ||||
| 			} | ||||
|  | ||||
| 			req.logIn(user, function(err) { | ||||
| 	      if (err) { return next(err); } | ||||
| 	      req.brute.reset(function () { | ||||
| 				return res.redirect('/'); | ||||
| 			}); | ||||
| 	    }); | ||||
|  | ||||
| 		})(req, res, next); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Logout | ||||
|  */ | ||||
| router.get('/logout', function(req, res) { | ||||
| 	req.logout(); | ||||
| 	res.redirect('/'); | ||||
| }); | ||||
|  | ||||
| module.exports = router; | ||||
							
								
								
									
										13
									
								
								controllers/pages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								controllers/pages.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var express = require('express'); | ||||
| var router = express.Router(); | ||||
|  | ||||
| /** | ||||
|  * Home | ||||
|  */ | ||||
| router.get('/', (req, res) => { | ||||
| 	res.send('OK'); | ||||
| }); | ||||
|  | ||||
| module.exports = router; | ||||
							
								
								
									
										17
									
								
								middlewares/flash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								middlewares/flash.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| "use strict"; | ||||
|  | ||||
| /** | ||||
|  * Flash middleware | ||||
|  * | ||||
|  * @param      {Express Request}   req     Express Request object | ||||
|  * @param      {Express Response}  res     Express Response object | ||||
|  * @param      {Function}          next    Next callback function | ||||
|  * @return     {any}               void | ||||
|  */ | ||||
| module.exports = (req, res, next) => { | ||||
|  | ||||
| 	res.locals.appflash = req.flash('alert'); | ||||
|  | ||||
| 	next(); | ||||
|  | ||||
| }; | ||||
							
								
								
									
										65
									
								
								models/auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								models/auth.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| var LocalStrategy = require('passport-local').Strategy; | ||||
|  | ||||
| module.exports = function(passport, appconfig) { | ||||
|  | ||||
|     // Serialization user methods | ||||
|  | ||||
|     passport.serializeUser(function(user, done) { | ||||
|         done(null, user._id); | ||||
|     }); | ||||
|  | ||||
|     passport.deserializeUser(function(id, done) { | ||||
|         db.User.findById(id).then((user) => { | ||||
|             done(null, user); | ||||
|         }).catch((err) => { | ||||
|             done(err, null); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     // Setup local user authentication strategy | ||||
|  | ||||
|     passport.use( | ||||
|         'local', | ||||
|         new LocalStrategy({ | ||||
|             usernameField : 'email', | ||||
|             passwordField : 'password', | ||||
|             passReqToCallback : true | ||||
|         }, | ||||
|         function(req, uEmail, uPassword, done) { | ||||
|             db.User.findOne({ 'email' :  uEmail }).then((user) => { | ||||
|                 if (user) { | ||||
|                     user.validatePassword(uPassword).then((isValid) => { | ||||
|                         return (isValid) ? done(null, user) : done(null, false); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     return done(null, false); | ||||
|                 } | ||||
|             }).catch((err) => { | ||||
|                 done(err); | ||||
|             }); | ||||
|         }) | ||||
|     ); | ||||
|  | ||||
|     // Check for admin access | ||||
|  | ||||
|     db.connectPromise.then(() => { | ||||
|  | ||||
|         db.User.count().then((count) => { | ||||
|             if(count < 1) { | ||||
|                 winston.info('No administrator account found. Creating a new one...'); | ||||
|                 db.User.new({ | ||||
|                     email: appconfig.admin, | ||||
|                     firstName: "Admin", | ||||
|                     lastName: "Admin", | ||||
|                     password: "admin123" | ||||
|                 }).then(() => { | ||||
|                     winston.info('Administrator account created successfully!'); | ||||
|                 }).catch((ex) => { | ||||
|                     winston.error('An error occured while creating administrator account: ' + ex); | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
|  | ||||
| }; | ||||
| @@ -31,6 +31,7 @@ | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "auto-load": "^2.1.0", | ||||
|     "bcryptjs-then": "^1.0.1", | ||||
|     "bluebird": "^3.4.1", | ||||
|     "body-parser": "^1.15.2", | ||||
|     "compression": "^1.6.2", | ||||
| @@ -54,7 +55,7 @@ | ||||
|     "moment-timezone": "^0.5.5", | ||||
|     "mongoose": "^4.5.9", | ||||
|     "mongoose-delete": "^0.3.4", | ||||
|     "node-bcrypt": "0.0.1", | ||||
|     "nodegit": "^0.14.1", | ||||
|     "passport": "^0.3.2", | ||||
|     "passport-local": "^1.0.0", | ||||
|     "pug": "^2.0.0-beta5", | ||||
|   | ||||
							
								
								
									
										171
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										171
									
								
								server.js
									
									
									
									
									
								
							| @@ -14,5 +14,174 @@ winston.info('Requarks Wiki is initializing...'); | ||||
| global.ROOTPATH = __dirname; | ||||
|  | ||||
| var appconfig = require('./models/config')('./config.yml'); | ||||
| global.db = require('./models/db')(appconfig); | ||||
| global.db = require('./models/mongodb')(appconfig); | ||||
| global.red = require('./models/redis')(appconfig); | ||||
|  | ||||
| var _ = require('lodash'); | ||||
| var express = require('express'); | ||||
| var path = require('path'); | ||||
| var favicon = require('serve-favicon'); | ||||
| var session = require('express-session'); | ||||
| var redisStore = require('connect-redis')(session); | ||||
| var cookieParser = require('cookie-parser'); | ||||
| var bodyParser = require('body-parser'); | ||||
| var flash = require('connect-flash'); | ||||
| var compression = require('compression'); | ||||
| var passport = require('passport'); | ||||
| var autoload = require('auto-load'); | ||||
| var expressValidator = require('express-validator'); | ||||
| var http = require('http'); | ||||
|  | ||||
| global.lang = require('i18next'); | ||||
| var i18next_backend = require('i18next-node-fs-backend'); | ||||
| var i18next_mw = require('i18next-express-middleware'); | ||||
|  | ||||
| var mw = autoload(path.join(ROOTPATH, '/middlewares')); | ||||
| var ctrl = autoload(path.join(ROOTPATH, '/controllers')); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Define Express App | ||||
| // ---------------------------------------- | ||||
|  | ||||
| global.app = express(); | ||||
| global.ROOTPATH = __dirname; | ||||
| var _isDebug = (app.get('env') === 'development'); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Security | ||||
| // ---------------------------------------- | ||||
|  | ||||
| app.use(mw.security); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Passport Authentication | ||||
| // ---------------------------------------- | ||||
|  | ||||
| var strategy = require('./models/auth')(passport, appconfig); | ||||
|  | ||||
| app.use(cookieParser()); | ||||
| app.use(session({ | ||||
|   name: 'requarkswiki.sid', | ||||
|   store: new redisStore({ client: red }), | ||||
|   secret: appconfig.sessionSecret, | ||||
|   resave: false, | ||||
|   saveUninitialized: false | ||||
| })); | ||||
| app.use(flash()); | ||||
| app.use(passport.initialize()); | ||||
| app.use(passport.session()); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Localization Engine | ||||
| // ---------------------------------------- | ||||
|  | ||||
| lang | ||||
|   .use(i18next_backend) | ||||
|   .use(i18next_mw.LanguageDetector) | ||||
|   .init({ | ||||
|     load: 'languageOnly', | ||||
|     ns: ['common'], | ||||
|     defaultNS: 'common', | ||||
|     saveMissing: false, | ||||
|     supportedLngs: ['en', 'fr'], | ||||
|     preload: ['en', 'fr'], | ||||
|     fallbackLng : 'en', | ||||
|     backend: { | ||||
|       loadPath: './locales/{{lng}}/{{ns}}.json' | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // View Engine Setup | ||||
| // ---------------------------------------- | ||||
|  | ||||
| app.use(compression()); | ||||
|  | ||||
| app.use(i18next_mw.handle(lang)); | ||||
| app.set('views', path.join(ROOTPATH, 'views')); | ||||
| app.set('view engine', 'pug'); | ||||
|  | ||||
| //app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico'))); | ||||
| app.use(bodyParser.json()); | ||||
| app.use(bodyParser.urlencoded({ extended: false })); | ||||
| app.use(expressValidator()); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Public Assets | ||||
| // ---------------------------------------- | ||||
|  | ||||
| app.use(express.static(path.join(ROOTPATH, 'assets'))); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // View accessible data | ||||
| // ---------------------------------------- | ||||
|  | ||||
| app.locals._ = require('lodash'); | ||||
| app.locals.moment = require('moment'); | ||||
| app.locals.appconfig = appconfig; | ||||
| //app.locals.appdata = require('./data.json'); | ||||
| app.use(mw.flash); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Controllers | ||||
| // ---------------------------------------- | ||||
|  | ||||
| app.use('/', ctrl.auth); | ||||
|  | ||||
| app.use('/', ctrl.pages); | ||||
| app.use('/admin', mw.auth, ctrl.admin); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Error handling | ||||
| // ---------------------------------------- | ||||
|  | ||||
| // catch 404 and forward to error handler | ||||
| app.use(function(req, res, next) { | ||||
|   var err = new Error('Not Found'); | ||||
|   err.status = 404; | ||||
|   next(err); | ||||
| }); | ||||
|  | ||||
| // error handlers | ||||
| app.use(function(err, req, res, next) { | ||||
|   res.status(err.status || 500); | ||||
|   res.render('error', { | ||||
|     message: err.message, | ||||
|     error: _isDebug ? err : {} | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| // ---------------------------------------- | ||||
| // Start HTTP server | ||||
| // ---------------------------------------- | ||||
|  | ||||
| winston.info('Requarks Wiki has initialized successfully.'); | ||||
|  | ||||
| winston.info('Starting HTTP server on port ' + appconfig.port + '...'); | ||||
|  | ||||
| app.set('port', appconfig.port); | ||||
| var server = http.createServer(app); | ||||
| server.listen(appconfig.port); | ||||
| server.on('error', (error) => { | ||||
|   if (error.syscall !== 'listen') { | ||||
|     throw error; | ||||
|   } | ||||
|  | ||||
|   // handle specific listen errors with friendly messages | ||||
|   switch (error.code) { | ||||
|     case 'EACCES': | ||||
|       console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!'); | ||||
|       process.exit(1); | ||||
|       break; | ||||
|     case 'EADDRINUSE': | ||||
|       console.error('Port ' + appconfig.port + ' is already in use!'); | ||||
|       process.exit(1); | ||||
|       break; | ||||
|     default: | ||||
|       throw error; | ||||
|   } | ||||
| }); | ||||
|  | ||||
| server.on('listening', () => { | ||||
|   winston.info('HTTP server started successfully! [RUNNING]'); | ||||
| }); | ||||
							
								
								
									
										1
									
								
								views/auth/login.pug
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								views/auth/login.pug
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| DUDE | ||||
							
								
								
									
										21
									
								
								views/error.pug
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								views/error.pug
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| doctype html | ||||
| html | ||||
| 	head | ||||
| 		meta(http-equiv='X-UA-Compatible', content='IE=edge') | ||||
| 		meta(charset='UTF-8') | ||||
| 		title= appconfig.title | ||||
|  | ||||
| 		// Favicon | ||||
| 		each favsize in [32, 96, 16] | ||||
| 			link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize href='/images/favicon-' + favsize + 'x' + favsize + '.png') | ||||
|  | ||||
| 		// CSS | ||||
| 		link(href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700|Inconsolata', rel='stylesheet', type='text/css') | ||||
| 		link(type='text/css', rel='stylesheet', href='/css/app.css') | ||||
|  | ||||
| 	body(class='server-error') | ||||
| 		#root | ||||
| 			img(src='/images/logo-text_218x80.png') | ||||
| 			h1 Oops, something went wrong | ||||
| 			h4= message | ||||
| 			pre #{error.stack} | ||||
							
								
								
									
										28
									
								
								views/layout.pug
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								views/layout.pug
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| doctype html | ||||
| html | ||||
| 	head | ||||
| 		meta(http-equiv='X-UA-Compatible', content='IE=edge') | ||||
| 		meta(charset='UTF-8') | ||||
| 		meta(name='theme-color', content='#009688') | ||||
| 		meta(name='msapplication-TileColor', content='#009688') | ||||
| 		title= appconfig.title | ||||
|  | ||||
| 		// Favicon | ||||
| 		each favsize in [32, 96, 16] | ||||
| 			link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize href='/images/favicon-' + favsize + 'x' + favsize + '.png') | ||||
|  | ||||
| 		// CSS | ||||
| 		link(type='text/css', rel='stylesheet', href='/css/libs.css') | ||||
| 		link(type='text/css', rel='stylesheet', href='/css/app.css') | ||||
|  | ||||
| 		block head | ||||
|  | ||||
| 	body | ||||
| 		#root | ||||
| 			include ./common/header | ||||
| 			include ./common/alerts | ||||
| 			main | ||||
| 				block content | ||||
| 			include ./common/footer | ||||
|  | ||||
| 		block outside | ||||
		Reference in New Issue
	
	Block a user