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 loopback from 'loopback';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
import { themes } from '../utils/themes';
|
import { themes } from '../utils/themes';
|
||||||
import { saveUser, observeMethod } from '../../server/utils/rx.js';
|
import { saveUser, observeMethod } from '../../server/utils/rx.js';
|
||||||
@ -379,6 +380,8 @@ module.exports = function(User) {
|
|||||||
domain: process.env.COOKIE_DOMAIN || 'localhost'
|
domain: process.env.COOKIE_DOMAIN || 'localhost'
|
||||||
};
|
};
|
||||||
if (accessToken && accessToken.id) {
|
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('access_token', accessToken.id, config);
|
||||||
res.cookie('userId', accessToken.userId, config);
|
res.cookie('userId', accessToken.userId, config);
|
||||||
}
|
}
|
||||||
|
59
package-lock.json
generated
59
package-lock.json
generated
@ -9954,6 +9954,30 @@
|
|||||||
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
|
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
|
||||||
"dev": true
|
"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": {
|
"jsprim": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
|
||||||
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI="
|
"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": {
|
"lodash.isarguments": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
@ -11083,6 +11112,11 @@
|
|||||||
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
|
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
|
||||||
"dev": true
|
"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": {
|
"lodash.isequal": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
@ -11095,6 +11129,26 @@
|
|||||||
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
|
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
|
||||||
"dev": true
|
"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": {
|
"lodash.kebabcase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz",
|
||||||
"integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw="
|
"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": {
|
"lodash.partialright": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz",
|
||||||
|
@ -84,6 +84,7 @@
|
|||||||
"jquery": "~3.1.1",
|
"jquery": "~3.1.1",
|
||||||
"jshint": "~2.9.4",
|
"jshint": "~2.9.4",
|
||||||
"jsonlint-cli": "^1.0.1",
|
"jsonlint-cli": "^1.0.1",
|
||||||
|
"jsonwebtoken": "^8.2.1",
|
||||||
"lightbox2": "~2.8.2",
|
"lightbox2": "~2.8.2",
|
||||||
"lodash": "^4.1.0",
|
"lodash": "^4.1.0",
|
||||||
"loopback": "^3.11.1",
|
"loopback": "^3.11.1",
|
||||||
|
@ -13,5 +13,7 @@ export default function bootServices(app) {
|
|||||||
Fetchr.registerFetcher(mapUi);
|
Fetchr.registerFetcher(mapUi);
|
||||||
Fetchr.registerFetcher(user);
|
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
|
timezone
|
||||||
) {
|
) {
|
||||||
let finalChallenge;
|
let finalChallenge;
|
||||||
const updateData = { $set: {}, $push: {} };
|
const updateData = { $push: {} };
|
||||||
const { timezone: userTimezone, completedChallenges = [] } = user;
|
const { timezone: userTimezone, completedChallenges = [] } = user;
|
||||||
|
|
||||||
const oldChallenge = _.find(
|
const oldChallenge = _.find(
|
||||||
@ -127,13 +127,12 @@ export default function(app) {
|
|||||||
router.get('/map', redirectToLearn);
|
router.get('/map', redirectToLearn);
|
||||||
|
|
||||||
app.use(api);
|
app.use(api);
|
||||||
|
app.use('/external', api);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
function modernChallengeCompleted(req, res, next) {
|
function modernChallengeCompleted(req, res, next) {
|
||||||
const type = accepts(req).type('html', 'json', 'text');
|
const type = accepts(req).type('html', 'json', 'text');
|
||||||
req.checkBody('id', 'id must be an ObjectId').isMongoId();
|
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);
|
const errors = req.validationErrors(true);
|
||||||
if (errors) {
|
if (errors) {
|
||||||
|
@ -3,6 +3,7 @@ import { PassportConfigurator } from
|
|||||||
'@freecodecamp/loopback-component-passport';
|
'@freecodecamp/loopback-component-passport';
|
||||||
import passportProviders from './passport-providers';
|
import passportProviders from './passport-providers';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
const passportOptions = {
|
const passportOptions = {
|
||||||
emailOptional: true,
|
emailOptional: true,
|
||||||
@ -143,6 +144,8 @@ export default function setupPassport(app) {
|
|||||||
maxAge: accessToken.ttl,
|
maxAge: accessToken.ttl,
|
||||||
domain: process.env.COOKIE_DOMAIN || 'localhost'
|
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('access_token', accessToken.id, cookieConfig);
|
||||||
res.cookie('userId', accessToken.userId, cookieConfig);
|
res.cookie('userId', accessToken.userId, cookieConfig);
|
||||||
req.login(user);
|
req.login(user);
|
||||||
|
@ -55,7 +55,8 @@
|
|||||||
"./middlewares/csp": {},
|
"./middlewares/csp": {},
|
||||||
"./middlewares/jade-helpers": {},
|
"./middlewares/jade-helpers": {},
|
||||||
"./middlewares/flash-cheaters": {},
|
"./middlewares/flash-cheaters": {},
|
||||||
"./middlewares/passport-login": {}
|
"./middlewares/passport-login": {},
|
||||||
|
"./middlewares/jwt-authorization": {}
|
||||||
},
|
},
|
||||||
"files": {},
|
"files": {},
|
||||||
"final:after": {
|
"final:after": {
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import csurf from 'csurf';
|
import csurf from 'csurf';
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
const protection = csurf({ cookie: true });
|
const protection = csurf(
|
||||||
|
{
|
||||||
|
cookie: {
|
||||||
|
domain: process.env.COOKIE_DOMAIN || 'localhost'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
return function csrf(req, res, next) {
|
return function csrf(req, res, next) {
|
||||||
|
|
||||||
const path = req.path.split('/')[1];
|
const path = req.path.split('/')[1];
|
||||||
if (/api/.test(path)) {
|
if (/(api|external)/.test(path)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
return protection(req, res, 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(charset='utf-8')
|
||||||
meta(http-equiv='X-UA-Compatible', content='IE=edge')
|
meta(http-equiv='X-UA-Compatible', content='IE=edge')
|
||||||
meta(name='viewport', content='width=device-width, initial-scale=1.0')
|
meta(name='viewport', content='width=device-width, initial-scale=1.0')
|
||||||
meta(name='csrf-token', content=_csrf)
|
|
||||||
title #{title} | freeCodeCamp
|
title #{title} | freeCodeCamp
|
||||||
link(rel='canonical', href='https://www.freecodecamp.org')
|
link(rel='canonical', href='https://www.freecodecamp.org')
|
||||||
meta(charset='utf-8')
|
meta(charset='utf-8')
|
||||||
|
Reference in New Issue
Block a user