Base server logic
This commit is contained in:
parent
991efbb1bc
commit
0f96377e30
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": {
|
"dependencies": {
|
||||||
"auto-load": "^2.1.0",
|
"auto-load": "^2.1.0",
|
||||||
|
"bcryptjs-then": "^1.0.1",
|
||||||
"bluebird": "^3.4.1",
|
"bluebird": "^3.4.1",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
"compression": "^1.6.2",
|
"compression": "^1.6.2",
|
||||||
@ -54,7 +55,7 @@
|
|||||||
"moment-timezone": "^0.5.5",
|
"moment-timezone": "^0.5.5",
|
||||||
"mongoose": "^4.5.9",
|
"mongoose": "^4.5.9",
|
||||||
"mongoose-delete": "^0.3.4",
|
"mongoose-delete": "^0.3.4",
|
||||||
"node-bcrypt": "0.0.1",
|
"nodegit": "^0.14.1",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pug": "^2.0.0-beta5",
|
"pug": "^2.0.0-beta5",
|
||||||
|
173
server.js
173
server.js
@ -14,5 +14,174 @@ winston.info('Requarks Wiki is initializing...');
|
|||||||
global.ROOTPATH = __dirname;
|
global.ROOTPATH = __dirname;
|
||||||
|
|
||||||
var appconfig = require('./models/config')('./config.yml');
|
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);
|
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
|
Loading…
Reference in New Issue
Block a user