2022-08-16 15:33:33 +02:00
|
|
|
import { Response, Request } from 'express';
|
|
|
|
import Controller from '../controller';
|
|
|
|
import { IUnleashConfig, IUnleashServices } from '../../types';
|
|
|
|
import { Logger } from '../../logger';
|
|
|
|
import { NONE } from '../../types/permissions';
|
|
|
|
import ApiUser from '../../types/api-user';
|
|
|
|
import {
|
|
|
|
proxyFeaturesSchema,
|
|
|
|
ProxyFeaturesSchema,
|
|
|
|
} from '../../openapi/spec/proxy-features-schema';
|
|
|
|
import { Context } from 'unleash-client';
|
|
|
|
import { createContext } from '../../proxy/create-context';
|
|
|
|
import { ProxyMetricsSchema } from '../../openapi/spec/proxy-metrics-schema';
|
|
|
|
import { ProxyClientSchema } from '../../openapi/spec/proxy-client-schema';
|
|
|
|
import { createResponseSchema } from '../../openapi/util/create-response-schema';
|
|
|
|
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
|
|
|
import { emptyResponse } from '../../openapi/util/standard-responses';
|
2022-08-19 08:09:44 +02:00
|
|
|
import { corsOriginMiddleware } from '../../middleware/cors-origin-middleware';
|
2022-08-16 15:33:33 +02:00
|
|
|
|
|
|
|
interface ApiUserRequest<
|
|
|
|
PARAM = any,
|
|
|
|
ResBody = any,
|
|
|
|
ReqBody = any,
|
|
|
|
ReqQuery = any,
|
|
|
|
> extends Request<PARAM, ResBody, ReqBody, ReqQuery> {
|
|
|
|
user: ApiUser;
|
|
|
|
}
|
|
|
|
|
2022-08-26 09:09:48 +02:00
|
|
|
type Services = Pick<
|
|
|
|
IUnleashServices,
|
|
|
|
'settingService' | 'proxyService' | 'openApiService'
|
|
|
|
>;
|
|
|
|
|
2022-08-16 15:33:33 +02:00
|
|
|
export default class ProxyController extends Controller {
|
|
|
|
private readonly logger: Logger;
|
|
|
|
|
2022-08-26 09:09:48 +02:00
|
|
|
private services: Services;
|
2022-08-16 15:33:33 +02:00
|
|
|
|
2022-08-26 09:09:48 +02:00
|
|
|
constructor(config: IUnleashConfig, services: Services) {
|
2022-08-16 15:33:33 +02:00
|
|
|
super(config);
|
2022-08-31 16:02:22 +02:00
|
|
|
this.logger = config.getLogger('proxy-api/index.ts');
|
2022-08-26 09:09:48 +02:00
|
|
|
this.services = services;
|
2022-08-16 15:33:33 +02:00
|
|
|
|
2022-08-26 15:16:29 +02:00
|
|
|
// Support CORS requests for the frontend endpoints.
|
|
|
|
// Preflight requests are handled in `app.ts`.
|
|
|
|
this.app.use(corsOriginMiddleware(services));
|
2022-08-19 08:09:44 +02:00
|
|
|
|
2022-08-16 15:33:33 +02:00
|
|
|
this.route({
|
|
|
|
method: 'get',
|
|
|
|
path: '',
|
|
|
|
handler: this.getProxyFeatures,
|
|
|
|
permission: NONE,
|
|
|
|
middleware: [
|
2022-08-26 09:09:48 +02:00
|
|
|
this.services.openApiService.validPath({
|
2022-08-16 15:33:33 +02:00
|
|
|
tags: ['Unstable'],
|
|
|
|
operationId: 'getFrontendFeatures',
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('proxyFeaturesSchema'),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
this.route({
|
|
|
|
method: 'post',
|
|
|
|
path: '',
|
|
|
|
handler: ProxyController.endpointNotImplemented,
|
|
|
|
permission: NONE,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.route({
|
|
|
|
method: 'get',
|
|
|
|
path: '/client/features',
|
|
|
|
handler: ProxyController.endpointNotImplemented,
|
|
|
|
permission: NONE,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.route({
|
|
|
|
method: 'post',
|
|
|
|
path: '/client/metrics',
|
|
|
|
handler: this.registerProxyMetrics,
|
|
|
|
permission: NONE,
|
|
|
|
middleware: [
|
2022-08-26 09:09:48 +02:00
|
|
|
this.services.openApiService.validPath({
|
2022-08-16 15:33:33 +02:00
|
|
|
tags: ['Unstable'],
|
|
|
|
operationId: 'registerFrontendMetrics',
|
|
|
|
requestBody: createRequestSchema('proxyMetricsSchema'),
|
|
|
|
responses: { 200: emptyResponse },
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
this.route({
|
|
|
|
method: 'post',
|
|
|
|
path: '/client/register',
|
|
|
|
handler: ProxyController.registerProxyClient,
|
|
|
|
permission: NONE,
|
|
|
|
middleware: [
|
2022-08-26 09:09:48 +02:00
|
|
|
this.services.openApiService.validPath({
|
2022-08-16 15:33:33 +02:00
|
|
|
tags: ['Unstable'],
|
|
|
|
operationId: 'registerFrontendClient',
|
|
|
|
requestBody: createRequestSchema('proxyClientSchema'),
|
|
|
|
responses: { 200: emptyResponse },
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
this.route({
|
|
|
|
method: 'get',
|
|
|
|
path: '/health',
|
|
|
|
handler: ProxyController.endpointNotImplemented,
|
|
|
|
permission: NONE,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.route({
|
|
|
|
method: 'get',
|
|
|
|
path: '/internal-backstage/prometheus',
|
|
|
|
handler: ProxyController.endpointNotImplemented,
|
|
|
|
permission: NONE,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async endpointNotImplemented(
|
|
|
|
req: ApiUserRequest,
|
|
|
|
res: Response,
|
|
|
|
) {
|
|
|
|
res.status(405).json({
|
|
|
|
message: 'The frontend API does not support this endpoint.',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private async getProxyFeatures(
|
|
|
|
req: ApiUserRequest,
|
|
|
|
res: Response<ProxyFeaturesSchema>,
|
|
|
|
) {
|
2022-08-26 09:09:48 +02:00
|
|
|
const toggles = await this.services.proxyService.getProxyFeatures(
|
2022-08-16 15:33:33 +02:00
|
|
|
req.user,
|
|
|
|
ProxyController.createContext(req),
|
|
|
|
);
|
2022-08-26 09:09:48 +02:00
|
|
|
this.services.openApiService.respondWithValidation(
|
2022-08-16 15:33:33 +02:00
|
|
|
200,
|
|
|
|
res,
|
|
|
|
proxyFeaturesSchema.$id,
|
|
|
|
{ toggles },
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private async registerProxyMetrics(
|
|
|
|
req: ApiUserRequest<unknown, unknown, ProxyMetricsSchema>,
|
|
|
|
res: Response,
|
|
|
|
) {
|
2022-08-26 09:09:48 +02:00
|
|
|
await this.services.proxyService.registerProxyMetrics(
|
2022-08-16 15:33:33 +02:00
|
|
|
req.user,
|
|
|
|
req.body,
|
|
|
|
req.ip,
|
|
|
|
);
|
|
|
|
res.sendStatus(200);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async registerProxyClient(
|
|
|
|
req: ApiUserRequest<unknown, unknown, ProxyClientSchema>,
|
|
|
|
res: Response<string>,
|
|
|
|
) {
|
|
|
|
// Client registration is not yet supported by @unleash/proxy,
|
|
|
|
// but proxy clients may still expect a 200 from this endpoint.
|
|
|
|
res.sendStatus(200);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static createContext(req: ApiUserRequest): Context {
|
|
|
|
const { query } = req;
|
|
|
|
query.remoteAddress = query.remoteAddress || req.ip;
|
|
|
|
query.environment = req.user.environment;
|
|
|
|
return createContext(query);
|
|
|
|
}
|
|
|
|
}
|