Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Middleware loading #106

Merged
merged 4 commits into from
Feb 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ An API gateway provides a single, unified entry point across one or more interna

Tree Gateway is a free and open source solution writen in Node JS that has a complete and customizable pipeline to handle your requests.
It provides:
- **Authentication**: More than 300 strategies available through an easy [passportjs](http://passportjs.org/) integration, including support to JWT tokens, Oauth, Basic and many others.
- **Authentication**: More than 480 strategies available through an easy [passportjs](http://passportjs.org/) integration, including support to JWT tokens, Oauth, Basic and many others.
- A flexible and robust **Routing system** that allows any kind of customized request pipeline.
- **Rate limits** - To control quotas for your customers and to define actions to be taken when any quota is exceeded.
- **Caching system** - Allow you to easily inject and control caching behavior for your APIs. Tree Gateway provides two kinds of cache:
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tree-gateway",
"version": "1.6.0",
"version": "2.0.0",
"homepage": "http://treegateway.org",
"description": "The Tree Gateway API Gateway",
"author": "Thiago da Rosa de Bustamante <trbustamante@gmail.com>",
Expand Down
62 changes: 44 additions & 18 deletions src/authentication/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,32 @@ export class ApiAuth {

authentication(apiRouter: express.Router, apiKey: string, api: ApiConfig, gatewayFeatures: ApiFeaturesConfig) {
const path: string = api.path;
let authentication: ApiAuthenticationConfig = api.authentication;
try {
authentication = this.resolveReferences(authentication, gatewayFeatures);
const authStrategy: auth.Strategy = this.middlewareLoader.loadMiddleware('authentication/strategy', authentication.strategy);
if (!authStrategy) {
this.logger.error('Error configuring authenticator. Invalid Strategy');
} else {
auth.use(apiKey, authStrategy);
const authentications: Array<ApiAuthenticationConfig> = this.sortMiddlewares(api.authentication, path);

const authenticator = auth.authenticate(apiKey, { session: false, failWithError: true });
if (authentication.group) {
this.createAuthenticatorForGroup(apiRouter, api, authentication, authenticator);
authentications.forEach((authentication: ApiAuthenticationConfig, index: number) => {
try {
authentication = this.resolveReferences(authentication, gatewayFeatures);
const authStrategy: auth.Strategy = this.middlewareLoader.loadMiddleware('authentication/strategy', authentication.strategy);
if (!authStrategy) {
this.logger.error('Error configuring authenticator. Invalid Strategy');
} else {
this.createAuthenticator(apiRouter, api, authentication, authenticator);
}
auth.use(`${apiKey}_${index}`, authStrategy);

const authenticator = auth.authenticate(`${apiKey}_${index}`, { session: false, failWithError: true });
if (authentication.group) {
this.createAuthenticatorForGroup(apiRouter, api, authentication, authenticator);
} else {
this.createAuthenticator(apiRouter, api, authentication, authenticator);
}

if (this.logger.isDebugEnabled) {
this.logger.debug(`Authentication Strategy [${this.middlewareLoader.getId(authentication.strategy)}] configured for path [${path}]`);
if (this.logger.isDebugEnabled) {
this.logger.debug(`Authentication Strategy [${this.middlewareLoader.getId(authentication.strategy)}] configured for path [${path}]`);
}
}
} catch (e) {
this.logger.error(`Error configuring Authentication Strategy [${this.middlewareLoader.getId(authentication.strategy)}] for path [${path}]`, e);
}
} catch (e) {
this.logger.error(`Error configuring Authentication Strategy [${this.middlewareLoader.getId(authentication.strategy)}] for path [${path}]`, e);
}
});
}

private resolveReferences(authentication: ApiAuthenticationConfig, features: ApiFeaturesConfig) {
Expand Down Expand Up @@ -132,4 +135,27 @@ export class ApiAuth {

return null;
}

private sortMiddlewares(middlewares: Array<ApiAuthenticationConfig>, path: string): Array<ApiAuthenticationConfig> {
const generalMiddlewares = _.filter(middlewares, (value) => {
if (value.group) {
return true;
}
return false;
});

if (generalMiddlewares.length > 1) {
this.logger.error(`Invalid authentication configuration for api [${path}]. Conflicting configurations for default group`);
return [];
}

if (generalMiddlewares.length > 0) {
const index = middlewares.indexOf(generalMiddlewares[0]);
if (index < middlewares.length - 1) {
const gen = middlewares.splice(index, 1);
middlewares.push(gen[0]);
}
}
return middlewares;
}
}
1 change: 1 addition & 0 deletions src/authentication/strategies/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ module.exports = function(authConfig: BasicAuthentication) {
const verifyFunction = middlewareLoader.loadMiddleware('authentication/verify', authConfig.verify);
return new BasicStrategy(verifyFunction);
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/authentication/strategies/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ function getExtractor(extractor: string, param: string) {
return ExtractJwt.fromAuthHeader();
}
}
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/authentication/strategies/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ module.exports = function(authConfig: LocalAuthentication) {
const verifyFunction = middlewareLoader.loadMiddleware('authentication/verify', authConfig.verify);
return new Strategy(opts, verifyFunction);
};
module.exports.factory = true;
6 changes: 3 additions & 3 deletions src/circuitbreaker/circuit-breaker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,19 @@ export class ApiCircuitBreaker {
if (config.onOpen) {
const openHandler = this.middlewareLoader.loadMiddleware('circuitbreaker', config.onOpen);
breakerInfo.circuitBreaker.on('open', () => {
openHandler(path, 'open');
openHandler(path, 'open', apiId);
});
}
if (config.onClose) {
const closeHandler = this.middlewareLoader.loadMiddleware('circuitbreaker', config.onClose);
breakerInfo.circuitBreaker.on('close', () => {
closeHandler(path, 'close');
closeHandler(path, 'close', apiId);
});
}
if (config.onRejected) {
const rejectedHandler = this.middlewareLoader.loadMiddleware('circuitbreaker', config.onRejected);
breakerInfo.circuitBreaker.on('rejected', () => {
rejectedHandler(path, 'rejected');
rejectedHandler(path, 'rejected', apiId);
});
}
}
Expand Down
40 changes: 21 additions & 19 deletions src/circuitbreaker/express-circuit-breaker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { EventEmitter } from 'events';
import * as express from 'express';
import { ProxyError } from '../error/errors';

export enum State { OPEN, CLOSED, HALF_OPEN }

Expand Down Expand Up @@ -73,16 +74,15 @@ export class CircuitBreaker extends EventEmitter {
}

middleware(): express.RequestHandler {
const self = this;
return (req, res, next) => {
// self.emit('request');
if (self.isOpen() || (self.isHalfOpen() && self.options.stateHandler.halfOpenCallPending)) {
return self.fastFail(res);
} else if (self.isHalfOpen() && !self.options.stateHandler.halfOpenCallPending) {
self.options.stateHandler.halfOpenCallPending = true;
return self.invokeApi(req, res, next);
// this.emit('request');
if (this.isOpen() || (this.isHalfOpen() && this.options.stateHandler.halfOpenCallPending)) {
return this.fastFail(next);
} else if (this.isHalfOpen() && !this.options.stateHandler.halfOpenCallPending) {
this.options.stateHandler.halfOpenCallPending = true;
return this.invokeApi(req, res, next);
} else {
return self.invokeApi(req, res, next);
return this.invokeApi(req, res, next);
}
};
}
Expand All @@ -96,13 +96,13 @@ export class CircuitBreaker extends EventEmitter {
}

private invokeApi(req: express.Request, res: express.Response, next: express.NextFunction) {
const self = this;
let operationTimeout = false;
const timeoutID = setTimeout(() => {
operationTimeout = true;
self.handleTimeout(req, res);
}, self.options.timeout);
this.handleTimeout(req, res, next);
}, this.options.timeout);
const end = res.end;
const self = this;
res.end = function(...args: any[]) {
if (!operationTimeout) {
clearTimeout(timeoutID);
Expand All @@ -120,18 +120,20 @@ export class CircuitBreaker extends EventEmitter {
return next();
}

private fastFail(res: express.Response) {
res.status(this.options.rejectStatusCode);
const err = new Error(this.options.rejectMessage);
res.end(err.message);
private fastFail(next: express.NextFunction) {
const err = new ProxyError(this.options.rejectMessage, this.options.rejectStatusCode);
next(err);
// res.status(this.options.rejectStatusCode);
// res.end(err.message);
this.emit('rejected', err);
}

private handleTimeout(req: express.Request, res: express.Response) {
const err = new Error(this.options.timeoutMessage);
private handleTimeout(req: express.Request, res: express.Response, next: express.NextFunction) {
const err = new ProxyError(this.options.timeoutMessage, this.options.timeoutStatusCode);
this.handleFailure(req, err);
res.status(this.options.timeoutStatusCode);
res.end(err.message);
// res.status(this.options.timeoutStatusCode);
// res.end(err.message);
next(err);
// this.emit('timeout', (Date.now() - startTime));
}

Expand Down
4 changes: 2 additions & 2 deletions src/config/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export interface ApiConfig {
/**
* Configuration for API authentication.
*/
authentication?: ApiAuthenticationConfig;
authentication?: Array<ApiAuthenticationConfig>;
/**
* Configuration for API cache.
*/
Expand Down Expand Up @@ -97,7 +97,7 @@ export interface ApiConfig {
}

export let apiConfigValidatorSchema = Joi.object().keys({
authentication: apiAuthenticationValidatorSchema,
authentication: Joi.alternatives([Joi.array().items(apiAuthenticationValidatorSchema), apiAuthenticationValidatorSchema]),
cache: Joi.alternatives([Joi.array().items(apiCacheConfigValidatorSchema), apiCacheConfigValidatorSchema]),
circuitBreaker: Joi.alternatives([Joi.array().items(apiCircuitBreakerConfigValidatorSchema), apiCircuitBreakerConfigValidatorSchema]),
cors: Joi.alternatives([Joi.array().items(apiCorsConfigSchema), apiCorsConfigSchema]),
Expand Down
9 changes: 8 additions & 1 deletion src/error/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { Errors } from 'typescript-rest';
import { HttpError, Errors } from 'typescript-rest';
import * as Joi from 'joi';
import * as _ from 'lodash';

Expand Down Expand Up @@ -70,3 +70,10 @@ export class UnavailableError extends Error {
Object.setPrototypeOf(this, UnavailableError.prototype);
}
}

export class ProxyError extends HttpError {
constructor(message: string, statusCode: number) {
super('gatewayError', statusCode, message);
Object.setPrototypeOf(this, UnavailableError.prototype);
}
}
1 change: 1 addition & 0 deletions src/error/handlers/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ module.exports = function(config: JSONAtaExpression) {
}
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/error/handlers/mustache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ module.exports = function(config: MustacheConfig) {
}
};
};
module.exports.factory = true;
16 changes: 11 additions & 5 deletions src/filter/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Logger } from '../logger';
import { Inject } from 'typescript-ioc';
import { MiddlewareLoader } from '../utils/middleware-loader';
import { ApiFilter as Filter } from '../config/filter';
import { NotFoundError } from '../error/errors';

export class ApiFilter {
@Inject private middlewareLoader: MiddlewareLoader;
Expand Down Expand Up @@ -39,7 +40,8 @@ export class ApiFilter {
return next();
}
if (!res.headersSent) {
res.sendStatus(404);
next(new NotFoundError());
// res.sendStatus(404);
}
}).catch(err => {
next(err);
Expand All @@ -66,7 +68,8 @@ export class ApiFilter {
return next();
}
if (!res.headersSent) {
res.sendStatus(404);
next(new NotFoundError());
// res.sendStatus(404);
}
}).catch(err => {
next(err);
Expand All @@ -82,7 +85,8 @@ export class ApiFilter {
return next();
}
if (!res.headersSent) {
res.sendStatus(404);
next(new NotFoundError());
// res.sendStatus(404);
}
}).catch(err => {
next(err);
Expand Down Expand Up @@ -113,7 +117,8 @@ export class ApiFilter {
if (groupValidator(req, res)) {
next();
} else {
res.sendStatus(404);
next(new NotFoundError());
// res.sendStatus(404);
}
});
}
Expand All @@ -129,7 +134,8 @@ export class ApiFilter {
if (groupValidator(req, res)) {
next();
} else {
res.sendStatus(404);
next(new NotFoundError());
// res.sendStatus(404);
}
});
}
Expand Down
1 change: 1 addition & 0 deletions src/filter/filters/ipFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,4 @@ module.exports = function(config: IpFilterConfig) {
}
return getBlacklistFilter(config);
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/requestBodyTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ module.exports = function(config: JSONAtaExpression) {
return result;
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/requestHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ module.exports = function(config: RequestHeadersConfig) {
return { headers: h };
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/requestMustache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ module.exports = function(config: MustacheConfig) {
return result;
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/requestXml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ module.exports = function(config: any) {
return result;
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/responseBodyTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ module.exports = function(config: JSONAtaExpression) {
});
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/responseHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ module.exports = function(config: ResponseHeadersConfig) {
return result;
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/responseMustache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ module.exports = function(config: MustacheConfig) {
return result;
};
};
module.exports.factory = true;
1 change: 1 addition & 0 deletions src/proxy/interceptors/responseXml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ module.exports = function(config: any) {
});
};
};
module.exports.factory = true;