1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00
unleash.unleash/src/lib/app.ts
Pedro Papadopolis 24c2a70138
feat: Make compression middleware optional (#5306)
## Why
Currently AWS API Gateway doesn't have compression enabled by default,
this PR will make it easier to for example deploy Unleash over to AWS
Lambda without further configuration in API Gateway, frameworks like
Serverless requires a bit more work to set up compression and some times
one might not need compression at all.

## How
Create a new config flag called `disableCompression` which will not
include `compression` middleware in express' instance when set as true.
2023-12-16 08:06:26 +01:00

212 lines
6.9 KiB
TypeScript

import express, { Application, RequestHandler } from 'express';
import compression from 'compression';
import favicon from 'serve-favicon';
import cookieParser from 'cookie-parser';
import path from 'path';
import errorHandler from 'errorhandler';
import { responseTimeMetrics } from './middleware/response-time-metrics';
import { corsOriginMiddleware } from './middleware/cors-origin-middleware';
import rbacMiddleware from './middleware/rbac-middleware';
import apiTokenMiddleware from './middleware/api-token-middleware';
import { IUnleashServices } from './types/services';
import { IAuthType, IUnleashConfig } from './types/option';
import { IUnleashStores } from './types/stores';
import IndexRouter from './routes';
import requestLogger from './middleware/request-logger';
import demoAuthentication from './middleware/demo-authentication';
import ossAuthentication from './middleware/oss-authentication';
import noAuthentication from './middleware/no-authentication';
import secureHeaders from './middleware/secure-headers';
import { loadIndexHTML } from './util/load-index-html';
import { findPublicFolder } from './util/findPublicFolder';
import { conditionalMiddleware } from './middleware/conditional-middleware';
import patMiddleware from './middleware/pat-middleware';
import { Knex } from 'knex';
import maintenanceMiddleware from './features/maintenance/maintenance-middleware';
import { unless } from './middleware/unless-middleware';
import { catchAllErrorHandler } from './middleware/catch-all-error-handler';
import NotFoundError from './error/notfound-error';
export default async function getApp(
config: IUnleashConfig,
stores: IUnleashStores,
services: IUnleashServices,
unleashSession?: RequestHandler,
db?: Knex,
): Promise<Application> {
const app = express();
const baseUriPath = config.server.baseUriPath || '';
const publicFolder = config.publicFolder || findPublicFolder();
const indexHTML = await loadIndexHTML(config, publicFolder);
app.set('trust proxy', true);
app.disable('x-powered-by');
app.set('port', config.server.port);
app.locals.baseUriPath = baseUriPath;
if (config.server.serverMetrics && config.eventBus) {
app.use(
responseTimeMetrics(
config.eventBus,
config.flagResolver,
services.instanceStatsService,
),
);
}
app.use(requestLogger(config));
if (typeof config.preHook === 'function') {
config.preHook(app, config, services, db);
}
if (!config.server.disableCompression) {
app.use(compression());
}
app.use(cookieParser());
app.use((req, res, next) => {
req.url = req.url.replace(/\/+/g, '/');
next();
});
app.use(
`${baseUriPath}/api/admin/features-batch`,
express.json({ strict: false, limit: '500kB' }),
);
app.use(
unless(
`${baseUriPath}/api/admin/features-batch`,
express.json({ strict: false }),
),
);
if (unleashSession) {
app.use(unleashSession);
}
app.use(secureHeaders(config));
app.use(express.urlencoded({ extended: true }));
app.use(favicon(path.join(publicFolder, 'favicon.ico')));
app.use(baseUriPath, favicon(path.join(publicFolder, 'favicon.ico')));
app.use(baseUriPath, express.static(publicFolder, { index: false }));
if (config.enableOAS) {
app.use(`${baseUriPath}/oas`, express.static('docs/api/oas'));
}
if (config.enableOAS && services.openApiService) {
services.openApiService.useDocs(app);
}
// Support CORS preflight requests for the frontend endpoints.
// Preflight requests should not have Authorization headers,
// so this must be handled before the API token middleware.
app.options(
`${baseUriPath}/api/frontend*`,
corsOriginMiddleware(services, config),
);
app.use(baseUriPath, patMiddleware(config, services));
switch (config.authentication.type) {
case IAuthType.OPEN_SOURCE: {
app.use(baseUriPath, apiTokenMiddleware(config, services));
ossAuthentication(app, config.getLogger, config.server.baseUriPath);
break;
}
case IAuthType.ENTERPRISE: {
app.use(baseUriPath, apiTokenMiddleware(config, services));
if (config.authentication.customAuthHandler) {
config.authentication.customAuthHandler(app, config, services);
}
break;
}
case IAuthType.HOSTED: {
app.use(baseUriPath, apiTokenMiddleware(config, services));
if (config.authentication.customAuthHandler) {
config.authentication.customAuthHandler(app, config, services);
}
break;
}
case IAuthType.DEMO: {
app.use(baseUriPath, apiTokenMiddleware(config, services));
demoAuthentication(
app,
config.server.baseUriPath,
services,
config,
);
break;
}
case IAuthType.CUSTOM: {
app.use(baseUriPath, apiTokenMiddleware(config, services));
if (config.authentication.customAuthHandler) {
config.authentication.customAuthHandler(app, config, services);
}
break;
}
case IAuthType.NONE: {
noAuthentication(baseUriPath, app);
break;
}
default: {
app.use(baseUriPath, apiTokenMiddleware(config, services));
demoAuthentication(
app,
config.server.baseUriPath,
services,
config,
);
break;
}
}
app.use(
baseUriPath,
rbacMiddleware(config, stores, services.accessService),
);
app.use(
`${baseUriPath}/api/admin`,
maintenanceMiddleware(config, services.maintenanceService),
);
if (typeof config.preRouterHook === 'function') {
config.preRouterHook(app, config, services, stores, db);
}
// Setup API routes
app.use(`${baseUriPath}/`, new IndexRouter(config, services, db).router);
if (services.openApiService) {
services.openApiService.useErrorHandler(app);
}
if (process.env.NODE_ENV !== 'production') {
app.use(errorHandler());
} else {
app.use(catchAllErrorHandler(config.getLogger));
}
app.get(`${baseUriPath}`, (req, res) => {
res.send(indexHTML);
});
// handle all API 404s
app.use(`${baseUriPath}/api`, (req, res) => {
const error = new NotFoundError(
`The path you were looking for (${baseUriPath}/api${req.path}) is not available.`,
);
res.status(error.statusCode).send(error);
return;
});
app.get(`${baseUriPath}/*`, (req, res) => {
res.send(indexHTML);
});
return app;
}