feat(auth): Authorise 'external' requests through JWT (#17224)

This commit is contained in:
Stuart Taylor
2018-05-23 21:10:56 +01:00
committed by mrugesh mohapatra
parent 3397fbbf60
commit dfda68fb58
10 changed files with 132 additions and 8 deletions

View File

@ -8,6 +8,7 @@ import path from 'path';
import loopback from 'loopback';
import _ from 'lodash';
import { ObjectId } from 'mongodb';
import jwt from 'jsonwebtoken';
import { themes } from '../utils/themes';
import { saveUser, observeMethod } from '../../server/utils/rx.js';
@ -379,6 +380,8 @@ module.exports = function(User) {
domain: process.env.COOKIE_DOMAIN || 'localhost'
};
if (accessToken && accessToken.id) {
const jwtAccess = jwt.sign({accessToken}, process.env.JWT_SECRET);
res.cookie('jwt_access_token', jwtAccess, config);
res.cookie('access_token', accessToken.id, config);
res.cookie('userId', accessToken.userId, config);
}

59
package-lock.json generated
View File

@ -9954,6 +9954,30 @@
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
"dev": true
},
"jsonwebtoken": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.1.tgz",
"integrity": "sha512-l8rUBr0fqYYwPc8/ZGrue7GiW7vWdZtZqelxo4Sd5lMvuEeCK8/wS54sEo6tJhdZ6hqfutsj6COgC0d1XdbHGw==",
"requires": {
"jws": "3.1.4",
"lodash.includes": "4.3.0",
"lodash.isboolean": "3.0.3",
"lodash.isinteger": "4.0.4",
"lodash.isnumber": "3.0.3",
"lodash.isplainobject": "4.0.6",
"lodash.isstring": "4.0.1",
"lodash.once": "4.1.1",
"ms": "2.1.1",
"xtend": "4.0.1"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@ -11071,6 +11095,11 @@
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI="
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
@ -11083,6 +11112,11 @@
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
"dev": true
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
@ -11095,6 +11129,26 @@
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
"dev": true
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@ -11140,6 +11194,11 @@
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz",
"integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"lodash.partialright": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz",

View File

@ -84,6 +84,7 @@
"jquery": "~3.1.1",
"jshint": "~2.9.4",
"jsonlint-cli": "^1.0.1",
"jsonwebtoken": "^8.2.1",
"lightbox2": "~2.8.2",
"lodash": "^4.1.0",
"loopback": "^3.11.1",

View File

@ -13,5 +13,7 @@ export default function bootServices(app) {
Fetchr.registerFetcher(mapUi);
Fetchr.registerFetcher(user);
app.use('/services', Fetchr.middleware());
const middleware = Fetchr.middleware();
app.use('/services', middleware);
app.use('/external/services', middleware);
}

View File

@ -20,7 +20,7 @@ function buildUserUpdate(
timezone
) {
let finalChallenge;
const updateData = { $set: {}, $push: {} };
const updateData = { $push: {} };
const { timezone: userTimezone, completedChallenges = [] } = user;
const oldChallenge = _.find(
@ -127,13 +127,12 @@ export default function(app) {
router.get('/map', redirectToLearn);
app.use(api);
app.use('/external', api);
app.use(router);
function modernChallengeCompleted(req, res, next) {
const type = accepts(req).type('html', 'json', 'text');
req.checkBody('id', 'id must be an ObjectId').isMongoId();
req.checkBody('files', 'files must be an object with polyvinyls for keys')
.isFiles();
const errors = req.validationErrors(true);
if (errors) {

View File

@ -3,6 +3,7 @@ import { PassportConfigurator } from
'@freecodecamp/loopback-component-passport';
import passportProviders from './passport-providers';
import url from 'url';
import jwt from 'jsonwebtoken';
const passportOptions = {
emailOptional: true,
@ -143,6 +144,8 @@ export default function setupPassport(app) {
maxAge: accessToken.ttl,
domain: process.env.COOKIE_DOMAIN || 'localhost'
};
const jwtAccess = jwt.sign({accessToken}, process.env.JWT_SECRET);
res.cookie('jwt_access_token', jwtAccess, cookieConfig);
res.cookie('access_token', accessToken.id, cookieConfig);
res.cookie('userId', accessToken.userId, cookieConfig);
req.login(user);

View File

@ -55,7 +55,8 @@
"./middlewares/csp": {},
"./middlewares/jade-helpers": {},
"./middlewares/flash-cheaters": {},
"./middlewares/passport-login": {}
"./middlewares/passport-login": {},
"./middlewares/jwt-authorization": {}
},
"files": {},
"final:after": {

View File

@ -1,10 +1,17 @@
import csurf from 'csurf';
export default function() {
const protection = csurf({ cookie: true });
const protection = csurf(
{
cookie: {
domain: process.env.COOKIE_DOMAIN || 'localhost'
}
}
);
return function csrf(req, res, next) {
const path = req.path.split('/')[1];
if (/api/.test(path)) {
if (/(api|external)/.test(path)) {
return next();
}
return protection(req, res, next);

View File

@ -0,0 +1,50 @@
import jwt from 'jsonwebtoken';
import { isBefore } from 'date-fns';
import { wrapHandledError } from '../utils/create-handled-error';
export default () => function authorizeByJWT(req, res, next) {
const path = req.path.split('/')[1];
if (/external/.test(path)) {
const cookie = req.signedCookies && req.signedCookies['jwt_access_token'];
if (!cookie) {
throw wrapHandledError(
new Error('Access token is required for this request'),
{
type: 'info',
redirect: '/signin',
message: 'Access token is required for this request',
status: 403
}
);
}
let token;
try {
token = jwt.verify(cookie, process.env.JWT_SECRET);
} catch (err) {
throw wrapHandledError(
new Error(err.message),
{
type: 'info',
redirct: '/signin',
message: 'Your access token is invalid',
status: 403
}
);
}
const { accessToken: {created, ttl }} = token;
const valid = isBefore(Date.now(), Date.parse(created) + ttl);
if (!valid) {
throw wrapHandledError(
new Error('Access token is no longer vaild'),
{
type: 'info',
redirect: '/signin',
message: 'Access token is no longer vaild',
status: 403
}
);
}
return next();
}
return next();
};

View File

@ -1,7 +1,6 @@
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(name='csrf-token', content=_csrf)
title #{title} | freeCodeCamp
link(rel='canonical', href='https://www.freecodecamp.org')
meta(charset='utf-8')