diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx b/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx index 5cce172e92..d3018e184b 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx @@ -70,7 +70,7 @@ const ApiTokenForm: React.FC = ({ }, ]; - if (uiConfig.embedProxy) { + if (uiConfig.flags.embedProxyFrontend) { selectableTypes.splice(1, 0, { key: TokenType.FRONTEND, label: `Client-side SDK (${TokenType.FRONTEND})`, diff --git a/frontend/src/component/admin/menu/AdminMenu.tsx b/frontend/src/component/admin/menu/AdminMenu.tsx index f85102eb9c..2d684fe71b 100644 --- a/frontend/src/component/admin/menu/AdminMenu.tsx +++ b/frontend/src/component/admin/menu/AdminMenu.tsx @@ -85,7 +85,7 @@ function AdminMenu() { } /> - {uiConfig.embedProxy && ( + {uiConfig.flags.embedProxyFrontend && ( 0 - ) { - // 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('/api/frontend*', corsOriginMiddleware(services)); - } + // 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( + '/api/frontend*', + conditionalMiddleware( + () => config.flagResolver.isEnabled('embedProxy'), + corsOriginMiddleware(services), + ), + ); switch (config.authentication.type) { case IAuthType.OPEN_SOURCE: { @@ -152,7 +154,7 @@ export default async function getApp( app.get(`${baseUriPath}/*`, (req, res) => { if (req.path.startsWith(`${baseUriPath}/api`)) { - res.status(404).send({ message: '404 - Not found' }); + res.status(404).send({ message: 'Not found' }); return; } diff --git a/src/lib/middleware/api-token-middleware.ts b/src/lib/middleware/api-token-middleware.ts index a3267647c7..33bf563ed3 100644 --- a/src/lib/middleware/api-token-middleware.ts +++ b/src/lib/middleware/api-token-middleware.ts @@ -28,8 +28,8 @@ const apiAccessMiddleware = ( { getLogger, authentication, - experimental, - }: Pick, + flagResolver, + }: Pick, { apiTokenService }: any, ): any => { const logger = getLogger('/middleware/api-token.ts'); @@ -54,7 +54,7 @@ const apiAccessMiddleware = ( (apiUser.type === CLIENT && !isClientApi(req)) || (apiUser.type === FRONTEND && !isProxyApi(req)) || (apiUser.type === FRONTEND && - !experimental.flags.embedProxy) + !flagResolver.isEnabled('embedProxy')) ) { res.status(403).send({ message: TOKEN_TYPE_ERROR_MESSAGE }); return; diff --git a/src/lib/middleware/conditional-middleware.ts b/src/lib/middleware/conditional-middleware.ts new file mode 100644 index 0000000000..7292b299ce --- /dev/null +++ b/src/lib/middleware/conditional-middleware.ts @@ -0,0 +1,19 @@ +import { RequestHandler, Router } from 'express'; + +export const conditionalMiddleware = ( + condition: () => boolean, + middleware: RequestHandler, +): RequestHandler => { + const router = Router(); + + router.use((req, res, next) => { + if (condition()) { + next(); + } else { + res.status(404).send({ message: 'Not found' }); + } + }); + + router.use(middleware); + return router; +}; diff --git a/src/lib/openapi/spec/ui-config-schema.ts b/src/lib/openapi/spec/ui-config-schema.ts index 1c82027bab..fa7dc3e163 100644 --- a/src/lib/openapi/spec/ui-config-schema.ts +++ b/src/lib/openapi/spec/ui-config-schema.ts @@ -69,9 +69,6 @@ export const uiConfigSchema = { versionInfo: { $ref: '#/components/schemas/versionSchema', }, - embedProxy: { - type: 'boolean', - }, }, components: { schemas: { diff --git a/src/lib/routes/admin-api/config.ts b/src/lib/routes/admin-api/config.ts index e0037c9a8a..3604909b59 100644 --- a/src/lib/routes/admin-api/config.ts +++ b/src/lib/routes/admin-api/config.ts @@ -118,7 +118,6 @@ class ConfigController extends Controller { frontendApiOrigins: frontendSettings.frontendApiOrigins, versionInfo: this.versionService.getVersionInfo(), disablePasswordAuth, - embedProxy: this.config.experimental.flags.embedProxy, }; this.openApiService.respondWithValidation( diff --git a/src/lib/routes/index.ts b/src/lib/routes/index.ts index 27e842dfb4..132b909401 100644 --- a/src/lib/routes/index.ts +++ b/src/lib/routes/index.ts @@ -10,10 +10,12 @@ const ClientApi = require('./client-api'); const Controller = require('./controller'); import { HealthCheckController } from './health-check'; import ProxyController from './proxy-api'; +import { conditionalMiddleware } from '../middleware/conditional-middleware'; class IndexRouter extends Controller { constructor(config: IUnleashConfig, services: IUnleashServices) { super(config); + this.use('/health', new HealthCheckController(config, services).router); this.use('/internal-backstage', new BackstageController(config).router); this.use('/logout', new LogoutController(config).router); @@ -28,12 +30,13 @@ class IndexRouter extends Controller { this.use('/api/admin', new AdminApi(config, services).router); this.use('/api/client', new ClientApi(config, services).router); - if (config.experimental.flags.embedProxy) { - this.use( - '/api/frontend', + this.use( + '/api/frontend', + conditionalMiddleware( + () => config.flagResolver.isEnabled('embedProxy'), new ProxyController(config, services).router, - ); - } + ), + ); } } diff --git a/src/lib/routes/proxy-api/index.ts b/src/lib/routes/proxy-api/index.ts index 2443a47957..e2da77ce7a 100644 --- a/src/lib/routes/proxy-api/index.ts +++ b/src/lib/routes/proxy-api/index.ts @@ -41,11 +41,9 @@ export default class ProxyController extends Controller { this.logger = config.getLogger('client-api/feature.js'); this.services = services; - if (config.frontendApiOrigins.length > 0) { - // Support CORS requests for the frontend endpoints. - // Preflight requests are handled in `app.ts`. - this.app.use(corsOriginMiddleware(services)); - } + // Support CORS requests for the frontend endpoints. + // Preflight requests are handled in `app.ts`. + this.app.use(corsOriginMiddleware(services)); this.route({ method: 'get', diff --git a/src/lib/services/client-metrics/metrics-service-v2.ts b/src/lib/services/client-metrics/metrics-service-v2.ts index 3ffece6f23..dfb4ce15d1 100644 --- a/src/lib/services/client-metrics/metrics-service-v2.ts +++ b/src/lib/services/client-metrics/metrics-service-v2.ts @@ -10,7 +10,6 @@ import { import { clientMetricsSchema } from './schema'; import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns'; import { IFeatureToggleStore } from '../../types/stores/feature-toggle-store'; -import EventEmitter from 'events'; import { CLIENT_METRICS } from '../../types/events'; import ApiUser from '../../types/api-user'; import { ALL } from '../../types/models/api-token'; @@ -18,6 +17,8 @@ import User from '../../types/user'; import { collapseHourlyMetrics } from '../../util/collapseHourlyMetrics'; export default class ClientMetricsServiceV2 { + private config: IUnleashConfig; + private timers: NodeJS.Timeout[] = []; private unsavedMetrics: IClientMetricsEnv[] = []; @@ -26,10 +27,6 @@ export default class ClientMetricsServiceV2 { private featureToggleStore: IFeatureToggleStore; - private batchMetricsEnabled: boolean; - - private eventBus: EventEmitter; - private logger: Logger; constructor( @@ -37,28 +34,21 @@ export default class ClientMetricsServiceV2 { featureToggleStore, clientMetricsStoreV2, }: Pick, - { - experimental, - eventBus, - getLogger, - }: Pick, + config: IUnleashConfig, bulkInterval = secondsToMilliseconds(5), ) { this.featureToggleStore = featureToggleStore; this.clientMetricsStoreV2 = clientMetricsStoreV2; - this.batchMetricsEnabled = experimental.flags.batchMetrics; - this.eventBus = eventBus; - this.logger = getLogger( + this.config = config; + this.logger = config.getLogger( '/services/client-metrics/client-metrics-service-v2.ts', ); - if (this.batchMetricsEnabled) { - this.timers.push( - setInterval(() => { - this.bulkAdd().catch(console.error); - }, bulkInterval).unref(), - ); - } + this.timers.push( + setInterval(() => { + this.bulkAdd().catch(console.error); + }, bulkInterval).unref(), + ); this.timers.push( setInterval(() => { @@ -90,7 +80,7 @@ export default class ClientMetricsServiceV2 { })) .filter((item) => !(item.yes === 0 && item.no === 0)); - if (this.batchMetricsEnabled) { + if (this.config.flagResolver.isEnabled('batchMetrics')) { this.unsavedMetrics = collapseHourlyMetrics([ ...this.unsavedMetrics, ...clientMetrics, @@ -99,11 +89,11 @@ export default class ClientMetricsServiceV2 { await this.clientMetricsStoreV2.batchInsertMetrics(clientMetrics); } - this.eventBus.emit(CLIENT_METRICS, value); + this.config.eventBus.emit(CLIENT_METRICS, value); } async bulkAdd(): Promise { - if (this.batchMetricsEnabled && this.unsavedMetrics.length > 0) { + if (this.unsavedMetrics.length > 0) { // Make a copy of `unsavedMetrics` in case new metrics // arrive while awaiting `batchInsertMetrics`. const copy = [...this.unsavedMetrics]; diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 902442ff8d..3f9e0738b4 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -10,6 +10,10 @@ export const defaultExperimentalOptions = { process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY, false, ), + embedProxyFrontend: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY_FRONTEND, + false, + ), batchMetrics: parseEnvVarBoolean( process.env.UNLEASH_EXPERIMENTAL_BATCH_METRICS, false, @@ -23,6 +27,7 @@ export interface IExperimentalOptions { [key: string]: boolean; ENABLE_DARK_MODE_SUPPORT?: boolean; embedProxy?: boolean; + embedProxyFrontend?: boolean; batchMetrics?: boolean; anonymiseEventLog?: boolean; }; diff --git a/src/server-dev.ts b/src/server-dev.ts index 20975cc69f..ed8ea5d1c8 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -34,6 +34,7 @@ process.nextTick(async () => { // externalResolver: unleash, flags: { embedProxy: true, + embedProxyFrontend: true, batchMetrics: true, anonymiseEventLog: false, }, diff --git a/src/test/config/test-config.ts b/src/test/config/test-config.ts index 76033fbb79..39d9636278 100644 --- a/src/test/config/test-config.ts +++ b/src/test/config/test-config.ts @@ -25,6 +25,7 @@ export function createTestConfig(config?: IUnleashOptions): IUnleashConfig { experimental: { flags: { embedProxy: true, + embedProxyFrontend: true, batchMetrics: true, }, }, diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index 0f250958cb..548eda30c9 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -2839,9 +2839,6 @@ Object { "emailEnabled": Object { "type": "boolean", }, - "embedProxy": Object { - "type": "boolean", - }, "environment": Object { "type": "string", },