Skip to content

Commit

Permalink
feat(middleware): Add customAuthorization middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
Yusipov Asan authored and antongolub committed Nov 14, 2018
1 parent f1b460d commit 0a41072
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 5 deletions.
42 changes: 42 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"mocha-lcov-reporter": "^1.3.0",
"mocha-sinon": "^2.1.0",
"nyc": "^12.0.2",
"proxyquire": "^2.1.0",
"reqresnext": "^1.5.1",
"semantic-release": "^15.8.1",
"sinon": "^6.1.4",
Expand Down
6 changes: 4 additions & 2 deletions src/base/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
find,
mapValues,
assign,
isMatch
isMatch,
pick
} from 'lodash-es';

export {
Expand All @@ -26,5 +27,6 @@ export {
find,
mapValues,
assign,
isMatch
isMatch,
pick
};
19 changes: 18 additions & 1 deletion src/config/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ export const HOST_ARRAY = {type: 'array', items: HOST};
export const STRING_NON_EMPTY = {type: 'string', minLength: 1};
export const INTEGER = {type: 'integer'};


const URL = { type: "string", pattern: "^https?://.+" };
const STRING_ARRAY = { type: "array", items: STRING_NON_EMPTY };
const CUSTOM_AUTHORIZATION = {
type: "object",
properties: {
targetUrl: URL,
authorizationUrl: URL,
headers: STRING_ARRAY,
authPath: STRING_NON_EMPTY
},
required: ["targetUrl", "authorizationUrl", "headers", "authPath"]
};

// TODO refactor
export const SCHEMA = {
type: 'object',
Expand All @@ -34,7 +48,10 @@ export const SCHEMA = {
type: 'object',
patternProperties: {
'.+': {
type: 'object'
type: 'object',
properties: {
customAuthorization: CUSTOM_AUTHORIZATION
},
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/servlet/corsproxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import basicAuth from 'basic-auth';
import url from './url';
import Stats from './stats';
import Rules from './rules';
import { pipe, cors, error, gatekeeper, from, to, end, intercept, logger, crumbs } from './middlewares';
import { pipe, cors, error, gatekeeper, from, to, end, intercept, logger, crumbs, customAuthorization } from './middlewares';

export default class Server {
constructor() {
Expand All @@ -32,6 +32,7 @@ export default class Server {
.use(cors) // NOTE Handles res.piped.headers
.use(statsRes)
.use(crumbs)
.use(customAuthorization)
.use(end)
.use(error)
.disable('x-powered-by');
Expand Down
45 changes: 45 additions & 0 deletions src/servlet/corsproxy/middlewares/customAuthorization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import request from 'request';
import { get, pick } from '../../../base';

const normalizeUrl = url => url.replace(/^\//, '');

const checkUrl = (url, targetUrl) => normalizeUrl(url).indexOf(targetUrl) === 0;

const extractAuthorization = (body, path) => {
try {
const json = JSON.parse(body);
return get(json, path);
} catch (error) {
return false;
}
};

const processRequest = (req, res, next, config) => {
const options = {
url: config.authorizationUrl,
headers: pick(req.headers, config.headers)
};
request.get(options, (error, response, body) => {
const authorization = extractAuthorization(body, config.authPath);
if (authorization) {
const options = {
uri: normalizeUrl(req.url),
method: req.method,
headers: {
Authorization: authorization
}
};
request(options).pipe(res);
} else {
next(error);
}
});
};

export default (req, res, next) => {
const config = get(req, 'proxy.rule.customAuthorization');
if (config && checkUrl(req.url, config.targetUrl)) {
return processRequest(req, res, next, config);
}
return next();
};
4 changes: 3 additions & 1 deletion src/servlet/corsproxy/middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import end from './end';
import intercept from './intercept';
import crumbs from './crumbs';
import logger from './logger';
import customAuthorization from './customAuthorization';

export {
cors,
Expand All @@ -20,5 +21,6 @@ export {
end,
intercept,
crumbs,
logger
logger,
customAuthorization
};
72 changes: 72 additions & 0 deletions test/servlet/corsproxy/middleware/customAuthorization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import proxyquire from 'proxyquire';
import reqres from 'reqresnext';

const stubRequest = request =>
proxyquire(
'../../../../src/servlet/corsproxy/middlewares/customAuthorization',
{ request: request }
).default;

describe('corsproxy.middleware.customAuthorization', () => {
const authBody = '{"key1":{"key2":"SuchSecretMuchSecurity"}}';
const rule = {
customAuthorization: {
targetUrl: 'http://target',
authorizationUrl: 'http://authorization',
headers: ['authorization', 'additionalHeader'],
authPath: 'key1.key2'
}
};
const proxy = { rule };
const headers = {
authorization: '1',
additionalHeader: '2',
badHeader: '3'
};
const expectedHeaders = {
authorization: '1',
additionalHeader: '2'
};
const targetUrl = '/http://target';
const otherUrl = '/http://other';

it('exchanges headers to Authorization', () => {
let authReqOpts = {};
let proxyedReqOpts = {};
const request = sinon.stub().callsFake((opts, cb) => {
proxyedReqOpts = opts;
return { pipe: sinon.stub() };
});
sinon.stub(request, 'get').callsFake((opts, cb) => {
authReqOpts = opts;
cb(null, {}, authBody);
});
const customAuthorization = stubRequest(request);

const { req, res, next } = reqres(
{ proxy, url: targetUrl, headers },
{}
);
customAuthorization(req, res, next);

expect(authReqOpts.headers).to.deep.equal(expectedHeaders);
expect(proxyedReqOpts.headers.Authorization).to.be.equal(
'SuchSecretMuchSecurity'
);
});

it('does nothing to urls not from config', () => {
const request = sinon.stub();
const customAuthorization = stubRequest(request);
const pass = sinon.stub();

const { req, res, next } = reqres(
{ proxy, url: otherUrl, headers },
{}
);
customAuthorization(req, res, pass);

expect(pass.called).to.be.true;
expect(request.called).to.be.false;
});
});

0 comments on commit 0a41072

Please sign in to comment.