mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
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>
This commit is contained in:
parent
1c55d6b1f9
commit
a30ddd81c5
@ -76,6 +76,7 @@ exports[`should create default config 1`] = `
|
||||
"adminTokenKillSwitch": false,
|
||||
"anonymiseEventLog": false,
|
||||
"automatedActions": false,
|
||||
"bearerTokenMiddleware": false,
|
||||
"caseInsensitiveInOperators": false,
|
||||
"celebrateUnleash": false,
|
||||
"collectTrafficDataUsage": false,
|
||||
|
@ -28,6 +28,7 @@ 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,
|
||||
@ -59,6 +60,8 @@ export default async function getApp(
|
||||
|
||||
app.use(requestLogger(config));
|
||||
|
||||
app.use(bearerTokenMiddleware(config));
|
||||
|
||||
if (typeof config.preHook === 'function') {
|
||||
config.preHook(app, config, services, db);
|
||||
}
|
||||
|
66
src/lib/middleware/bearer-token-middleware.test.ts
Normal file
66
src/lib/middleware/bearer-token-middleware.test.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { bearerTokenMiddleware } from './bearer-token-middleware';
|
||||
import type { IUnleashConfig } from '../types';
|
||||
import { createTestConfig } from '../../test/config/test-config';
|
||||
import getLogger from '../../test/fixtures/no-logger';
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
const exampleSignalToken = 'signal_tokensecret';
|
||||
|
||||
describe('bearerTokenMiddleware', () => {
|
||||
const req = { headers: {}, path: '' } as Request;
|
||||
const res = {} as Response;
|
||||
const next = jest.fn();
|
||||
|
||||
let config: IUnleashConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
config = createTestConfig({
|
||||
getLogger,
|
||||
experimental: {
|
||||
flags: {
|
||||
bearerTokenMiddleware: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should call next', () => {
|
||||
const middleware = bearerTokenMiddleware(config);
|
||||
|
||||
middleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should leave Unleash tokens intact', () => {
|
||||
const middleware = bearerTokenMiddleware(config);
|
||||
|
||||
req.headers = { authorization: exampleSignalToken };
|
||||
|
||||
middleware(req, res, next);
|
||||
|
||||
expect(req.headers.authorization).toBe(exampleSignalToken);
|
||||
});
|
||||
|
||||
it('should convert Bearer token to Unleash token', () => {
|
||||
const middleware = bearerTokenMiddleware(config);
|
||||
|
||||
const bearerToken = `Bearer ${exampleSignalToken}`;
|
||||
req.headers = { authorization: bearerToken };
|
||||
|
||||
middleware(req, res, next);
|
||||
|
||||
expect(req.headers.authorization).toBe(exampleSignalToken);
|
||||
});
|
||||
|
||||
it('should be case insensitive in the scheme', () => {
|
||||
const middleware = bearerTokenMiddleware(config);
|
||||
|
||||
const bearerToken = `bEaReR ${exampleSignalToken}`;
|
||||
req.headers = { authorization: bearerToken };
|
||||
|
||||
middleware(req, res, next);
|
||||
|
||||
expect(req.headers.authorization).toBe(exampleSignalToken);
|
||||
});
|
||||
});
|
27
src/lib/middleware/bearer-token-middleware.ts
Normal file
27
src/lib/middleware/bearer-token-middleware.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import type { IUnleashConfig } from '../types';
|
||||
|
||||
export const bearerTokenMiddleware = ({
|
||||
getLogger,
|
||||
flagResolver,
|
||||
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>) => {
|
||||
const logger = getLogger('/middleware/bearer-token-middleware.ts');
|
||||
logger.debug('Enabling bearer token middleware');
|
||||
|
||||
return (req: Request, _: Response, next: NextFunction) => {
|
||||
if (
|
||||
req.path.startsWith('/api/signal-endpoint/') ||
|
||||
flagResolver.isEnabled('bearerTokenMiddleware')
|
||||
) {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (authHeader) {
|
||||
req.headers.authorization = authHeader.replace(
|
||||
/^Bearer\s+/i,
|
||||
'',
|
||||
);
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
@ -89,13 +89,22 @@ export const createOpenApiSchema = ({
|
||||
title: 'Unleash API',
|
||||
version: apiVersion,
|
||||
},
|
||||
security: [{ apiKey: [] }],
|
||||
security: [{ apiKey: [] }, { bearerToken: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
// https://swagger.io/docs/specification/authentication/api-keys/
|
||||
apiKey: {
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'Authorization',
|
||||
description: 'API key needed to access this API',
|
||||
},
|
||||
// https://swagger.io/docs/specification/authentication/bearer-authentication/
|
||||
bearerToken: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
description:
|
||||
'API key needed to access this API, in Bearer token format',
|
||||
},
|
||||
},
|
||||
schemas: mapValues(schemas, removeJsonSchemaProps),
|
||||
|
@ -56,7 +56,8 @@ export type IFlagKey =
|
||||
| 'returnGlobalFrontendApiCache'
|
||||
| 'projectOverviewRefactor'
|
||||
| 'variantDependencies'
|
||||
| 'newContextFieldsUI';
|
||||
| 'newContextFieldsUI'
|
||||
| 'bearerTokenMiddleware';
|
||||
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
|
||||
@ -277,6 +278,10 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_VARIANT_DEPENDENCIES,
|
||||
false,
|
||||
),
|
||||
bearerTokenMiddleware: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_BEARER_TOKEN_MIDDLEWARE,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||
|
Loading…
Reference in New Issue
Block a user