feat(auth): Authorise 'external' requests through JWT (#17224)
This commit is contained in:
committed by
mrugesh mohapatra
parent
3397fbbf60
commit
dfda68fb58
@ -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
59
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -55,7 +55,8 @@
|
||||
"./middlewares/csp": {},
|
||||
"./middlewares/jade-helpers": {},
|
||||
"./middlewares/flash-cheaters": {},
|
||||
"./middlewares/passport-login": {}
|
||||
"./middlewares/passport-login": {},
|
||||
"./middlewares/jwt-authorization": {}
|
||||
},
|
||||
"files": {},
|
||||
"final:after": {
|
||||
|
@ -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);
|
||||
|
50
server/middlewares/jwt-authorization.js
Normal file
50
server/middlewares/jwt-authorization.js
Normal 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();
|
||||
};
|
@ -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')
|
||||
|
Reference in New Issue
Block a user