From f01a66c5c7194c0b8dd36f8cd9cdee63412156ca Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 01:25:34 -0700 Subject: [PATCH 01/12] use (LMP)loopback middleware phases bump loopback-component-passport which uses LMP move custom middlewares to middlewares directory --- package.json | 2 +- server/middleware.json | 43 ++++- server/middlewares/add-return-to.js | 13 ++ server/middlewares/constant-headers.js | 9 + server/middlewares/cookie-parser.js | 4 + server/middlewares/csp.js | 98 +++++++++++ server/middlewares/error-handlers.js | 43 +++++ server/middlewares/express-rx.js | 10 ++ server/middlewares/global-locals.js | 7 + server/middlewares/keymetrics.js | 8 + server/middlewares/sessions.js | 17 ++ server/middlewares/validator.js | 9 + server/server.js | 235 +------------------------ server/utils/rx.js | 9 - 14 files changed, 259 insertions(+), 248 deletions(-) create mode 100644 server/middlewares/add-return-to.js create mode 100644 server/middlewares/constant-headers.js create mode 100644 server/middlewares/cookie-parser.js create mode 100644 server/middlewares/csp.js create mode 100644 server/middlewares/error-handlers.js create mode 100644 server/middlewares/express-rx.js create mode 100644 server/middlewares/global-locals.js create mode 100644 server/middlewares/keymetrics.js create mode 100644 server/middlewares/sessions.js create mode 100644 server/middlewares/validator.js diff --git a/package.json b/package.json index 4e96eda08e..1b20841b84 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "lodash": "^3.9.3", "loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password", "loopback-boot": "^2.8.0", - "loopback-component-passport": "1.4.0", + "loopback-component-passport": "^1.5.0", "loopback-connector-mongodb": "^1.10.0", "lusca": "~1.0.2", "method-override": "~2.3.0", diff --git a/server/middleware.json b/server/middleware.json index 664cacda58..10d78033a0 100644 --- a/server/middleware.json +++ b/server/middleware.json @@ -1,27 +1,60 @@ { "initial:before": { - "loopback#favicon": {} + "loopback#favicon": { + "params": "$!../public/favicon.ico" + } }, "initial": { - "compression": {} + "compression": {}, + "morgan": { + "params": "dev" + } }, "session": { + "./middlewares/sessions.js": {} + }, + "auth:before": { + "./middlewares/add-return-to": {} }, "auth": { }, "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": { }, "files": { "loopback#static": { - "params": "$!../public" + "params": [ + "$!../public", + { + "maxAge": "86400000" + } + ] } }, "final": { - "loopback#urlNotFound": {} }, "final:after": { - "errorhandler": {} + "./middlewares/keymetrics": {}, + "./middlewares/error-handlers": {} } } diff --git a/server/middlewares/add-return-to.js b/server/middlewares/add-return-to.js new file mode 100644 index 0000000000..9e9522b1ad --- /dev/null +++ b/server/middlewares/add-return-to.js @@ -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(); + }; +} diff --git a/server/middlewares/constant-headers.js b/server/middlewares/constant-headers.js new file mode 100644 index 0000000000..28a1ad32bd --- /dev/null +++ b/server/middlewares/constant-headers.js @@ -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(); + }; +} diff --git a/server/middlewares/cookie-parser.js b/server/middlewares/cookie-parser.js new file mode 100644 index 0000000000..a991b5dc7e --- /dev/null +++ b/server/middlewares/cookie-parser.js @@ -0,0 +1,4 @@ +import cookieParser from 'cookie-parser'; +import secrets from '../../config/secrets'; + +export default cookieParser.bind(cookieParser, secrets.cookieSecret); diff --git a/server/middlewares/csp.js b/server/middlewares/csp.js new file mode 100644 index 0000000000..8354ce1df9 --- /dev/null +++ b/server/middlewares/csp.js @@ -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 + }); +} diff --git a/server/middlewares/error-handlers.js b/server/middlewares/error-handlers.js new file mode 100644 index 0000000000..c8eefaef08 --- /dev/null +++ b/server/middlewares/error-handlers.js @@ -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); + } + }; +} diff --git a/server/middlewares/express-rx.js b/server/middlewares/express-rx.js new file mode 100644 index 0000000000..d687e38363 --- /dev/null +++ b/server/middlewares/express-rx.js @@ -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(); + }; +} diff --git a/server/middlewares/global-locals.js b/server/middlewares/global-locals.js new file mode 100644 index 0000000000..32a6148562 --- /dev/null +++ b/server/middlewares/global-locals.js @@ -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(); + }; +} diff --git a/server/middlewares/keymetrics.js b/server/middlewares/keymetrics.js new file mode 100644 index 0000000000..0902d6caab --- /dev/null +++ b/server/middlewares/keymetrics.js @@ -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(); +} diff --git a/server/middlewares/sessions.js b/server/middlewares/sessions.js new file mode 100644 index 0000000000..fc0e4ccc1b --- /dev/null +++ b/server/middlewares/sessions.js @@ -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 + }) + }); +} diff --git a/server/middlewares/validator.js b/server/middlewares/validator.js new file mode 100644 index 0000000000..b40765bc89 --- /dev/null +++ b/server/middlewares/validator.js @@ -0,0 +1,9 @@ +import validator from 'express-validator'; + +export default validator.bind(validator, { + customValidators: { + matchRegex: function matchRegex(param, regex) { + return regex.test(param); + } + } +}); diff --git a/server/server.js b/server/server.js index fe0e924a55..3c047934e5 100755 --- a/server/server.js +++ b/server/server.js @@ -5,28 +5,9 @@ pmx.init(); var assign = require('lodash').assign, loopback = require('loopback'), boot = require('loopback-boot'), - accepts = require('accepts'), - cookieParser = require('cookie-parser'), - compress = require('compression'), - session = require('express-session'), 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'), - expressValidator = require('express-validator'), - lessMiddleware = require('less-middleware'), - - passportProviders = require('./passport-providers'), - rxMiddleware = require('./utils/rx').rxMiddleware, - /** - * API keys and Passport configuration. - */ - secrets = require('./../config/secrets'); + passportProviders = require('./passport-providers'); var 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('views', path.join(__dirname, 'views')); 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.use(helmet.xssFilter()); -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 -})); - +// adds passport initialization after session middleware phase is complete 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, { appRootDir: __dirname, 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({ 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.listen(app.get('port'), function() { app.emit('started'); diff --git a/server/utils/rx.js b/server/utils/rx.js index ed7e0ebf73..7f0bac0704 100644 --- a/server/utils/rx.js +++ b/server/utils/rx.js @@ -30,12 +30,3 @@ exports.observableQueryFromModel = exports.observeMethod = function observeMethod(context, methodName) { 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(); - }; -}; From efa73c93ee29eba10c8ce62b4051bca64d403f61 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 01:28:21 -0700 Subject: [PATCH 02/12] remove twitter auth --- server/views/account/account.jade | 2 +- server/views/account/signin.jade | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/views/account/account.jade b/server/views/account/account.jade index 0dc4e5cb2c..436651c1e0 100644 --- a/server/views/account/account.jade +++ b/server/views/account/account.jade @@ -268,7 +268,7 @@ block content | Link GitHub with my account if (!user.twitter) .col-xs-12 - a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/auth/twitter') + a.btn.btn-lg.btn-block.btn-twitter.btn-link-social.disabled(href='#') i.fa.fa-twitter | Link Twitter with my account if (!user.google) diff --git a/server/views/account/signin.jade b/server/views/account/signin.jade index 0e2e22a9ab..94682de221 100644 --- a/server/views/account/signin.jade +++ b/server/views/account/signin.jade @@ -2,10 +2,10 @@ extends ../layout block content .jumbotron.text-center h2 Sign in with one of these options: - a.btn.btn-lg.btn-block.btn-twitter.btn-social(href='/auth/github') + a.btn.btn-lg.btn-block.btn-github.btn-social(href='/auth/github') i.fa.fa-github | Sign in with Github - a.btn.btn-lg.btn-block.btn-twitter.btn-social(href='/auth/twitter') + a.btn.btn-lg.btn-block.btn-twitter.btn-social.disabled(href='#') i.fa.fa-twitter | Sign in with Twitter a.btn.btn-lg.btn-block.btn-facebook.btn-social.disabled(href='#') From 43d74c2f48619739409b68c22a1a664d5af42136 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 01:34:06 -0700 Subject: [PATCH 03/12] add beta warning banner --- server/views/home.jade | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/views/home.jade b/server/views/home.jade index 7b25a30777..4e7a8f9c73 100644 --- a/server/views/home.jade +++ b/server/views/home.jade @@ -1,5 +1,8 @@ extends layout block content + .bg-danger + a(href='https://github.com/FreeCodeCamp/freecodecamp/wiki/beta before continuing') + h3 Warning! You are on beta! Please read this link before continuing! https://github.com/freecodecamp/freecodecamp/wiki/beta .jumbotron .text-center h1.hug-top Code with Us From a1a64435d6031c8b65ad80246c3ee649d5e03bc0 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 01:47:27 -0700 Subject: [PATCH 04/12] readding twitter auth twitter auth is the only oauth that works in local dev --- server/views/account/signin.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/views/account/signin.jade b/server/views/account/signin.jade index 94682de221..67047098a5 100644 --- a/server/views/account/signin.jade +++ b/server/views/account/signin.jade @@ -5,7 +5,7 @@ block content a.btn.btn-lg.btn-block.btn-github.btn-social(href='/auth/github') i.fa.fa-github | Sign in with Github - a.btn.btn-lg.btn-block.btn-twitter.btn-social.disabled(href='#') + a.btn.btn-lg.btn-block.btn-twitter.btn-social(href='/auth/twitter') i.fa.fa-twitter | Sign in with Twitter a.btn.btn-lg.btn-block.btn-facebook.btn-social.disabled(href='#') From fc0ccf3aeafcb66b5747858ecd5020db33c904d6 Mon Sep 17 00:00:00 2001 From: benmcmahon100 Date: Tue, 4 Aug 2015 13:28:41 +0100 Subject: [PATCH 05/12] Fixed typos in magical maths waypoints and restored server.js to a previous working state --- seed/challenges/basic-javascript.json | 8 +- server/server.js | 169 +++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 10 deletions(-) diff --git a/seed/challenges/basic-javascript.json b/seed/challenges/basic-javascript.json index 59c10f984c..ab30786d54 100644 --- a/seed/challenges/basic-javascript.json +++ b/seed/challenges/basic-javascript.json @@ -277,7 +277,7 @@ "difficulty": "9.98141", "description": [ "", - "In JavaScript whole numbers (called integers) can be really easily to preform mathematical functions", + "In JavaScript whole numbers (called integers) can be easily used to preform mathematical functions", "Let's try a few of the most commonly used ones now", "We use + for addition", "Replace the 0 with correct number to achieve the result in the comment." @@ -301,7 +301,7 @@ "difficulty": "9.98142", "description": [ "", - "In JavaScript whole numbers (called integers) can be really easily to preform mathematical functions", + "In JavaScript whole numbers (called integers) can be easily used to preform mathematical functions", "Let's try a few of the most commonly used ones now", "We use - for subtraction", "Replace the 0 with correct number to achieve the result in the comment." @@ -325,7 +325,7 @@ "difficulty": "9.98143", "description": [ "", - "In JavaScript whole numbers (called integers) can be really easily to preform mathematical functions", + "In JavaScript whole numbers (called integers) can be easily used to preform mathematical functions", "Let's try a few of the most commonly used ones now", "We use * for multiplication", "Replace the 0 with correct number to achieve the result in the comment." @@ -349,7 +349,7 @@ "difficulty": "9.9814", "description": [ "", - "In JavaScript whole numbers (called integers) can be really easily to preform mathematical functions", + "In JavaScript whole numbers (called integers) can be easily used to preform mathematical functions", "Let's try a few of the most commonly used ones now", "We use / for division", "Replace the 0 with correct number to achieve the result in the comment." diff --git a/server/server.js b/server/server.js index 3c047934e5..f8f6955e97 100755 --- a/server/server.js +++ b/server/server.js @@ -3,11 +3,30 @@ var pmx = require('pmx'); pmx.init(); var assign = require('lodash').assign, - loopback = require('loopback'), - boot = require('loopback-boot'), - expressState = require('express-state'), - path = require('path'), - passportProviders = require('./passport-providers'); + loopback = require('loopback'), + boot = require('loopback-boot'), + accepts = require('accepts'), + cookieParser = require('cookie-parser'), + compress = require('compression'), + session = require('express-session'), + 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'), + expressValidator = require('express-validator'), + lessMiddleware = require('less-middleware'), + + passportProviders = require('./passport-providers'), + rxMiddleware = require('./utils/rx').rxMiddleware, + /** + * API keys and Passport configuration. + */ + secrets = require('./../config/secrets'); var generateKey = require('loopback-component-passport/lib/models/utils').generateKey; @@ -26,9 +45,147 @@ var passportConfigurator = new PassportConfigurator(app); app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); -app.disable('x-powered-by'); +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'); // adds passport initialization after session middleware phase is complete + +app.use(helmet.xssFilter()); +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', + '127.0.0.1', + '127.0.0.1:3000', + 'ws://127.0.0.1:3000/', + 'http://127.0.0.1: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/', + 'http://*.github.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(); boot(app, { From dbf8443430127fa30904bc670edec19b78189ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Floty=C5=84ski?= Date: Tue, 4 Aug 2015 15:00:22 +0200 Subject: [PATCH 06/12] Small Fix of Bootstrap waypoint challenge - Issue 1502 Changed second check for second class - 'btn-default' --- seed/challenges/bootstrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index c0b4014f93..8a796f709a 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -1743,7 +1743,7 @@ ], "tests": [ "assert($('.btn').length > 5, 'Apply the \"btn\" class to each of your button elements.')", - "assert($('.btn').length > 5, 'Apply the \"btn-default\" class to each of your button elements.')" + "assert($('.btn-default').length > 5, 'Apply the \"btn-default\" class to each of your button elements.')" ], "challengeSeed": [ "
", From c8488c84193f0f1592fc242cdfe7b1c774242184 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 06:28:10 -0700 Subject: [PATCH 07/12] reremove extra middlewares from server file --- server/server.js | 169 ++--------------------------------------------- 1 file changed, 6 insertions(+), 163 deletions(-) diff --git a/server/server.js b/server/server.js index f8f6955e97..3c047934e5 100755 --- a/server/server.js +++ b/server/server.js @@ -3,30 +3,11 @@ var pmx = require('pmx'); pmx.init(); var assign = require('lodash').assign, - loopback = require('loopback'), - boot = require('loopback-boot'), - accepts = require('accepts'), - cookieParser = require('cookie-parser'), - compress = require('compression'), - session = require('express-session'), - 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'), - expressValidator = require('express-validator'), - lessMiddleware = require('less-middleware'), - - passportProviders = require('./passport-providers'), - rxMiddleware = require('./utils/rx').rxMiddleware, - /** - * API keys and Passport configuration. - */ - secrets = require('./../config/secrets'); + loopback = require('loopback'), + boot = require('loopback-boot'), + expressState = require('express-state'), + path = require('path'), + passportProviders = require('./passport-providers'); var generateKey = require('loopback-component-passport/lib/models/utils').generateKey; @@ -45,147 +26,9 @@ var passportConfigurator = new PassportConfigurator(app); app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); 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'); + // adds passport initialization after session middleware phase is complete - -app.use(helmet.xssFilter()); -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', - '127.0.0.1', - '127.0.0.1:3000', - 'ws://127.0.0.1:3000/', - 'http://127.0.0.1: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/', - 'http://*.github.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(); boot(app, { From c05a936c8710e91f726f9504899d254046a3cba4 Mon Sep 17 00:00:00 2001 From: Shouvik Roy Date: Tue, 4 Aug 2015 20:47:25 +0530 Subject: [PATCH 08/12] Update main.less to fix #1510 Update main.less to fix #1510. There was an alignment issue on Safari because of missing vendor prefix ```-webkit``` on ```transform``` on the ```.points-on-top``` class. --- public/css/main.less | 1 + 1 file changed, 1 insertion(+) diff --git a/public/css/main.less b/public/css/main.less index 68ffd44dc0..21e225994f 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -577,6 +577,7 @@ thead { margin: 0 auto; position: relative; top: 50%; + -webkit-transform: translateY(-50%); transform: translateY(-50%); } From ad46b72bd0d6ea2fdf22cdb5ff0c48eeabe15fb1 Mon Sep 17 00:00:00 2001 From: Nic Galluzzo Date: Tue, 4 Aug 2015 08:54:21 -0700 Subject: [PATCH 09/12] Update bootstrap.json --- seed/challenges/bootstrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index c0b4014f93..32b069f30d 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -12,7 +12,7 @@ "Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name Responsive Design.", "With responsive design, there is no need to design a mobile version of your website. It will look good on devices with screens of any width.", "You can add Bootstrap to any app just by including it with <link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/> at the top of your HTML. But we've gone ahead and automatically added it to your Cat Photo App for you.", - "To get started, we should nest all of our HTML in a div element with the class \"fluid-container\"." + "To get started, we should nest all of our HTML in a div element with the class \"container-fluid\"." ], "tests": [ "assert($('div').hasClass('container-fluid'), 'Your div element should have the class \"row\"')", From 70c5bcea209bcf9037e29526d5005efe60557bc1 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 10:52:41 -0700 Subject: [PATCH 10/12] fix error message string --- common/models/user.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/models/user.js b/common/models/user.js index e4f5360444..3c7e54cbb3 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -161,9 +161,9 @@ module.exports = function(User) { if (!username) { // Zalgo!! return nextTick(() => { - cb( - new TypeError('FCC: username should be a string but got %s', username) - ); + cb(new TypeError( + `username should be a string but got ${ username }` + )); }); } User.findOne({ where: { username } }, (err, user) => { @@ -171,7 +171,7 @@ module.exports = function(User) { return cb(err); } if (!user || user.username !== username) { - return cb(new Error('FCC: no user found for %s', username)); + return cb(new Error(`no user found for ${ username }`)); } const aboutUser = getAboutProfile(user); return cb(null, aboutUser); From ecacbc623391a9b7227d78eeb9b28701bfb30d42 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 4 Aug 2015 14:03:19 -0500 Subject: [PATCH 11/12] Update bootstrap.json fixed typo (misspelled 'container') --- seed/challenges/bootstrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index c0b4014f93..a54c808017 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -1566,7 +1566,7 @@ "difficulty": 2.18, "description": [ "Now let's make sure all the content on your page is mobile-responsive.", - "Let's nest your h3 element within a div element with the class \"containter-fluid\"." + "Let's nest your h3 element within a div element with the class \"container-fluid\"." ], "tests": [ "assert($('div').hasClass('container-fluid'), 'Your div element should have the class \"container-fluid\"')", From 2dc9934da2b5f5d66bf63d5e012cd9a58bc25e92 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 4 Aug 2015 13:39:59 -0700 Subject: [PATCH 12/12] add user.isGithubCool check if user signs up with github, they become GithubCool. --- common/models/user.json | 43 ++++------------------------------------- server/server.js | 3 +++ 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/common/models/user.json b/common/models/user.json index aeb02b6397..940777a5a4 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -19,28 +19,14 @@ "password": { "type": "string" }, - "facebook": { - "type": "string" - }, - "twitter": { - "type": "string" - }, - "google": { - "type": "string" - }, - "github": { - "type": "string" - }, - "linkedin": { - "type": "string" - }, - "tokens": { - "type": "array" - }, "progressTimestamps": { "type": "array", "default": [] }, + "isGithubCool": { + "type": "boolean", + "default": false + }, "username": { "type": "string", "lowercase": true, @@ -123,12 +109,6 @@ "type": "string", "default": "" }, - "resetPasswordToken": { - "type": "string" - }, - "resetPasswordExpires": { - "type": "string" - }, "completedBonfires": { "type": [ { @@ -170,21 +150,10 @@ "type": "number", "default": 0 }, - "needsSomeDataModeled": { - "type": "boolean", - "default": false - }, - "needsMigration": { - "type": "boolean", - "default": true - }, "sendMonthlyEmail": { "type": "boolean", "default": true }, - "challengesHash": { - "type": {} - }, "currentChallenge": { "type": {} }, @@ -205,10 +174,6 @@ } ], "default": [] - }, - "uncompletedChallenges": { - "type": "array", - "default": [] } }, "validations": [], diff --git a/server/server.js b/server/server.js index 3c047934e5..93b0a1a322 100755 --- a/server/server.js +++ b/server/server.js @@ -65,6 +65,9 @@ var passportOptions = { if (email) { userObj.email = email; } + if (provider === 'github-login') { + userObj.isGithubCool = true; + } return userObj; } };