feat: Use new (tested) accessToken utils to authoize requests

This commit is contained in:
Bouncey
2019-02-20 23:05:31 +00:00
committed by mrugesh mohapatra
parent 2f944b3aed
commit 3e8bac4590
3 changed files with 187 additions and 89 deletions

View File

@ -31,7 +31,7 @@
"auth:before": { "auth:before": {
"./middlewares/add-return-to": {}, "./middlewares/add-return-to": {},
"./middlewares/cookie-parser": {}, "./middlewares/cookie-parser": {},
"./middlewares/jwt-authorization": {} "./middlewares/request-authorization": {}
}, },
"parse": { "parse": {
"body-parser#json": {}, "body-parser#json": {},

View File

@ -53,7 +53,7 @@ describe('request-authorization', () => {
describe('createRequestAuthorization', () => { describe('createRequestAuthorization', () => {
const requestAuthorization = createRequestAuthorization({ const requestAuthorization = createRequestAuthorization({
_jwtSecret: validJWTSecret, jwtSecret: validJWTSecret,
getUserById: mockGetUserById getUserById: mockGetUserById
}); });
@ -61,79 +61,183 @@ describe('request-authorization', () => {
expect(typeof requestAuthorization).toEqual('function'); expect(typeof requestAuthorization).toEqual('function');
}); });
it('throws when no access token is present', () => { describe('cookies', () => {
expect.assertions(2); it('throws when no access token is present', () => {
const req = mockReq({ path: '/internal/some-path/that-needs/auth' }); expect.assertions(2);
const res = mockRes(); const req = mockReq({ path: '/internal/some-path/that-needs/auth' });
const next = sinon.spy(); const res = mockRes();
expect(() => requestAuthorization(req, res, next)).toThrowError( const next = sinon.spy();
'Access token is required for this request' expect(() => requestAuthorization(req, res, next)).toThrowError(
); 'Access token is required for this request'
expect(next.called).toBe(false); );
}); expect(next.called).toBe(false);
it('throws when the access token is invalid', () => {
expect.assertions(2);
const invalidJWT = jwt.sign({ accessToken }, invalidJWTSecret);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: invalidJWT }
}); });
const res = mockRes();
const next = sinon.spy();
expect(() => requestAuthorization(req, res, next)).toThrowError( it('throws when the access token is invalid', () => {
'invalid signature' expect.assertions(2);
); const invalidJWT = jwt.sign({ accessToken }, invalidJWTSecret);
expect(next.called).toBe(false); const req = mockReq({
}); path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: invalidJWT }
});
const res = mockRes();
const next = sinon.spy();
it('throws when the access token has expired', () => { expect(() => requestAuthorization(req, res, next)).toThrowError(
expect.assertions(2); 'Access token is invalid'
const invalidJWT = jwt.sign( );
{ accessToken: { ...accessToken, created: theBeginningOfTime } }, expect(next.called).toBe(false);
validJWTSecret
);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: invalidJWT }
}); });
const res = mockRes();
const next = sinon.spy();
expect(() => requestAuthorization(req, res, next)).toThrowError( it('throws when the access token has expired', () => {
'Access token is no longer vaild' expect.assertions(2);
); const invalidJWT = jwt.sign(
expect(next.called).toBe(false); { accessToken: { ...accessToken, created: theBeginningOfTime } },
}); validJWTSecret
);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: invalidJWT }
});
const res = mockRes();
const next = sinon.spy();
it('adds the user to the request object', async done => { expect(() => requestAuthorization(req, res, next)).toThrowError(
expect.assertions(5); 'Access token is no longer vaild'
const validJWT = jwt.sign({ accessToken }, validJWTSecret); );
const req = mockReq({ expect(next.called).toBe(false);
path: '/internal/some-path/that-needs/auth', });
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: validJWT } it('adds the user to the request object', async done => {
expect.assertions(5);
const validJWT = jwt.sign({ accessToken }, validJWTSecret);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: validJWT }
});
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(next.called).toBe(true);
expect(req).toHaveProperty('user');
expect(req.user).toEqual(users['456def']);
expect(req.user).toHaveProperty('points');
expect(req.user.points).toEqual(4);
return done();
});
it('adds the jwt to the headers', async done => {
const validJWT = jwt.sign({ accessToken }, validJWTSecret);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: validJWT }
});
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(res.set.calledWith('X-fcc-access-token', validJWT)).toBe(true);
return done();
});
it('calls next if request does not require authorization', async () => {
const req = mockReq({ path: '/unauthenticated/another/route' });
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(next.called).toBe(true);
}); });
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(next.called).toBe(true);
expect(req).toHaveProperty('user');
expect(req.user).toEqual(users['456def']);
expect(req.user).toHaveProperty('points');
expect(req.user.points).toEqual(4);
return done();
}); });
it('calls next if request does not require authorization', async () => { describe('Auth header', () => {
const req = mockReq({ path: '/unauthenticated/another/route' }); it('throws when no access token is present', () => {
const res = mockRes(); expect.assertions(2);
const next = sinon.spy(); const req = mockReq({ path: '/internal/some-path/that-needs/auth' });
await requestAuthorization(req, res, next); const res = mockRes();
expect(next.called).toBe(true); const next = sinon.spy();
expect(() => requestAuthorization(req, res, next)).toThrowError(
'Access token is required for this request'
);
expect(next.called).toBe(false);
});
it('throws when the access token is invalid', () => {
expect.assertions(2);
const invalidJWT = jwt.sign({ accessToken }, invalidJWTSecret);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
headers: { 'X-fcc-access-token': invalidJWT }
});
const res = mockRes();
const next = sinon.spy();
expect(() => requestAuthorization(req, res, next)).toThrowError(
'Access token is invalid'
);
expect(next.called).toBe(false);
});
it('throws when the access token has expired', () => {
expect.assertions(2);
const invalidJWT = jwt.sign(
{ accessToken: { ...accessToken, created: theBeginningOfTime } },
validJWTSecret
);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
headers: { 'X-fcc-access-token': invalidJWT }
});
const res = mockRes();
const next = sinon.spy();
expect(() => requestAuthorization(req, res, next)).toThrowError(
'Access token is no longer vaild'
);
expect(next.called).toBe(false);
});
it('adds the user to the request object', async done => {
expect.assertions(5);
const validJWT = jwt.sign({ accessToken }, validJWTSecret);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
headers: { 'X-fcc-access-token': validJWT }
});
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(next.called).toBe(true);
expect(req).toHaveProperty('user');
expect(req.user).toEqual(users['456def']);
expect(req.user).toHaveProperty('points');
expect(req.user.points).toEqual(4);
return done();
});
it('adds the jwt to the headers', async done => {
const validJWT = jwt.sign({ accessToken }, validJWTSecret);
const req = mockReq({
path: '/internal/some-path/that-needs/auth',
// eslint-disable-next-line camelcase
cookie: { jwt_access_token: validJWT }
});
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(res.set.calledWith('X-fcc-access-token', validJWT)).toBe(true);
return done();
});
it('calls next if request does not require authorization', async () => {
const req = mockReq({ path: '/unauthenticated/another/route' });
const res = mockRes();
const next = sinon.spy();
await requestAuthorization(req, res, next);
expect(next.called).toBe(true);
});
}); });
}); });
}); });

View File

@ -1,9 +1,12 @@
import loopback from 'loopback'; import loopback from 'loopback';
import jwt from 'jsonwebtoken';
import { isBefore } from 'date-fns';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import {
getAccessTokenFromRequest,
errorTypes,
authHeaderNS
} from '../utils/getSetAccessToken';
import { homeLocation } from '../../../config/env'; import { homeLocation } from '../../../config/env';
import { jwtSecret as _jwtSecret } from '../../../config/secrets';
import { wrapHandledError } from '../utils/create-handled-error'; import { wrapHandledError } from '../utils/create-handled-error';
@ -19,18 +22,15 @@ export function isWhiteListedPath(path, whiteListREs = _whiteListREs) {
return whiteListREs.some(re => re.test(path)); return whiteListREs.some(re => re.test(path));
} }
export default ({ export default ({ jwtSecret = _jwtSecret, getUserById = _getUserById } = {}) =>
_jwtSecret = process.env.JWT_SECRET,
getUserById = _getUserById
} = {}) =>
function requestAuthorisation(req, res, next) { function requestAuthorisation(req, res, next) {
const { path } = req; const { path } = req;
if (apiProxyRE.test(path) && !isWhiteListedPath(path)) { if (apiProxyRE.test(path) && !isWhiteListedPath(path)) {
const cookie = const { accessToken, error, jwt } = getAccessTokenFromRequest(
(req.signedCookies && req.signedCookies['jwt_access_token']) || req,
(req.cookie && req.cookie['jwt_access_token']); jwtSecret
);
if (!cookie) { if (!accessToken && error === errorTypes.noTokenFound) {
throw wrapHandledError( throw wrapHandledError(
new Error('Access token is required for this request'), new Error('Access token is required for this request'),
{ {
@ -41,22 +41,15 @@ export default ({
} }
); );
} }
let token; if (!accessToken && error === errorTypes.invalidToken) {
try { throw wrapHandledError(new Error('Access token is invalid'), {
token = jwt.verify(cookie, _jwtSecret);
} catch (err) {
throw wrapHandledError(new Error(err.message), {
type: 'info', type: 'info',
redirect: `${homeLocation}/signin`, redirect: `${homeLocation}/signin`,
message: 'Your access token is invalid', message: 'Your access token is invalid',
status: 403 status: 403
}); });
} }
const { if (!accessToken && error === errorTypes.expiredToken) {
accessToken: { created, ttl, userId }
} = token;
const valid = isBefore(Date.now(), Date.parse(created) + ttl);
if (!valid) {
throw wrapHandledError(new Error('Access token is no longer vaild'), { throw wrapHandledError(new Error('Access token is no longer vaild'), {
type: 'info', type: 'info',
redirect: `${homeLocation}/signin`, redirect: `${homeLocation}/signin`,
@ -64,8 +57,9 @@ export default ({
status: 403 status: 403
}); });
} }
res.set(authHeaderNS, jwt);
if (isEmpty(req.user)) { if (isEmpty(req.user)) {
const { userId } = accessToken;
return getUserById(userId) return getUserById(userId)
.then(user => { .then(user => {
if (user) { if (user) {