diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index aa6eb447c1..4dc64bfa8b 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -144,6 +144,7 @@ exports[`should create default config 1`] = ` }, "migrationLock": true, "navigationSidebar": true, + "originMiddleware": false, "outdatedSdksBanner": false, "parseProjectFromSession": false, "personalAccessTokensKillSwitch": false, diff --git a/src/lib/app.ts b/src/lib/app.ts index d2843ee535..6de9ec9fc4 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -30,6 +30,7 @@ import { catchAllErrorHandler } from './middleware/catch-all-error-handler'; import NotFoundError from './error/notfound-error'; import { bearerTokenMiddleware } from './middleware/bearer-token-middleware'; import { auditAccessMiddleware } from './middleware'; +import { originMiddleware } from './middleware/origin-middleware'; export default async function getApp( config: IUnleashConfig, @@ -177,6 +178,8 @@ export default async function getApp( rbacMiddleware(config, stores, services.accessService), ); + app.use(`${baseUriPath}/api/admin`, originMiddleware(config)); + app.use(`${baseUriPath}/api/admin`, auditAccessMiddleware(config)); app.use( `${baseUriPath}/api/admin`, diff --git a/src/lib/middleware/origin-middleware.test.ts b/src/lib/middleware/origin-middleware.test.ts new file mode 100644 index 0000000000..dd51b8fb95 --- /dev/null +++ b/src/lib/middleware/origin-middleware.test.ts @@ -0,0 +1,66 @@ +import { originMiddleware } from './origin-middleware'; +import type { IUnleashConfig } from '../types'; +import { createTestConfig } from '../../test/config/test-config'; +import type { Request, Response } from 'express'; + +const TEST_UNLEASH_TOKEN = 'TEST_UNLEASH_TOKEN'; +const TEST_USER_AGENT = 'TEST_USER_AGENT'; + +describe('originMiddleware', () => { + const req = { headers: {}, path: '' } as Request; + const res = {} as Response; + const next = jest.fn(); + const loggerMock = { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + }; + const getLogger = jest.fn(() => loggerMock); + + let config: IUnleashConfig; + + beforeEach(() => { + config = createTestConfig({ + getLogger, + experimental: { + flags: { + originMiddleware: true, + }, + }, + }); + }); + + it('should call next', () => { + const middleware = originMiddleware(config); + + middleware(req, res, next); + + expect(next).toHaveBeenCalled(); + }); + + it('should log UI request', () => { + const middleware = originMiddleware(config); + + middleware(req, res, next); + + expect(loggerMock.debug).toHaveBeenCalledWith('UI request', { + method: req.method, + }); + }); + + it('should log API request', () => { + const middleware = originMiddleware(config); + + req.headers.authorization = TEST_UNLEASH_TOKEN; + req.headers['user-agent'] = TEST_USER_AGENT; + + middleware(req, res, next); + + expect(loggerMock.debug).toHaveBeenCalledWith('API request', { + method: req.method, + userAgent: TEST_USER_AGENT, + }); + }); +}); diff --git a/src/lib/middleware/origin-middleware.ts b/src/lib/middleware/origin-middleware.ts new file mode 100644 index 0000000000..dee875a97d --- /dev/null +++ b/src/lib/middleware/origin-middleware.ts @@ -0,0 +1,29 @@ +import type { Request, Response, NextFunction } from 'express'; +import type { IUnleashConfig } from '../types'; + +export const originMiddleware = ({ + getLogger, + flagResolver, +}: Pick) => { + const logger = getLogger('/middleware/origin-middleware.ts'); + logger.debug('Enabling origin middleware'); + + return (req: Request, _: Response, next: NextFunction) => { + if (!flagResolver.isEnabled('originMiddleware')) { + return next(); + } + + const isUI = !req.headers.authorization; + + if (isUI) { + logger.debug('UI request', { method: req.method }); + } else { + logger.debug('API request', { + method: req.method, + userAgent: req.headers['user-agent'], + }); + } + + next(); + }; +}; diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index f045dcc441..4d09867b7b 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -70,7 +70,8 @@ export type IFlagKey = | 'insightsV2' | 'integrationEvents' | 'featureCollaborators' - | 'improveCreateFlagFlow'; + | 'improveCreateFlagFlow' + | 'originMiddleware'; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; @@ -339,6 +340,10 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_IMPROVE_CREATE_FLAG_FLOW, false, ), + originMiddleware: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = { diff --git a/src/server-dev.ts b/src/server-dev.ts index 0ef6ff71bd..ba772be3d0 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -60,6 +60,7 @@ process.nextTick(async () => { integrationEvents: true, featureCollaborators: true, improveCreateFlagFlow: true, + originMiddleware: true, }, }, authentication: {