feat: Use new (tested) accessToken utils to authoize requests
This commit is contained in:
committed by
mrugesh mohapatra
parent
2f944b3aed
commit
3e8bac4590
@ -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": {},
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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) {
|
||||||
|
Reference in New Issue
Block a user