use (LMP)loopback middleware phases

bump loopback-component-passport which uses LMP
move custom middlewares to middlewares directory
This commit is contained in:
Berkeley Martinez
2015-08-04 01:25:34 -07:00
parent aa10e1ff69
commit f01a66c5c7
14 changed files with 259 additions and 248 deletions

View File

@ -66,7 +66,7 @@
"lodash": "^3.9.3", "lodash": "^3.9.3",
"loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password", "loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password",
"loopback-boot": "^2.8.0", "loopback-boot": "^2.8.0",
"loopback-component-passport": "1.4.0", "loopback-component-passport": "^1.5.0",
"loopback-connector-mongodb": "^1.10.0", "loopback-connector-mongodb": "^1.10.0",
"lusca": "~1.0.2", "lusca": "~1.0.2",
"method-override": "~2.3.0", "method-override": "~2.3.0",

View File

@ -1,27 +1,60 @@
{ {
"initial:before": { "initial:before": {
"loopback#favicon": {} "loopback#favicon": {
"params": "$!../public/favicon.ico"
}
}, },
"initial": { "initial": {
"compression": {} "compression": {},
"morgan": {
"params": "dev"
}
}, },
"session": { "session": {
"./middlewares/sessions.js": {}
},
"auth:before": {
"./middlewares/add-return-to": {}
}, },
"auth": { "auth": {
}, },
"parse": { "parse": {
"body-parser#json": {},
"body-parser#urlencoded": {
"params": { "extended": true }
},
"method-override": {},
"./middlewares/cookie-parser": {}
},
"parse:after": {
"./middlewares/validator": {}
},
"routes:before": {
"express-flash": {},
"helmet#xssFilter": {},
"helmet#noSniff": {},
"helmet#frameguard": {},
"./middlewares/constant-headers": {},
"./middlewares/csp": {},
"./middlewares/express-rx": {},
"./middlewares/global-locals": {}
}, },
"routes": { "routes": {
}, },
"files": { "files": {
"loopback#static": { "loopback#static": {
"params": "$!../public" "params": [
"$!../public",
{
"maxAge": "86400000"
}
]
} }
}, },
"final": { "final": {
"loopback#urlNotFound": {}
}, },
"final:after": { "final:after": {
"errorhandler": {} "./middlewares/keymetrics": {},
"./middlewares/error-handlers": {}
} }
} }

View File

@ -0,0 +1,13 @@
export default function addReturnToUrl() {
return function(req, res, next) {
// Remember original destination before login.
var path = req.path.split('/')[1];
if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) {
return next();
} else if (/\/stories\/\w+/i.test(req.path)) {
return next();
}
req.session.returnTo = req.path;
next();
};
}

View File

@ -0,0 +1,9 @@
export default function constantHeaders() {
return function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
};
}

View File

@ -0,0 +1,4 @@
import cookieParser from 'cookie-parser';
import secrets from '../../config/secrets';
export default cookieParser.bind(cookieParser, secrets.cookieSecret);

98
server/middlewares/csp.js Normal file
View File

@ -0,0 +1,98 @@
import helmet from 'helmet';
const trusted = [
"'self'",
'blob:',
'104.236.218.15',
'*.freecodecamp.com',
'http://www.freecodecamp.com',
'https://www.freecodecamp.com',
'https://freecodecamp.com',
'https://freecodecamp.org',
'*.freecodecamp.org',
// NOTE(berks): add the following as the blob above was not covering www
'http://www.freecodecamp.org',
'ws://freecodecamp.com/',
'ws://www.freecodecamp.com/',
'*.gstatic.com',
'*.google-analytics.com',
'*.googleapis.com',
'*.google.com',
'*.gstatic.com',
'*.doubleclick.net',
'*.twitter.com',
'*.twitch.tv',
'*.twimg.com',
"'unsafe-eval'",
"'unsafe-inline'",
'*.bootstrapcdn.com',
'*.cloudflare.com',
'https://*.cloudflare.com',
'localhost:3001',
'ws://localhost:3001/',
'http://localhost:3001',
'localhost:3000',
'ws://localhost:3000/',
'http://localhost:3000',
'*.ionicframework.com',
'https://syndication.twitter.com',
'*.youtube.com',
'*.jsdelivr.net',
'https://*.jsdelivr.net',
'*.ytimg.com',
'*.bitly.com',
'http://cdn.inspectlet.com/',
'https://cdn.inspeclet.com/',
'wss://inspectletws.herokuapp.com/',
'http://hn.inspectlet.com/',
'*.googleapis.com',
'*.gstatic.com',
'https://hn.inspectlet.com/'
];
export default function csp() {
return helmet.csp({
defaultSrc: trusted,
scriptSrc: [
'*.optimizely.com',
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js',
'http://beta.freecodecamp.com'
].concat(trusted),
'connect-src': [
'vimeo.com'
].concat(trusted),
styleSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
imgSrc: [
// allow all input since we have user submitted images for
// public profile
'*'
].concat(trusted),
fontSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
mediaSrc: [
'*.amazonaws.com',
'*.twitter.com'
].concat(trusted),
frameSrc: [
'*.gitter.im',
'*.gitter.im https:',
'*.vimeo.com',
'*.twitter.com',
'*.ghbtns.com'
].concat(trusted),
// set to true if you only want to report errors
reportOnly: false,
// set to true if you want to set all headers
setAllHeaders: false,
// set to true if you want to force buggy CSP in Safari 5
safari5: false
});
}

View File

@ -0,0 +1,43 @@
import errorHanlder from 'errorhandler';
import accepts from 'accepts';
export default function prodErrorHandler() {
if (process.env.NODE_ENV === 'development') {
return errorHanlder({ log: true });
}
// error handling in production.
// disabling eslint due to express parity rules for error handlers
return function(err, req, res, next) { // eslint-disable-line
// respect err.status
if (err.status) {
res.statusCode = err.status;
}
// default status code to 500
if (res.statusCode < 400) {
res.statusCode = 500;
}
// parse res type
var accept = accepts(req);
var type = accept.type('html', 'json', 'text');
var message = 'opps! Something went wrong. Please try again later';
if (type === 'html') {
req.flash('errors', {
msg: message
});
return res.redirect('/');
// json
} else if (type === 'json') {
res.setHeader('Content-Type', 'application/json');
return res.send({
message: message
});
// plain text
} else {
res.setHeader('Content-Type', 'text/plain');
return res.send(message);
}
};
}

View File

@ -0,0 +1,10 @@
import { Observable } from 'rx';
// add rx methods to express
export default function expressRx() {
return function expressRx(req, res, next) {
// render to observable stream
res.render$ = Observable.fromNodeCallback(res.render, res);
next();
};
}

View File

@ -0,0 +1,7 @@
export default function globalLocals() {
return function(req, res, next) {
// Make user object available in templates.
res.locals.user = req.user;
next();
};
}

View File

@ -0,0 +1,8 @@
import pmx from 'pmx';
export default function keymetrics() {
if (process.env.NODE_ENV !== 'production') {
return (err, req, res, next) => next(err);
}
return pmx.expressErrorHandler();
}

View File

@ -0,0 +1,17 @@
import session from 'express-session';
import MongoStoreFactory from 'connect-mongo';
import secrets from '../../config/secrets';
const MongoStore = MongoStoreFactory(session);
export default function sessionsMiddleware() {
return session({
resave: true,
saveUninitialized: true,
secret: secrets.sessionSecret,
store: new MongoStore({
url: secrets.db,
'autoReconnect': true
})
});
}

View File

@ -0,0 +1,9 @@
import validator from 'express-validator';
export default validator.bind(validator, {
customValidators: {
matchRegex: function matchRegex(param, regex) {
return regex.test(param);
}
}
});

View File

@ -5,28 +5,9 @@ pmx.init();
var assign = require('lodash').assign, var assign = require('lodash').assign,
loopback = require('loopback'), loopback = require('loopback'),
boot = require('loopback-boot'), boot = require('loopback-boot'),
accepts = require('accepts'),
cookieParser = require('cookie-parser'),
compress = require('compression'),
session = require('express-session'),
expressState = require('express-state'), expressState = require('express-state'),
logger = require('morgan'),
errorHandler = require('errorhandler'),
methodOverride = require('method-override'),
bodyParser = require('body-parser'),
helmet = require('helmet'),
MongoStore = require('connect-mongo')(session),
flash = require('express-flash'),
path = require('path'), path = require('path'),
expressValidator = require('express-validator'), passportProviders = require('./passport-providers');
lessMiddleware = require('less-middleware'),
passportProviders = require('./passport-providers'),
rxMiddleware = require('./utils/rx').rxMiddleware,
/**
* API keys and Passport configuration.
*/
secrets = require('./../config/secrets');
var generateKey = var generateKey =
require('loopback-component-passport/lib/models/utils').generateKey; require('loopback-component-passport/lib/models/utils').generateKey;
@ -45,176 +26,16 @@ var passportConfigurator = new PassportConfigurator(app);
app.set('port', process.env.PORT || 3000); app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade'); app.set('view engine', 'jade');
app.use(compress());
app.use(lessMiddleware(path.join(__dirname, '/public')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(expressValidator({
customValidators: {
matchRegex: function(param, regex) {
return regex.test(param);
}
}
}));
app.use(methodOverride());
app.use(cookieParser(secrets.cookieSecret));
app.use(session({
resave: true,
saveUninitialized: true,
secret: secrets.sessionSecret,
store: new MongoStore({
url: secrets.db,
'autoReconnect': true
})
}));
app.use(flash());
app.disable('x-powered-by'); app.disable('x-powered-by');
app.use(helmet.xssFilter()); // adds passport initialization after session middleware phase is complete
app.use(helmet.noSniff());
app.use(helmet.frameguard());
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
var trusted = [
"'self'",
'blob:',
'104.236.218.15',
'*.freecodecamp.com',
'http://www.freecodecamp.com',
'https://www.freecodecamp.com',
'https://freecodecamp.com',
'https://freecodecamp.org',
'*.freecodecamp.org',
// NOTE(berks): add the following as the blob above was not covering www
'http://www.freecodecamp.org',
'ws://freecodecamp.com/',
'ws://www.freecodecamp.com/',
'*.gstatic.com',
'*.google-analytics.com',
'*.googleapis.com',
'*.google.com',
'*.gstatic.com',
'*.doubleclick.net',
'*.twitter.com',
'*.twitch.tv',
'*.twimg.com',
"'unsafe-eval'",
"'unsafe-inline'",
'*.bootstrapcdn.com',
'*.cloudflare.com',
'https://*.cloudflare.com',
'localhost:3001',
'ws://localhost:3001/',
'http://localhost:3001',
'localhost:3000',
'ws://localhost:3000/',
'http://localhost:3000',
'*.ionicframework.com',
'https://syndication.twitter.com',
'*.youtube.com',
'*.jsdelivr.net',
'https://*.jsdelivr.net',
'*.ytimg.com',
'*.bitly.com',
'http://cdn.inspectlet.com/',
'https://cdn.inspeclet.com/',
'wss://inspectletws.herokuapp.com/',
'http://hn.inspectlet.com/',
'*.googleapis.com',
'*.gstatic.com',
'https://hn.inspectlet.com/'
];
app.use(helmet.csp({
defaultSrc: trusted,
scriptSrc: [
'*.optimizely.com',
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js',
'http://beta.freecodecamp.com'
].concat(trusted),
'connect-src': [
'vimeo.com'
].concat(trusted),
styleSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
imgSrc: [
/* allow all input since we have user submitted images for public profile*/
'*'
].concat(trusted),
fontSrc: [
'*.googleapis.com',
'*.gstatic.com'
].concat(trusted),
mediaSrc: [
'*.amazonaws.com',
'*.twitter.com'
].concat(trusted),
frameSrc: [
'*.gitter.im',
'*.gitter.im https:',
'*.vimeo.com',
'*.twitter.com',
'*.ghbtns.com'
].concat(trusted),
// set to true if you only want to report errors
reportOnly: false,
// set to true if you want to set all headers
setAllHeaders: false,
// set to true if you want to force buggy CSP in Safari 5
safari5: false
}));
passportConfigurator.init(); passportConfigurator.init();
// add rx methods to express
app.use(rxMiddleware());
app.use(function(req, res, next) {
// Make user object available in templates.
res.locals.user = req.user;
next();
});
app.use(
loopback.static(path.join(__dirname, '../public'), {
maxAge: 86400000
})
);
boot(app, { boot(app, {
appRootDir: __dirname, appRootDir: __dirname,
dev: process.env.NODE_ENV dev: process.env.NODE_ENV
}); });
app.use(function(req, res, next) {
// Remember original destination before login.
var path = req.path.split('/')[1];
if (/auth|login|logout|signin|signup|fonts|favicon/i.test(path)) {
return next();
} else if (/\/stories\/\w+/i.test(req.path)) {
return next();
}
req.session.returnTo = req.path;
next();
});
passportConfigurator.setupModels({ passportConfigurator.setupModels({
userModel: app.models.user, userModel: app.models.user,
@ -257,58 +78,6 @@ Object.keys(passportProviders).map(function(strategy) {
); );
}); });
/**
* OAuth sign-in routes.
*/
/**
* 500 Error Handler.
*/
if (process.env.NODE_ENV === 'development') {
app.use(errorHandler({
log: true
}));
} else {
app.use(pmx.expressErrorHandler());
// error handling in production disabling eslint due to express parity rules
// for error handlers
app.use(function(err, req, res, next) { // eslint-disable-line
// respect err.status
if (err.status) {
res.statusCode = err.status;
}
// default status code to 500
if (res.statusCode < 400) {
res.statusCode = 500;
}
// parse res type
var accept = accepts(req);
var type = accept.type('html', 'json', 'text');
var message = 'opps! Something went wrong. Please try again later';
if (type === 'html') {
req.flash('errors', {
msg: message
});
return res.redirect('/');
// json
} else if (type === 'json') {
res.setHeader('Content-Type', 'application/json');
return res.send({
message: message
});
// plain text
} else {
res.setHeader('Content-Type', 'text/plain');
return res.send(message);
}
});
}
app.start = function() { app.start = function() {
app.listen(app.get('port'), function() { app.listen(app.get('port'), function() {
app.emit('started'); app.emit('started');

View File

@ -30,12 +30,3 @@ exports.observableQueryFromModel =
exports.observeMethod = function observeMethod(context, methodName) { exports.observeMethod = function observeMethod(context, methodName) {
return Rx.Observable.fromNodeCallback(context[methodName], context); return Rx.Observable.fromNodeCallback(context[methodName], context);
}; };
// add rx methods to express
exports.rxMiddleware = function rxMiddleware() {
return function rxMiddleware(req, res, next) {
// render to observable
res.render$ = Rx.Observable.fromNodeCallback(res.render, res);
next();
};
};