require('dotenv').load(); // handle uncaught exceptions. Forever will restart process on shutdown process.on('uncaughtException', function (err) { console.error( (new Date()).toUTCString() + ' uncaughtException:', err.message ); console.error(err.stack); /* eslint-disable no-process-exit */ process.exit(1); /* eslint-enable no-process-exit */ }); var express = require('express'), accepts = require('accepts'), cookieParser = require('cookie-parser'), compress = require('compression'), session = require('express-session'), logger = require('morgan'), errorHandler = require('errorhandler'), methodOverride = require('method-override'), bodyParser = require('body-parser'), helmet = require('helmet'), frameguard = require('frameguard'), csp = require('helmet-csp'), MongoStore = require('connect-mongo')(session), flash = require('express-flash'), path = require('path'), mongoose = require('mongoose'), passport = require('passport'), expressValidator = require('express-validator'), request = require('request'), forceDomain = require('forcedomain'), lessMiddleware = require('less-middleware'), /** * Controllers (route handlers). */ homeController = require('./controllers/home'), resourcesController = require('./controllers/resources'), userController = require('./controllers/user'), nonprofitController = require('./controllers/nonprofits'), fieldGuideController = require('./controllers/fieldGuide'), challengeMapController = require('./controllers/challengeMap'), challengeController = require('./controllers/challenge'), /** * Stories */ storyController = require('./controllers/story'), /** * API keys and Passport configuration. */ secrets = require('./config/secrets'), passportConf = require('./config/passport'); /** * Create Express server. */ var app = express(); /** * Connect to MongoDB. */ mongoose.connect(secrets.db); mongoose.connection.on('error', function () { console.error( 'MongoDB Connection Error. Please make sure that MongoDB is running.' ); }); /** * Express configuration. */ app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); if (process.env.NODE_ENV === 'production') { app.use(forceDomain({ hostname: 'www.freecodecamp.com' })); } app.use(compress()); app.use(lessMiddleware(__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()); app.use(session({ resave: true, saveUninitialized: true, secret: secrets.sessionSecret, store: new MongoStore({ url: secrets.db, 'autoReconnect': true }) })); app.use(passport.initialize()); app.use(passport.session()); 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:', '*.freecodecamp.com', 'http://www.freecodecamp.com', '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/', 'http://hn.inspectlet.com/', '*.simplyhired.com', '*.simply-partner.com' ]; app.use(helmet.csp({ defaultSrc: trusted, scriptSrc: [ '*.optimizely.com', '*.aspnetcdn.com', '*.d3js.org' ].concat(trusted), 'connect-src': [ ].concat(trusted), styleSrc: trusted, imgSrc: [ /* allow all input since we have user submitted images for public profile*/ '*' ].concat(trusted), fontSrc: ['*.googleapis.com'].concat(trusted), mediaSrc: [ '*.amazonaws.com', '*.twitter.com' ].concat(trusted), frameSrc: [ '*.gitter.im', '*.gitter.im https:', '*.vimeo.com', '*.twitter.com', '*.ghbtns.com' ].concat(trusted), reportOnly: false, // set to true if you only want to report errors setAllHeaders: false, // set to true if you want to set all headers safari5: false // set to true if you want to force buggy CSP in Safari 5 })); app.use(function (req, res, next) { // Make user object available in templates. res.locals.user = req.user; next(); }); app.use(express.static(__dirname + '/public', {maxAge: 86400000 })); 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\/comments\/\w+/i.test(req.path)) { return next(); } req.session.returnTo = req.path; next(); }); /** * Main routes. */ app.get('/', homeController.index); app.get('/nonprofit-project-instructions', function(req, res) { res.redirect(301, '/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work'); }); app.post('/get-help', resourcesController.getHelp); app.get('/chat', resourcesController.chat); app.get('/twitch', resourcesController.twitch); // Agile Project Manager Onboarding app.get('/pmi-acp-agile-project-managers', resourcesController.agileProjectManagers); app.get('/agile', function(req, res) { res.redirect(301, '/pmi-acp-agile-project-managers'); }); app.get('/pmi-acp-agile-project-managers-form', resourcesController.agileProjectManagersForm); // Nonprofit Onboarding app.get('/nonprofits', resourcesController.nonprofits); app.get('/nonprofits/getNonprofitList', nonprofitController.showAllNonprofits); app.get('/nonprofits-form', resourcesController.nonprofitsForm); app.get('/map', challengeMapController.challengeMap); app.get('/live-pair-programming', function(req, res) { res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv'); }); app.get('/install-screenhero', function(req, res) { res.redirect(301, '/field-guide/install-screenhero'); }); app.get('/guide-to-our-nonprofit-projects', function(req, res) { res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects'); }); app.get('/chromebook', function(req, res) { res.redirect(301, '/field-guide/chromebook'); }); app.get('/deploy-a-website', function(req, res) { res.redirect(301, '/field-guide/deploy-a-website'); }); app.get('/gmail-shortcuts', function(req, res) { res.redirect(301, '/field-guide/gmail-shortcuts'); }); app.get('/nodeschool-challenges', function(req, res) { res.redirect(301, '/field-guide/nodeschool-challenges'); }); app.get('/learn-to-code', challengeMapController.challengeMap); app.get('/about', function(req, res) { res.redirect(301, '/map'); }); app.get('/signin', userController.getSignin); app.get('/login', function(req, res) { res.redirect(301, '/signin'); }); app.post('/signin', userController.postSignin); app.get('/signout', userController.signout); app.get('/logout', function(req, res) { res.redirect(301, '/signout'); }); app.get('/forgot', userController.getForgot); app.post('/forgot', userController.postForgot); app.get('/reset/:token', userController.getReset); app.post('/reset/:token', userController.postReset); app.get('/email-signup', userController.getEmailSignup); app.get('/email-signin', userController.getEmailSignin); app.post('/email-signup', userController.postEmailSignup); app.post('/email-signin', userController.postSignin); /** * Nonprofit Project routes. */ app.get('/nonprofits/directory', nonprofitController.nonprofitsDirectory); app.get( '/nonprofits/:nonprofitName', nonprofitController.returnIndividualNonprofit ); app.get( '/jobs', resourcesController.jobs ); app.get( '/jobs-form', resourcesController.jobsForm ); app.get('/privacy', function(req, res) { res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?'); }); app.get('/submit-cat-photo', resourcesController.catPhotoSubmit); app.get('/api/slack', function(req, res) { if (req.user) { if (req.user.email) { var invite = { 'email': req.user.email, 'token': process.env.SLACK_KEY, 'set_active': true }; var headers = { 'User-Agent': 'Node Browser/0.0.1', 'Content-Type': 'application/x-www-form-urlencoded' }; var options = { url: 'https://freecode.slack.com/api/users.admin.invite', method: 'POST', headers: headers, form: invite }; request(options, function (error, response, body) { if (!error && response.statusCode === 200) { req.flash('success', { msg: "We've successfully requested an invite for you. Please check your email and follow the instructions from Slack." }); req.user.sentSlackInvite = true; req.user.save(function(err, user) { if (err) { next(err); } return res.redirect('back'); }); } else { req.flash('errors', { msg: "The invitation email did not go through for some reason. Please try again or