From 1c2f5bab7e1b319733663ba74afca1c0ffca9899 Mon Sep 17 00:00:00 2001 From: Fabio Alemagna <507164+falemagn@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:01:38 +0200 Subject: [PATCH] fix: inherit root-level OpenAPI security requirements Use root-level security when an operation has no declaration, so the request console sends configured credentials for inherited requirements. Preserve explicit overrides, public operations and optional authentication. Add parser regression tests for inheritance, overrides and absent requirements. --- packages/rapidoc/src/utils/spec-parser.js | 2 +- tests/unit/spec-parser-security.test.js | 41 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/unit/spec-parser-security.test.js diff --git a/packages/rapidoc/src/utils/spec-parser.js b/packages/rapidoc/src/utils/spec-parser.js index a3d6a350..b2d2c840 100644 --- a/packages/rapidoc/src/utils/spec-parser.js +++ b/packages/rapidoc/src/utils/spec-parser.js @@ -593,7 +593,7 @@ function groupByTags(openApiSpec, sortEndpointsBy = 'none', generateMissingTags responses: pathOrHookObj.responses, callbacks: pathOrHookObj.callbacks, deprecated: pathOrHookObj.deprecated, - security: pathOrHookObj.security, + security: pathOrHookObj.security ?? openApiSpec.security, // commonSummary: commonPathProp.summary, // commonDescription: commonPathProp.description, xBadges: pathOrHookObj['x-badges'] || undefined, diff --git a/tests/unit/spec-parser-security.test.js b/tests/unit/spec-parser-security.test.js new file mode 100644 index 00000000..124b29b3 --- /dev/null +++ b/tests/unit/spec-parser-security.test.js @@ -0,0 +1,41 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import ProcessSpec from '../../packages/rapidoc/src/utils/spec-parser.js'; + +describe('operation security inheritance', () => { + const cases = [ + ['inherits root security', [{ bearerAuth: [] }], undefined, [{ bearerAuth: [] }]], + ['preserves explicit public access', [{ bearerAuth: [] }], [], []], + ['preserves operation overrides', [{ bearerAuth: [] }], [{ otherAuth: [] }], [{ otherAuth: [] }]], + ['preserves optional authentication', [{ bearerAuth: [] }], [{}, { bearerAuth: [] }], [{}, { bearerAuth: [] }]], + ['has no requirements when neither level declares security', undefined, undefined, undefined], + ]; + + for (const [name, rootSecurity, operationSecurity, expected] of cases) { + it(name, async () => { + const operation = { responses: { 200: { description: 'OK' } } }; + const spec = { + openapi: '3.0.3', + info: { title: 'Security inheritance', version: '1.0.0' }, + paths: { '/example': { get: operation } }, + components: { + securitySchemes: { + bearerAuth: { type: 'http', scheme: 'bearer' }, + otherAuth: { type: 'http', scheme: 'basic' }, + }, + }, + }; + + if (rootSecurity !== undefined) { + spec.security = rootSecurity; + } + if (operationSecurity !== undefined) { + operation.security = operationSecurity; + } + + const result = await ProcessSpec.call({ requestUpdate() {}, dispatchEvent() {} }, spec); + assert.strictEqual(result.specLoadError, false); + assert.deepStrictEqual(result.tags[0].paths[0].security, expected); + }); + } +});