1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-31 00:16:47 +01:00
unleash.unleash/src/lib/app.ts
Nuno Góis a30ddd81c5
chore: bearer token middleware (#6624)
Adds a bearer token middleware that adds support for tokens prefixed
with "Bearer" scheme. Prefixing with "Bearer" is optional and the old
way of authenticating still works, so we now support both ways.

Also, added as part of our OpenAPI spec which now displays authorization
as follows:

![image](https://github.com/Unleash/unleash/assets/455064/77b17342-2315-4c08-bf34-4655e12a1cc3)

Related to #4630. Doesn't fully close the issue as we're still using
some invalid characters for the RFC, in particular `*` and `[]`

For safety reasons this is behind a feature flag

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
2024-04-02 10:21:38 +01:00

220 lines
7.3 KiB
TypeScript

import express, { type Application, type 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 type { IUnleashServices } from './types/services';
import { IAuthType, type IUnleashConfig } from './types/option';
import type { IUnleashStores } from './types';
import IndexRouter from './routes';
import requestLogger from './middleware/request-logger';
import demoAuthentication from './middleware/demo-authentication';
import ossAuthentication from './middleware/oss-authentication';
import noAuthentication, { noApiToken } from './middleware/no-authentication';
import secureHeaders from './middleware/secure-headers';
import { loadIndexHTML } from './util/load-index-html';
import { findPublicFolder } from './util/findPublicFolder';
import patMiddleware from './middleware/pat-middleware';
import type { 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';
import { bearerTokenMiddleware } from './middleware/bearer-token-middleware';
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);
const logger = config.getLogger('lib/app.ts');
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));
app.use(bearerTokenMiddleware(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: {
logger.warn(
'The AuthType=none option for Unleash is no longer recommended and will be removed in version 6.',
);
noApiToken(baseUriPath, app);
app.use(baseUriPath, apiTokenMiddleware(config, services));
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;
}