prevent handlers overrides

Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com>
This commit is contained in:
Juan Pablo Garcia Ripa
2025-09-24 09:46:02 +02:00
parent fe17ef8115
commit c8cad6ac27
3 changed files with 94 additions and 10 deletions
@@ -354,6 +354,76 @@ describe('ExternalTokenHandler', () => {
expect(verifyMock).toHaveBeenCalledWith(customToken, { context: 'a' });
});
it('should fail if custom handler has same type as builtin handlers', async () => {
const logger = mockServices.logger.mock();
const customStaticHandler: ExternalTokenHandler<unknown> =
createExternalTokenHandler({
type: 'static',
initialize: jest.fn().mockResolvedValue(undefined),
verifyToken: jest.fn().mockResolvedValue({
subject: 'custom-static-subject',
}),
});
const createHandler = () =>
ExternalAuthTokenHandler.create({
ownPluginId: 'catalog',
logger,
config: mockServices.rootConfig({
data: {
backend: {
auth: {
externalAccess: [
{
type: 'static',
options: {
token: 'mytoken',
subject: 'static-subject',
},
accessRestrictions: [
{ plugin: 'catalog', permission: 'catalog.entity.read' },
],
},
],
},
},
},
}),
externalTokenHandlers: [customStaticHandler],
});
expect(createHandler).toThrowErrorMatchingInlineSnapshot(
`"Duplicate external token handler type 'static', each handler must have a unique type"`,
);
});
it('should fail if 2 custom handlers have the same type', async () => {
const createHandler = () =>
ExternalAuthTokenHandler.create({
ownPluginId: 'catalog',
logger: mockServices.logger.mock(),
config: mockServices.rootConfig(),
externalTokenHandlers: [
createExternalTokenHandler({
type: 'internal-custom',
initialize: jest.fn().mockResolvedValue(undefined),
verifyToken: jest.fn().mockResolvedValue({
subject: 'sub',
}),
}),
createExternalTokenHandler({
type: 'internal-custom',
initialize: jest.fn().mockResolvedValue(undefined),
verifyToken: jest.fn().mockResolvedValue({
subject: 'sub',
}),
}),
],
});
expect(createHandler).toThrowErrorMatchingInlineSnapshot(
`"Duplicate external token handler type 'internal-custom', each handler must have a unique type"`,
);
});
it('should fail if config contains types not declared', async () => {
const createHandler = () =>
ExternalAuthTokenHandler.create({
@@ -42,11 +42,11 @@ export const externalTokenHandlersServiceRef = createServiceRef<
multiton: true,
});
const defaultHandlers: ExternalTokenHandler<unknown>[] = [
staticTokenHandler,
legacyTokenHandler,
jwksTokenHandler,
];
const defaultHandlers: Record<string, ExternalTokenHandler<unknown>> = {
static: staticTokenHandler,
legacy: legacyTokenHandler,
jwks: jwksTokenHandler,
};
type ContextMapEntry<T> = {
context: T;
@@ -70,20 +70,35 @@ export class ExternalAuthTokenHandler {
const {
ownPluginId,
config,
externalTokenHandlers: customHandlers,
externalTokenHandlers: customHandlers = [],
logger,
} = options;
const handlersTypes = [...defaultHandlers, ...(customHandlers ?? [])];
const handlersTypes = customHandlers.reduce<
Record<string, ExternalTokenHandler<unknown>>
>(
(acc, handler) => {
if (acc[handler.type]) {
throw new Error(
`Duplicate external token handler type '${handler.type}', each handler must have a unique type`,
);
}
acc[handler.type] = handler;
return acc;
},
{ ...defaultHandlers },
);
const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];
const contexts: ContextMapEntry<unknown>[] = handlerConfigs.map(
handlerConfig => {
const type = handlerConfig.getString('type');
const handler = handlersTypes.find(h => h.type === type);
const handler = handlersTypes[type];
if (!handler) {
const valid = handlersTypes.map(h => `'${h.type}'`).join(', ');
const valid = Object.keys(handlersTypes)
.map(h => `'${h}'`)
.join(', ');
throw new Error(
`Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,
);
@@ -75,7 +75,6 @@ export const jwksTokenHandler = createExternalTokenHandler<JWKSTokenContext>({
: 'external:';
return {
subject: `${prefix}${sub}`,
allAccessRestrictions: context.allAccessRestrictions,
};
}
} catch {