Merge branch 'staging' into fix/normalize-flash-type

This commit is contained in:
Cassidy Pignatello
2018-01-10 14:19:00 -05:00
committed by GitHub
117 changed files with 42131 additions and 2328 deletions

View File

@@ -0,0 +1,15 @@
import { Observable } from 'rx';
export default function extendEmail(app) {
const { AccessToken, Email } = app.models;
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
AccessToken.findOne$ = Observable.fromNodeCallback(
AccessToken.findOne.bind(AccessToken)
);
AccessToken.prototype.validate$ = Observable.fromNodeCallback(
AccessToken.prototype.validate
);
AccessToken.prototype.destroy$ = Observable.fromNodeCallback(
AccessToken.prototype.destroy
);
}

View File

@@ -1,6 +0,0 @@
import { Observable } from 'rx';
export default function extendEmail(app) {
const { Email } = app.models;
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
}

View File

@@ -1,4 +1,229 @@
import _ from 'lodash';
import { Observable } from 'rx';
import dedent from 'dedent';
// import debugFactory from 'debug';
import { isEmail } from 'validator';
import { check, validationResult } from 'express-validator/check';
import { ifUserRedirectTo } from '../utils/middleware';
import {
wrapHandledError,
createValidatorErrorFormatter
} from '../utils/create-handled-error.js';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
// const debug = debugFactory('fcc:boot:auth');
if (isSignUpDisabled) {
console.log('fcc:boot:auth - Sign up is disabled');
}
module.exports = function enableAuthentication(app) {
// enable authentication
// enable loopback access control authentication. see:
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
app.enableAuth();
const ifUserRedirect = ifUserRedirectTo();
const router = app.loopback.Router();
const api = app.loopback.Router();
const { AuthToken, User } = app.models;
router.get('/login', (req, res) => res.redirect(301, '/signin'));
router.get('/logout', (req, res) => res.redirect(301, '/signout'));
function getEmailSignin(req, res) {
if (isSignUpDisabled) {
return res.render('account/beta', {
title: 'New sign ups are disabled'
});
}
return res.render('account/email-signin', {
title: 'Sign in to freeCodeCamp using your Email Address'
});
}
router.get('/signup', ifUserRedirect, getEmailSignin);
router.get('/signin', ifUserRedirect, getEmailSignin);
router.get('/email-signin', ifUserRedirect, getEmailSignin);
router.get('/signout', (req, res) => {
req.logout();
res.redirect('/');
});
router.get(
'/deprecated-signin',
ifUserRedirect,
(req, res) => res.render('account/deprecated-signin', {
title: 'Sign in to freeCodeCamp using a Deprecated Login'
})
);
const defaultErrorMsg = dedent`
Oops, something is not right,
please request a fresh link to sign in / sign up.
`;
const passwordlessGetValidators = [
check('email')
.isBase64()
.withMessage('Email should be a base64 encoded string.'),
check('token')
.exists()
.withMessage('Token should exist.')
// based on strongloop/loopback/common/models/access-token.js#L15
.isLength({ min: 64, max: 64 })
.withMessage('Token is not the right length.')
];
function getPasswordlessAuth(req, res, next) {
const {
query: {
email: encodedEmail,
token: authTokenId
} = {}
} = req;
const validation = validationResult(req)
.formatWith(createValidatorErrorFormatter('errors', '/email-signup'));
if (!validation.isEmpty()) {
const errors = validation.array();
return next(errors.pop());
}
const email = User.decodeEmail(encodedEmail);
if (!isEmail(email)) {
return next(wrapHandledError(
new TypeError('decoded email is invalid'),
{
type: 'info',
message: 'The email encoded in the link is incorrectly formatted',
redirectTo: '/email-sign'
}
));
}
// first find
return AuthToken.findOne$({ where: { id: authTokenId } })
.flatMap(authToken => {
if (!authToken) {
throw wrapHandledError(
new Error(`no token found for id: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/email-signin'
}
);
}
// find user then validate and destroy email validation token
// finally retun user instance
return User.findOne$({ where: { id: authToken.userId } })
.flatMap(user => {
if (!user) {
throw wrapHandledError(
new Error(`no user found for token: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/email-signin'
}
);
}
if (user.email !== email) {
throw wrapHandledError(
new Error('user email does not match'),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/email-signin'
}
);
}
return authToken.validate()
.map(isValid => {
if (!isValid) {
throw wrapHandledError(
new Error('token is invalid'),
{
type: 'info',
message: `
Looks like the link you clicked has expired,
please request a fresh link, to sign in.
`,
redirectTo: '/email-signin'
}
);
}
return authToken.destroy();
})
.map(() => user);
});
})
// at this point token has been validated and destroyed
// update user and log them in
.map(user => user.loginByRequest(req, res))
.do(() => {
let redirectTo = '/';
if (
req.session &&
req.session.returnTo
) {
redirectTo = req.session.returnTo;
}
req.flash('success', { msg:
'Success! You have signed in to your account. Happy Coding!'
});
return res.redirect(redirectTo);
})
.subscribe(
() => {},
next
);
}
router.get(
'/passwordless-auth',
ifUserRedirect,
passwordlessGetValidators,
getPasswordlessAuth
);
const passwordlessPostValidators = [
check('email')
.isEmail()
.withMessage('Email is not a valid email address.')
];
function postPasswordlessAuth(req, res, next) {
const { body: { email } = {} } = req;
const validation = validationResult(req)
.formatWith(createValidatorErrorFormatter('errors', '/email-signup'));
if (!validation.isEmpty()) {
const errors = validation.array();
return next(errors.pop());
}
return User.findOne$({ where: { email } })
.flatMap(_user => Observable.if(
// if no user found create new user and save to db
_.constant(_user),
Observable.of(_user),
User.create$({ email })
)
.flatMap(user => user.requestAuthEmail(!_user))
)
.do(msg => res.status(200).send({ message: msg }))
.subscribe(_.noop, next);
}
api.post(
'/passwordless-auth',
ifUserRedirect,
passwordlessPostValidators,
postPasswordlessAuth
);
app.use('/:lang', router);
app.use(api);
};

View File

@@ -14,9 +14,14 @@ import {
import { observeQuery } from '../utils/rx';
import {
respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
frontEndChallengeId,
dataVisChallengeId,
backEndChallengeId
dataVisId,
apisMicroservicesId,
backEndChallengeId,
infosecQaId
} from '../utils/constantStrings.json';
import {
@@ -60,9 +65,12 @@ function getIdsForCert$(id, Challenge) {
// {
// email: String,
// username: String,
// isFrontEndCert: Boolean,
// isBackEndCert: Boolean,
// isDataVisCert: Boolean
// isRespWebDesignCert: Boolean,
// isFrontEndLibsCert: Boolean,
// isJsAlgoDataStructCert: Boolean,
// isDataVisCert: Boolean,
// isApisMicroservicesCert: Boolean,
// isInfosecQaCert: Boolean
// },
// send$: Observable
// ) => Observable
@@ -71,17 +79,23 @@ function sendCertifiedEmail(
email,
name,
username,
isFrontEndCert,
isBackEndCert,
isDataVisCert
isRespWebDesignCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
},
send$
) {
if (
!isEmail(email) ||
!isFrontEndCert ||
!isBackEndCert ||
!isDataVisCert
!isRespWebDesignCert ||
!isFrontEndLibsCert ||
!isJsAlgoDataStructCert ||
!isDataVisCert ||
!isApisMicroservicesCert ||
!isInfosecQaCert
) {
return Observable.just(false);
}
@@ -107,8 +121,16 @@ export default function certificate(app) {
const certTypeIds = {
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge)
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
[certTypes.apisMicroservices]: getIdsForCert$(
apisMicroservicesId,
Challenge
),
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge)
};
router.post(
@@ -123,12 +145,42 @@ export default function certificate(app) {
verifyCert.bind(null, certTypes.backEnd)
);
router.post(
'/certificate/verify/responsive-web-design',
ifNoUser401,
verifyCert.bind(null, certTypes.respWebDesign)
);
router.post(
'/certificate/verify/front-end-libraries',
ifNoUser401,
verifyCert.bind(null, certTypes.frontEndLibs)
);
router.post(
'/certificate/verify/javascript-algorithms-data-structures',
ifNoUser401,
verifyCert.bind(null, certTypes.jsAlgoDataStruct)
);
router.post(
'/certificate/verify/data-visualization',
ifNoUser401,
verifyCert.bind(null, certTypes.dataVis)
);
router.post(
'/certificate/verify/apis-microservices',
ifNoUser401,
verifyCert.bind(null, certTypes.apisMicroservices)
);
router.post(
'/certificate/verify/information-security-quality-assurance',
ifNoUser401,
verifyCert.bind(null, certTypes.infosecQa)
);
router.post(
'/certificate/honest',
sendMessageToNonUser,

View File

@@ -132,7 +132,7 @@ export default function commit(app) {
const {
nonprofit: nonprofitName = 'girl develop it',
amount = '5',
goal = commitGoals.frontEndCert
goal = commitGoals.respWebDesignCert
} = req.query;
const nonprofit = findNonprofit(nonprofitName);

View File

@@ -1,15 +1,19 @@
const createDebugger = require('debug');
const log = createDebugger('fcc:boot:explorer');
module.exports = function mountLoopBackExplorer(app) {
if (process.env.NODE_ENV === 'production') {
return;
}
var explorer;
let explorer;
try {
explorer = require('loopback-component-explorer');
} catch (err) {
// Print the message only when the app was started via `app.listen()`.
// Do not print any message when the project is used as a component.
app.once('started', function() {
console.log(
log(
'Run `npm install loopback-component-explorer` to enable ' +
'the LoopBack explorer'
);
@@ -17,13 +21,13 @@ module.exports = function mountLoopBackExplorer(app) {
return;
}
var restApiRoot = app.get('restApiRoot');
var mountPath = '/explorer';
const restApiRoot = app.get('restApiRoot');
const mountPath = '/explorer';
explorer(app, { basePath: restApiRoot, mountPath });
app.once('started', function() {
var baseUrl = app.get('url').replace(/\/$/, '');
const baseUrl = app.get('url').replace(/\/$/, '');
console.log('Browse your REST API at %s%s', baseUrl, mountPath);
log('Browse your REST API at %s%s', baseUrl, mountPath);
});
};

View File

@@ -1,35 +0,0 @@
var path = require('path');
var loopback = require('loopback');
var express = require('express');
var port = 1337;
// this will listen to traffic on port 1337
// The purpose is to redirect any user who is direct to https
// instead of http by mistake. Our nginx proxy server will listen
// for https traffic and serve from this port on this server.
// the view being send will have a short timeout and a redirect
module.exports = function(loopbackApp) {
var app = express();
app.set('view engine', 'jade');
// views in ../views'
app.set('views', path.join(__dirname, '..'));
// server static files
app.use(loopback.static(path.join(
__dirname,
'../',
'../public'
)));
// all traffic will be redirected on page load;
app.use(function(req, res) {
return res.render('views/redirect-https');
});
loopbackApp.once('started', function() {
app.listen(port, function() {
console.log('https redirect listening on port %s', port);
});
});
};

View File

@@ -6,8 +6,13 @@ import emoji from 'node-emoji';
import {
frontEndChallengeId,
dataVisChallengeId,
backEndChallengeId
backEndChallengeId,
respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
dataVisId,
apisMicroservicesId,
infosecQaId
} from '../utils/constantStrings.json';
import certTypes from '../utils/certTypes.json';
import {
@@ -24,27 +29,44 @@ import {
import supportedLanguages from '../../common/utils/supported-languages';
import { getChallengeInfo, cachedMap } from '../utils/map';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map');
const certIds = {
[certTypes.frontEnd]: frontEndChallengeId,
[certTypes.dataVis]: dataVisChallengeId,
[certTypes.backEnd]: backEndChallengeId
[certTypes.backEnd]: backEndChallengeId,
[certTypes.respWebDesign]: respWebDesignId,
[certTypes.frontEndLibs]: frontEndLibsId,
[certTypes.jsAlgoDataStruct]: jsAlgoDataStructId,
[certTypes.dataVis]: dataVisId,
[certTypes.apisMicroservices]: apisMicroservicesId,
[certTypes.infosecQa]: infosecQaId
};
const certViews = {
[certTypes.frontEnd]: 'certificate/front-end.jade',
[certTypes.dataVis]: 'certificate/data-vis.jade',
[certTypes.backEnd]: 'certificate/back-end.jade',
[certTypes.fullStack]: 'certificate/full-stack.jade'
[certTypes.fullStack]: 'certificate/full-stack.jade',
[certTypes.respWebDesign]: 'certificate/responsive-web-design.jade',
[certTypes.frontEndLibs]: 'certificate/front-end-libraries.jade',
[certTypes.jsAlgoDataStruct]:
'certificate/javascript-algorithms-and-data-structures.jade',
[certTypes.dataVis]: 'certificate/data-visualization.jade',
[certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
[certTypes.infosecQa]:
'certificate/information-security-and-quality-assurance.jade'
};
const certText = {
[certTypes.frontEnd]: 'Front End certified',
[certTypes.dataVis]: 'Data Vis Certified',
[certTypes.backEnd]: 'Back End Certified',
[certTypes.fullStack]: 'Full Stack Certified'
[certTypes.fullStack]: 'Full Stack Certified',
[certTypes.respWebDesign]: 'Responsive Web Design Certified',
[certTypes.frontEndLibs]: 'Front End Libraries Certified',
[certTypes.jsAlgoDataStruct]:
'JavaScript Algorithms and Data Structures Certified',
[certTypes.dataVis]: 'Data Visualization Certified',
[certTypes.apisMicroservices]: 'APIs and Microservices Certified',
[certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
};
const dateFormat = 'MMM DD, YYYY';
@@ -139,7 +161,7 @@ function buildDisplayChallenges(
module.exports = function(app) {
const router = app.loopback.Router();
const api = app.loopback.Router();
const { AccessToken, Email, User } = app.models;
const { Email, User } = app.models;
const map$ = cachedMap(app.models);
function findUserByUsername$(username, fields) {
@@ -153,23 +175,6 @@ module.exports = function(app) {
);
}
AccessToken.findOne$ = Observable.fromNodeCallback(
AccessToken.findOne, AccessToken
);
router.get('/login', function(req, res) {
res.redirect(301, '/signin');
});
router.get('/logout', function(req, res) {
res.redirect(301, '/signout');
});
router.get('/signup', getEmailSignin);
router.get('/signin', getEmailSignin);
router.get('/signout', signout);
router.get('/email-signin', getEmailSignin);
router.get('/deprecated-signin', getDepSignin);
router.get('/passwordless-auth', invalidateAuthToken, getPasswordlessAuth);
api.post('/passwordless-auth', postPasswordlessAuth);
router.get(
'/delete-my-account',
sendNonUserToMap,
@@ -208,11 +213,6 @@ module.exports = function(app) {
showCert.bind(null, certTypes.frontEnd)
);
api.get(
'/:username/data-visualization-certification',
showCert.bind(null, certTypes.dataVis)
);
api.get(
'/:username/back-end-certification',
showCert.bind(null, certTypes.backEnd)
@@ -223,6 +223,36 @@ module.exports = function(app) {
(req, res) => res.redirect(req.url.replace('full-stack', 'back-end'))
);
api.get(
'/:username/responsive-web-design-certification',
showCert.bind(null, certTypes.respWebDesign)
);
api.get(
'/:username/front-end-libraries-certification',
showCert.bind(null, certTypes.frontEndLibs)
);
api.get(
'/:username/javascript-algorithms-data-structures-certification',
showCert.bind(null, certTypes.jsAlgoDataStruct)
);
api.get(
'/:username/data-visualization-certification',
showCert.bind(null, certTypes.dataVis)
);
api.get(
'/:username/apis-microservices-certification',
showCert.bind(null, certTypes.apisMicroservices)
);
api.get(
'/:username/information-security-quality-assurance-certification',
showCert.bind(null, certTypes.infosecQa)
);
router.get('/:username', showUserProfile);
router.get(
'/:username/report-user/',
@@ -240,179 +270,6 @@ module.exports = function(app) {
app.use('/:lang', router);
app.use(api);
const defaultErrorMsg = [ 'Oops, something is not right, please request a ',
'fresh link to sign in / sign up.' ].join('');
function postPasswordlessAuth(req, res) {
if (req.user || !(req.body && req.body.email)) {
return res.redirect('/');
}
return User.requestAuthEmail(req.body.email)
.then(msg => {
return res.status(200).send({ message: msg });
})
.catch(err => {
debug(err);
return res.status(200).send({ message: defaultErrorMsg });
});
}
function invalidateAuthToken(req, res, next) {
if (req.user) {
res.redirect('/');
}
if (!req.query || !req.query.email || !req.query.token) {
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const authTokenId = req.query.token;
const authEmailId = new Buffer(req.query.email, 'base64').toString();
return AccessToken.findOne$({ where: {id: authTokenId} })
.map(authToken => {
if (!authToken) {
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const userId = authToken.userId;
return User.findById(userId, (err, user) => {
if (err || !user || user.email !== authEmailId) {
debug(err);
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
return authToken.validate((err, isValid) => {
if (err) { throw err; }
if (!isValid) {
req.flash('info', { msg: [ 'Looks like the link you clicked has',
'expired, please request a fresh link, to sign in.'].join('')
});
return res.redirect('/email-signin');
}
return authToken.destroy((err) => {
if (err) { debug(err); }
next();
});
});
});
})
.subscribe(
() => {},
next
);
}
function getPasswordlessAuth(req, res, next) {
if (req.user) {
req.flash('info', {
msg: 'Hey, looks like youre already signed in.'
});
return res.redirect('/');
}
if (!req.query || !req.query.email || !req.query.token) {
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const email = new Buffer(req.query.email, 'base64').toString();
return User.findOne$({ where: { email }})
.map(user => {
if (!user) {
debug(`did not find a valid user with email: ${email}`);
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const emailVerified = true;
const emailAuthLinkTTL = null;
const emailVerifyTTL = null;
user.update$({
emailVerified, emailAuthLinkTTL, emailVerifyTTL
})
.do((user) => {
user.emailVerified = emailVerified;
user.emailAuthLinkTTL = emailAuthLinkTTL;
user.emailVerifyTTL = emailVerifyTTL;
});
return user.createAccessToken(
{ ttl: User.settings.ttl }, (err, accessToken) => {
if (err) { throw err; }
var config = {
signed: !!req.signedCookies,
maxAge: accessToken.ttl
};
if (accessToken && accessToken.id) {
debug('setting cookies');
res.cookie('access_token', accessToken.id, config);
res.cookie('userId', accessToken.userId, config);
}
return req.logIn({
id: accessToken.userId.toString() }, err => {
if (err) { return next(err); }
debug('user logged in');
if (req.session && req.session.returnTo) {
var redirectTo = req.session.returnTo;
if (redirectTo === '/map-aside') {
redirectTo = '/map';
}
return res.redirect(redirectTo);
}
req.flash('success', { msg:
'Success! You have signed in to your account. Happy Coding!'
});
return res.redirect('/');
});
});
})
.subscribe(
() => {},
next
);
}
function signout(req, res) {
req.logout();
res.redirect('/');
}
function getDepSignin(req, res) {
if (req.user) {
return res.redirect('/');
}
return res.render('account/deprecated-signin', {
title: 'Sign in to freeCodeCamp using a Deprecated Login'
});
}
function getEmailSignin(req, res) {
if (req.user) {
return res.redirect('/');
}
if (isSignUpDisabled) {
return res.render('account/beta', {
title: 'New sign ups are disabled'
});
}
return res.render('account/email-signin', {
title: 'Sign in to freeCodeCamp using your Email Address'
});
}
function getAccount(req, res) {
const { username } = req.user;
return res.redirect('/' + username);
@@ -586,9 +443,14 @@ module.exports = function(app) {
isLocked: true,
isAvailableForHire: true,
isFrontEndCert: true,
isDataVisCert: true,
isBackEndCert: true,
isFullStackCert: true,
isRespWebDesignCert: true,
isFrontEndLibsCert: true,
isJsAlgoDataStructCert: true,
isDataVisCert: true,
isApisMicroservicesCert: true,
isInfosecQaCert: true,
isHonest: true,
username: true,
name: true,

View File

@@ -7,8 +7,7 @@
"rest": {
"handleErrors": false,
"normalizeHttpPath": false,
"xml": false,
"handleErrors": false
"xml": false
},
"json": {
"strict": false,

View File

@@ -56,7 +56,8 @@
"./middlewares/jade-helpers": {},
"./middlewares/migrate-completed-challenges": {},
"./middlewares/add-lang": {},
"./middlewares/flash-cheaters": {}
"./middlewares/flash-cheaters": {},
"./middlewares/passport-login": {}
},
"files": {},
"final:after": {
@@ -64,9 +65,9 @@
"./middlewares/error-handlers": {},
"strong-error-handler": {
"params": {
"debug": false,
"log": true
}
"debug": false,
"log": true
}
}
}
}

View File

@@ -1,33 +1,83 @@
import errorHandler from 'errorhandler';
import { inspect } from 'util';
import _ from 'lodash/fp';
import accepts from 'accepts';
import { unwrapHandledError } from '../utils/create-handled-error.js';
export default function prodErrorHandler() {
if (process.env.NODE_ENV === 'development') {
return errorHandler({ log: true });
const isDev = process.env.NODE_ENV !== 'production';
const toString = Object.prototype.toString;
// is full error or just trace
// _.toString(new Error('foo')) => "Error: foo
// Object.prototype.toString.call(new Error('foo')) => "[object Error]"
const isInspect = val => !val.stack && _.toString(val) === toString.call(val);
const stringifyErr = val => {
if (val.stack) {
return String(val.stack);
}
const str = String(val);
return isInspect(val) ?
inspect(val) :
str;
};
const createStackHtml = _.flow(
_.cond([
[isInspect, err => [err]],
// may be stack or just err.msg
[_.stubTrue, _.flow(stringifyErr, _.split('\n'), _.tail) ]
]),
_.map(_.escape),
_.map(line => `<li>${line}</lin>`),
_.join('')
);
const createErrorTitle = _.cond([
[
_.negate(isInspect),
_.flow(stringifyErr, _.split('\n'), _.head, _.defaultTo('Error'))
],
[_.stubTrue, _.constant('Error')]
]);
export default function prodErrorHandler() {
// 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;
const handled = unwrapHandledError(err);
// respect handled error status
let status = handled.status || err.status || res.statusCode;
if (!handled.status && status < 400) {
status = 500;
}
res.status(status);
// parse res type
const accept = accepts(req);
const type = accept.type('html', 'json', 'text');
const handled = unwrapHandledError(err);
const redirectTo = handled.redirectTo || '/map';
const redirectTo = handled.redirectTo || '/';
const message = handled.message ||
'Oops! Something went wrong. Please try again later';
if (isDev) {
console.error(err);
}
if (type === 'html') {
if (isDev) {
return res.render(
'dev-error',
{
...handled,
stack: createStackHtml(err),
errorTitle: createErrorTitle(err),
title: 'freeCodeCamp - Server Error',
status
}
);
}
if (typeof req.flash === 'function') {
req.flash(
handled.type || 'danger',
@@ -39,6 +89,7 @@ export default function prodErrorHandler() {
} else if (type === 'json') {
res.setHeader('Content-Type', 'application/json');
return res.send({
type: handled.type || 'errors',
message
});
// plain text

View File

@@ -8,7 +8,7 @@ import {
const log = debug('fcc:middlewares:error-reporter');
export default function keymetrics() {
export default function errorHandler() {
if (process.env.NODE_ENV !== 'production') {
return (err, req, res, next) => {
if (isHandledError(err)) {

View File

@@ -0,0 +1,21 @@
import _ from 'lodash';
import { Observable } from 'rx';
import { login } from 'passport/lib/http/request';
// make login polymorphic
// if supplied callback it works as normal
// if called without callback it returns an observable
// login(user, options?, cb?) => Void|Observable
function login$(...args) {
console.log('args');
if (_.isFunction(_.last(args))) {
return login.apply(this, args);
}
return Observable.fromNodeCallback(login).apply(this, args);
}
export default function passportLogin() {
return (req, res, next) => {
req.login = req.logIn = login$;
next();
};
}

View File

@@ -30,25 +30,27 @@ export default function() {
customSanitizers: {
// Refer : http://stackoverflow.com/a/430240/1932901
trimTags(value) {
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
const tagOrComment = new RegExp(
'<(?:'
// Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name
+ '|/?[a-z]'
+ tagBody
+ ')>',
'gi');
let rawValue;
do {
rawValue = value;
value = value.replace(tagOrComment, '');
} while (value !== rawValue);
return value.replace(/</g, '&lt;');
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
const tagOrComment = new RegExp(
'<(?:'
// Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name
+ '|/?[a-z]'
+ tagBody
+ ')>',
'gi'
);
let rawValue;
do {
rawValue = value;
value = value.replace(tagOrComment, '');
} while (value !== rawValue);
return value.replace(/</g, '&lt;');
}
}
});

View File

@@ -78,5 +78,9 @@
"about": {
"dataSource": "db",
"public": true
},
"AuthToken": {
"dataSource": "db",
"public": false
}
}

View File

@@ -0,0 +1,15 @@
import { Observable } from 'rx';
export default function(AuthToken) {
AuthToken.on('dataSourceAttached', () => {
AuthToken.findOne$ = Observable.fromNodeCallback(
AuthToken.findOne.bind(AuthToken)
);
AuthToken.prototype.validate = Observable.fromNodeCallback(
AuthToken.prototype.validate
);
AuthToken.prototype.destroy = Observable.fromNodeCallback(
AuthToken.prototype.destroy
);
});
}

View File

@@ -0,0 +1,13 @@
{
"name": "AuthToken",
"base": "AccessToken",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

View File

@@ -1,32 +1,33 @@
// this ensures node understands the future
require('babel-register');
const _ = require('lodash');
const createDebugger = require('debug');
var startTime = Date.now();
var timeoutHandler;
const log = createDebugger('fcc:server:production-start');
const startTime = Date.now();
// force logger to always output
// this may be brittle
log.enabled = true;
// this is where server starts booting up
var app = require('./server');
console.log('waiting for db to connect');
const app = require('./server');
var onConnect = function() {
console.log('db connected in %s ms', Date.now() - startTime);
let timeoutHandler;
let killTime = 15;
const onConnect = _.once(() => {
log('db connected in: %s', Date.now() - startTime);
if (timeoutHandler) {
clearTimeout(timeoutHandler);
}
app.start();
};
});
timeoutHandler = setTimeout(function() {
var message =
'db did not connect after ' +
(Date.now() - startTime) +
' ms --- crashing hard';
console.log(message);
timeoutHandler = setTimeout(() => {
const message = `db did not connect after ${killTime}s -- crashing hard`;
// purposely shutdown server
// pm2 should restart this in production
throw new Error(message);
}, 15000);
}, killTime * 1000);
app.dataSources.db.on('connected', onConnect);

View File

@@ -1,4 +1,5 @@
require('dotenv').load();
require('./utils/webpack-code-split-polyfill');
if (process.env.OPBEAT_ID) {
console.log('loading opbeat');
@@ -9,33 +10,23 @@ if (process.env.OPBEAT_ID) {
});
}
var _ = require('lodash'),
Rx = require('rx'),
loopback = require('loopback'),
boot = require('loopback-boot'),
expressState = require('express-state'),
path = require('path'),
setupPassport = require('./component-passport');
const _ = require('lodash');
const Rx = require('rx');
const loopback = require('loopback');
const boot = require('loopback-boot');
const expressState = require('express-state');
const path = require('path');
const setupPassport = require('./component-passport');
const createDebugger = require('debug');
const log = createDebugger('fcc:server');
// force logger to always output
// this may be brittle
log.enabled = true;
// polyfill for webpack bundle splitting
const requireProto = Object.getPrototypeOf(require);
if (!requireProto.hasOwnProperty('ensure')) {
Object.defineProperties(
requireProto,
{
ensure: {
value: function ensure(modules, callback) {
callback(this);
},
writable: false,
enumerable: false
}
}
);
}
Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production';
var app = loopback();
var isBeta = !!process.env.BETA;
const app = loopback();
const isBeta = !!process.env.BETA;
expressState.extend(app);
app.set('state namespace', '__fcc__');
@@ -52,17 +43,36 @@ boot(app, {
setupPassport(app);
const { db } = app.datasources;
db.on('connected', _.once(() => log('db connected')));
app.start = _.once(function() {
app.listen(app.get('port'), function() {
const server = app.listen(app.get('port'), function() {
app.emit('started');
console.log(
log(
'freeCodeCamp server listening on port %d in %s',
app.get('port'),
app.get('env')
);
if (isBeta) {
console.log('freeCodeCamp is in beta mode');
log('freeCodeCamp is in beta mode');
}
log(`connecting to db at ${db.settings.url}`);
});
process.on('SIGINT', () => {
log('Shutting down server');
server.close(() => {
log('Server is closed');
});
log('closing db connection');
db.disconnect()
.then(() => {
log('DB connection closed');
// exit process
// this may close kept alive sockets
// eslint-disable-next-line no-process-exit
process.exit(0);
});
});
});

View File

@@ -21,6 +21,11 @@ const publicUserProps = [
'isBackEndCert',
'isDataVisCert',
'isFullStackCert',
'isRespWebDesignCert',
'isFrontEndLibsCert',
'isJsAlgoDataStructCert',
'isApisMicroservicesCert',
'isInfosecQaCert',
'githubURL',
'sendMonthlyEmail',

View File

@@ -1,6 +1,11 @@
{
"frontEnd": "isFrontEndCert",
"backEnd": "isBackEndCert",
"fullStack": "isFullStackCert",
"respWebDesign": "isRespWebDesignCert",
"frontEndLibs": "isFrontEndLibsCert",
"jsAlgoDataStruct": "isJsAlgoDataStructCert",
"dataVis": "isDataVisCert",
"fullStack": "isFullStackCert"
"apisMicroservices": "isApisMicroservicesCert",
"infosecQa": "isInfosecQaCert"
}

View File

@@ -1,6 +1,11 @@
{
"frontEndCert": "Front End Development Certification",
"backEndCert": "Back End Development Certification",
"dataVisCert": "Data Visualisation Certification",
"fullStackCert": "Full Stack Development Certification"
"fullStackCert": "Full Stack Development Certification",
"respWebDesign": "Responsive Web Design Certification",
"frontEndLibs": "Front End Libraries Certification",
"jsAlgoDataStruct": "JavaScript Algorithms and Data Structures Certification",
"dataVis": "Data Visualisation Certification",
"apisMicroservices": "APIs and Microservices Certification",
"infosecQa": "Information Security and Quality Assurance Certification"
}

View File

@@ -10,9 +10,14 @@ export { commitGoals };
export function completeCommitment$(user) {
const {
isFrontEndCert,
isDataVisCert,
isBackEndCert,
isFullStackCert
isFullStackCert,
isRespWebDesignCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
} = user;
return Observable.fromNodeCallback(user.pledge, user)()
@@ -25,9 +30,15 @@ export function completeCommitment$(user) {
if (
(isFrontEndCert && goal === commitGoals.frontEndCert) ||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
(isBackEndCert && goal === commitGoals.backEndCert) ||
(isFullStackCert && goal === commitGoals.fullStackCert)
(isFullStackCert && goal === commitGoals.fullStackCert) ||
(isRespWebDesignCert && goal === commitGoals.respWebDesignCert) ||
(isFrontEndLibsCert && goal === commitGoals.frontEndLibsCert) ||
(isJsAlgoDataStructCert && goal === commitGoals.jsAlgoDataStructCert) ||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
(isApisMicroservicesCert &&
goal === commitGoals.apisMicroservicesCert) ||
(isInfosecQaCert && goal === commitGoals.infosecQaCert)
) {
debug('marking goal complete');
pledge.isCompleted = true;

View File

@@ -1,6 +1,11 @@
{
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36",
"frontEndChallengeId": "561add10cb82ac38a17513be",
"dataVisChallengeId": "561add10cb82ac38a17513b3",
"backEndChallengeId": "660add10cb82ac38a17513be"
"backEndChallengeId": "660add10cb82ac38a17513be",
"respWebDesignId": "561add10cb82ac38a17513bc",
"frontEndLibsId": "561acd10cb82ac38a17513bc",
"jsAlgoDataStructId": "561abd10cb81ac38a17513bc",
"dataVisId": "561add10cb82ac39a17513bc",
"apisMicroservicesId": "561add10cb82ac38a17523bc",
"infosecQaId": "561add10cb82ac38a17213bc"
}

View File

@@ -11,8 +11,20 @@ export function unwrapHandledError(err) {
export function wrapHandledError(err, {
type,
message,
redirectTo
redirectTo,
status = 200
}) {
err[_handledError] = { type, message, redirectTo };
err[_handledError] = { type, message, redirectTo, status };
return err;
}
export const createValidatorErrorFormatter = (type, redirectTo, status) =>
({ msg }) => wrapHandledError(
new Error(msg),
{
type,
message: msg,
redirectTo,
status
}
);

View File

@@ -209,6 +209,7 @@ export function getChallenge(
) {
return map
.flatMap(({ entities, result: { superBlocks } }) => {
const superBlock = entities.superBlock;
const block = entities.block[blockDashedName];
const challenge = entities.challenge[challengeDashedName];
return Observable.if(
@@ -226,6 +227,7 @@ export function getChallenge(
`/challenges/${block.dashedName}/${challenge.dashedName}` :
false,
entities: {
superBlock,
challenge: {
[challenge.dashedName]: mapChallengeToLang(challenge, lang)
}

View File

@@ -43,3 +43,13 @@ export function ifNotVerifiedRedirectToSettings(req, res, next) {
}
return next();
}
export function ifUserRedirectTo(path = '/', status) {
status = status === 302 ? 302 : 301;
return (req, res, next) => {
if (req.user) {
return res.status(status).redirect(path);
}
return next();
};
}

View File

@@ -0,0 +1,18 @@
export default function codeSplitPolyfill() {
// polyfill for webpack bundle splitting
const requireProto = Object.getPrototypeOf(require);
if (!requireProto.hasOwnProperty('ensure')) {
Object.defineProperties(
requireProto,
{
ensure: {
value: function ensure(modules, callback) {
callback(this);
},
writable: false,
enumerable: false
}
}
);
}
}

View File

@@ -34,18 +34,17 @@ block content
a(href="/deprecated-signin") Or click here if you want to sign in with other options.
script.
$(document).ready(function() {
function disableMagicButton (isDisabled) {
if (isDisabled) {
$('#magic-btn')
.html('<span class="fa fa-circle-o-notch fa-spin fa-fw"></span>')
.prop('disabled', true);
} else {
$('#magic-btn')
.html('<span class="fa.fa-envelope">Get a magic link to sign in.</span>')
.prop('disabled', false);
}
$(document).ready(function() {
function disableMagicButton (isDisabled) {
if (isDisabled) {
$('#magic-btn')
.prop('disabled', true)
.html('<span style="color:#E0E0E0;"><i class="fa fa-circle-o-notch fa-spin fa-fw"></i>Ok - We will attempt sending to the email above.</span>');
} else {
$('#magic-btn')
.prop('disabled', true)
.html('<span style="color:#E0E0E0;">Did not get a link? Reload the page and resend again.</span>');
}
}
$('form').submit(function(event){
@@ -54,34 +53,53 @@ block content
disableMagicButton(true);
var $form = $(event.target);
$.ajax({
type : 'POST',
url : $form.attr('action'),
data : $form.serialize(),
dataType : 'json',
encode : true,
xhrFields : { withCredentials: true }
type : 'POST',
url : $form.attr('action'),
data : $form.serialize(),
dataType : 'json',
encode : true,
xhrFields : { withCredentials: true }
})
.fail(error => {
if (error.responseText){
var data = JSON.parse(error.responseText);
if(data.error && data.error.message)
var data = JSON.parse(error.responseText);
if(data.error && data.error.message) {
$('#flash-content').html(data.error.message);
$('#flash-board')
.removeClass('alert-success')
.addClass('alert-info')
.slideDown(400)
.delay(800)
.fadeIn();
disableMagicButton(false);
}
}
})
.done(data =>{
if(data && data.message){
.done(data => {
if(data && data.message) {
var alertType = 'alert-';
switch (data.type) {
case 'errors': {
alertType += 'danger';
break
}
case 'success': {
alertType += 'success';
break
}
default: {
alertType += 'info';
}
}
$('#flash-content').html(data.message);
$('#flash-board')
.removeClass('alert-info')
.addClass('alert-success')
.fadeIn();
.removeClass('alert-info alert-success alert-danger')
.addClass(alertType)
.slideDown(400)
.delay(800)
.fadeIn();
disableMagicButton(false);
}
});
});
});
});

View File

@@ -9,10 +9,10 @@ block content
if (!user.isGithubCool)
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github')
i.fa.fa-github
| Link my GitHub to unlock my portfolio
| Link my GitHub to enable my public profile
.col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings')
| Update your settings
| Update my settings
.col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/signout')
| Sign me out of freeCodeCamp
@@ -56,6 +56,21 @@ block content
if isBackEndCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/back-end-certification') View My Back End Development Certification
if isRespWebDesignCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/responsive-web-design-certification') View My Responsive Web Design Certification
if isFrontEndLibsCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/front-end-libraries-certification') View My Front End Libraries Certification
if isJsAlgoDataStructCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/javascript-algorithms-data-structures-certification') View My JavaScript Algorithms Data Structures Certification
if isApisMicroservicesCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/apis-microservices-certification') View My APIs Microservices Certification
if isInfosecQaCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/information-security-quality-assurance-certification') View My Information Sequrity Quality Assurance Certification
if (user && user.username != username)
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/report-user/') Report this user's profile for abuse

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Advanced Frontend Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/advanced-front-end-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong APIs and Microservices Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/apis-and-microservices-certification

View File

@@ -3,34 +3,30 @@ link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-boot
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed the
h1
strong Back End Development Projects
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
footer
.row.signatures
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
p
strong Michael D. Johnson
.row
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/back-end-certification
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Back End Development Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/back-end-certification

View File

@@ -1,36 +0,0 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed the
h1
strong Data Visualization Projects
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
footer
.row.signatures
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
p
strong Michael D. Johnson
.row
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/data-visualization-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Data Visualization Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/data-visualization-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Front End Libraries Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/front-end-libraries-certification

View File

@@ -3,34 +3,30 @@ link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-boot
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed the
h1
strong Front End Development Projects
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
footer
.row.signatures
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
p
strong Michael D. Johnson
.row
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/front-end-certification
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Front End Development Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/front-end-certification

View File

@@ -3,34 +3,30 @@ link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-boot
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed the
h1
strong Full Stack Development Projects
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
footer
.row.signatures
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
.col-md-6
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
p
strong Michael D. Johnson
.row
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/full-stack-certification
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Full Stack Development Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/full-stack-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Information Security and Quality Assurance Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/information-security-and-quality-assurance-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong JavaScript Algorithms and Data Structures Certificate
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/javascript-algorithms-and-data-structures-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Responsive Web Design Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/responsive-web-design-certification

View File

@@ -70,6 +70,7 @@ style.
.information {
margin-top: -20px;
height: 380px;
text-align: center;
background-color: #efefef;
}
@@ -98,7 +99,7 @@ style.
color: #006400;
}
.signatures .col-md-6 {
.signatures {
text-align: center;
margin: 0 auto;
background-color: #efefef;
@@ -183,7 +184,7 @@ style.
}
}
@media screen and (max-width: 350px) {
@media screen and (max-width: 675px) {
.container {
padding: 0;
border: 0;

View File

@@ -23,18 +23,24 @@ block content
.col-xs-12.col-sm-6.col-sm-offset-3
h3 Step 1: Which certification do you pledge to complete?
.btn-group.btn-group-justified(data-toggle='buttons' role='group')
label.btn.btn-primary.btn-lg.active
input(type='radio' id=frontEndCert value=frontEndCert name='goal' checked="checked")
| Front End
label.btn.btn-primary.btn-lg
input(type='radio' id=dataVisCert value=dataVisCert name='goal')
| Data Vis
label.btn.btn-primary.btn-lg
input(type='radio' id=backEndCert value=backEndCert name='goal')
| Back End
label.btn.btn-primary.btn-lg
input(type='radio' id=fullStackCert value=fullStackCert name='goal')
| Full Stack
label.btn.btn-primary.active
input(type='radio' id="respWebDesignCert" value="Responsive Web Design Certification" name='goal' checked="checked")
| Responsive Web Design
label.btn.btn-primary
input(type='radio' id="frontEndLibsCert" value="Front End Libraries Certification" name='goal')
| Front End Libraries
label.btn.btn-primary
input(type='radio' id="jsAlgoDataStructCert" value="JavaScript Algorithms and Data Structures Certification" name='goal')
| JavaScript Algorithms and Data Structures
label.btn.btn-primary
input(type='radio' id="dataVisCert" value="Data Visualization Certification" name='goal')
| Data Visualization
label.btn.btn-primary
input(type='radio' id="apisMicroservicesCert" value="APIs and Microservices Certification" name='goal')
| APIs and Microservices
label.btn.btn-primary
input(type='radio' id="infosecQaCert" value="Information Security and Quality Assurance Certification" name='goal')
| Information Security and Quality Assurance
.spacer
.row
.col-xs-12.col-sm-6.col-sm-offset-3

View File

@@ -0,0 +1,58 @@
doctype html
head
title!= errorTitle
style.
* {
margin: 0;
padding: 0;
outline: 0;
}
body {
padding: 80px 100px;
background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9));
background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
background-repeat: no-repeat;
color: #555;
-webkit-font-smoothing: antialiased;
}
h1, h2 {
font-size: 22px;
color: #343434;
}
h1 em, h2 em {
padding: 0 5px;
font-weight: normal;
}
h1 {
font-size: 60px;
}
h2 {
margin-top: 10px;
}
ul li {
list-style: none;
}
#trace {
margin-top: 10px;
margin-left: 60px;
}
body
#wrapper
h1!= title
h2
em!= status
| #{errorTitle}
h2
em User Message:
| #{message}
h2
em Type:
| #{type}
h2
em redirect to:
a(href=redirectTo) #{redirectTo}
h2
em stack trace:
ul#trace!= stack

View File

@@ -1,7 +1,10 @@
Camper <%= username %> has completed all three certifications!
Camper <%= username %> has completed all six certifications!
Completed front end cert on <%= frontEndDate %>.
Completed data vis cert on <%= dataVisDate %>.
Completed back end cert on <%= backEndDate %>.
Completed Responsive Web Design Certification on <%= responsiveWebDesignDate %>.
Completed Front End Libraries Certification on <%= frontEndLibrariesDate %>.
Completed JavaScript Algorithms and Data Structures Certification on <%= javascriptAlgorithmsDataStructuresDate %>.
Completed Data Visualization Certification on <%= dataVisualizationDate %>.
Completed API's and microservices Certification on <%= apisMicroservicesDate %>.
Completed Information Security and Quality Assurance Certification on <%= infosecQADate %>.
https://www.freecodecamp.org/<%= username %>

View File

@@ -51,29 +51,29 @@ block content
.spacer
.row
.text-center.negative-35
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.ion-social-html5
h2.black-text HTML5
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.ion-social-css3
h2.black-text CSS3
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.ion-social-javascript
h2.black-text JavaScript
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.fa.fa-database.font-awesome-padding
h2.black-text Databases
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.ion-social-github
h2.black-text Git & GitHub
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.ion-social-nodejs
h2.black-text Node.js
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.custom-landing-skill-icon
img(src='https://s3.amazonaws.com/freecodecamp/react.svg')
h2.black-text React.js
.col-xs-12.col-sm-12.col-md-3
.col-xs-12.col-sm-6.col-md-3
.landing-skill-icon.custom-landing-skill-icon
img(src='https://s3.amazonaws.com/freecodecamp/d3-logo.svg')
h2.black-text D3.js

View File

@@ -1,5 +1,5 @@
.container
.row.flashMessage.negative-30
.row.flashMessage
.col-xs-12.col-sm-8.col-sm-offset-2.col-md-6.col-md-offset-3
if (messages.danger)
.alert.alert-danger.fade.in