From 6c41d34dda86c0778b32c115c12913b049cc55bc Mon Sep 17 00:00:00 2001 From: Nolann Biron Date: Thu, 30 Oct 2025 14:27:13 +0100 Subject: [PATCH 1/4] Improve OAuth2 scopes handling in OpenAPI --- .../components/DocumentView/OpenAPI/style.css | 40 ++++++++++--------- .../react-openapi/src/OpenAPISecurities.tsx | 5 ++- .../src/resolveOpenAPIOperation.ts | 19 ++++++--- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index 5fac6a4f32..521a1a6c43 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -34,7 +34,7 @@ .openapi-deprecated, .openapi-stability { - @apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none; + @apply py-0.5 px-1.5 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded straight-corners:rounded-none circular-corners:rounded-sm text-sm leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none; } .openapi-stability-alpha { @@ -72,7 +72,7 @@ } .openapi-markdown code { - @apply py-px px-1 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none; + @apply py-px px-1 min-w-[1.625rem] font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded straight-corners:rounded-none circular-corners:rounded-md text-sm leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none; } .openapi-markdown pre code { @@ -95,7 +95,7 @@ /* Method Tags */ .openapi-method, .openapi-statuscode { - @apply rounded uppercase font-mono items-center shrink-0 font-semibold text-[0.813rem] px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap; + @apply rounded straight-corners:rounded-none circular-corners:rounded-md uppercase font-mono items-center shrink-0 font-semibold text-[0.813rem] px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap; } .openapi-method-get, @@ -270,11 +270,11 @@ } .openapi-schema-enum-value:first-child { - @apply rounded-l ml-0; + @apply rounded-l straight-corners:rounded-none circular-corners:rounded-l-md ml-0; } .openapi-schema-enum-value:last-child { - @apply rounded-r; + @apply rounded-r straight-corners:rounded-none circular-corners:rounded-r-md; } /* Schema Description */ @@ -308,7 +308,7 @@ .openapi-schema-pattern code, .openapi-schema-enum-value code, .openapi-schema-default code { - @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none; + @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded straight-corners:rounded-none circular-corners:rounded-md text-xs leading-[calc(max(1.20em,1.25rem))] before:content-none! after:!content-none; } /* Authentication */ @@ -325,6 +325,10 @@ @apply prose *:!prose-sm *:text-tint; } +.openapi-securities-oauth-content { + @apply flex flex-col gap-1 mt-1; +} + .openapi-securities-oauth-content.openapi-markdown code { @apply text-xs; } @@ -334,7 +338,7 @@ } .openapi-securities-url { - @apply ml-0.5 px-0.5 rounded hover:bg-tint transition-colors; + @apply ml-0.5 px-0.5 rounded straight-corners:rounded-none circular-corners:rounded-md hover:bg-tint dark:hover:bg-tint-hover transition-colors; } .openapi-securities-body { @@ -478,7 +482,7 @@ } .openapi-path-variable { - @apply p-px min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-sm leading-none before:content-none! after:!content-none; + @apply p-px min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded straight-corners:rounded-none circular-corners:rounded-md text-sm leading-none before:content-none! after:!content-none; } .openapi-path-server { @@ -601,8 +605,8 @@ body:has(.openapi-select-popover) { } .openapi-select > button { - @apply flex items-center font-normal cursor-pointer *:truncate gap-1.5 p-1.5 border border-tint-subtle text-tint-strong rounded leading-none; - @apply hover:bg-tint-hover transition-all; + @apply flex items-center font-normal cursor-pointer *:truncate gap-1.5 p-1.5 border border-tint-subtle text-tint-strong rounded straight-corners:rounded-none circular-corners:rounded-md leading-none; + @apply hover:bg-tint dark:hover:bg-tint-hover transition-all; } .openapi-select:not(.openapi-select-unstyled) > button { @@ -634,7 +638,7 @@ body:has(.openapi-select-popover) { } .openapi-select-popover { - @apply min-w-32 z-10 max-w-[max(20rem,var(--trigger-width))] overflow-x-hidden max-h-52 overflow-y-auto p-1.5 border border-tint-subtle bg-tint-base backdrop-blur-xl rounded-md circular-corners:rounded-xl straight-corners:rounded-none; + @apply min-w-32 z-10 max-w-[max(20rem,var(--trigger-width))] overflow-x-hidden max-h-52 overflow-y-auto p-1.5 border border-tint-subtle bg-tint-base backdrop-blur-xl rounded-md straight-corners:rounded-none circular-corners:rounded-xl; @apply shadow-md shadow-tint-12/1 dark:shadow-tint-1/1; } @@ -647,7 +651,7 @@ body:has(.openapi-select-popover) { } .openapi-select-item { - @apply text-sm flex items-center cursor-pointer px-1.5 overflow-hidden py-1 text-tint ring-0 border-none rounded !outline-none; + @apply text-sm flex items-center cursor-pointer px-1.5 overflow-hidden py-1 text-tint ring-0 border-none rounded straight-corners:rounded-none circular-corners:rounded-md !outline-none; @apply hover:bg-tint-hover hover:theme-gradient:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current; } @@ -743,7 +747,7 @@ body:has(.openapi-select-popover) { } .openapi-tabs-tab { - @apply hover:bg-primary-hover whitespace-nowrap font-mono font-normal tabular-nums hover:text-primary cursor-pointer transition-all relative text-[0.813rem] text-tint px-1 border border-transparent rounded; + @apply hover:bg-primary-hover whitespace-nowrap font-mono font-normal tabular-nums hover:text-primary cursor-pointer transition-all relative text-[0.813rem] text-tint px-1 border border-transparent rounded straight-corners:rounded-none circular-corners:rounded-md; } .openapi-tabs-tab[aria-selected="true"] { @@ -814,12 +818,12 @@ body:has(.openapi-select-popover) { } .openapi-schemas-disclosure > .openapi-disclosure-trigger { - @apply flex items-center font-mono transition-all font-normal text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-5 truncate -outline-offset-1; + @apply flex items-center font-mono transition-all font-normal text-tint-strong !text-sm hover:bg-tint-subtle dark:hover:bg-tint-hover relative flex-1 gap-2.5 p-5 truncate -outline-offset-1; } .openapi-schemas-disclosure > .openapi-disclosure-trigger, .openapi-schemas-disclosure .openapi-disclosure-panel { - @apply straight-corners:!rounded-none; + @apply straight-corners:!rounded-none circular-corners:!rounded-md; } .openapi-disclosure-panel { @@ -847,7 +851,7 @@ body:has(.openapi-select-popover) { .openapi-schema-alternatives .openapi-disclosure, .openapi-schemas-disclosure .openapi-schema.openapi-disclosure ) { - @apply rounded-xl; + @apply rounded-xl straight-corners:rounded-none; } .openapi-disclosure .openapi-schemas-disclosure .openapi-schema.openapi-disclosure { @@ -997,8 +1001,8 @@ body:has(.openapi-select-popover) { } .openapi-path-copy-button { - @apply p-1 flex rounded-md; - @apply hover:bg-tint; + @apply p-1 flex rounded-md straight-corners:rounded-none; + @apply hover:bg-tint dark:hover:bg-tint-hover; } .openapi-path-copy-button-icon { diff --git a/packages/react-openapi/src/OpenAPISecurities.tsx b/packages/react-openapi/src/OpenAPISecurities.tsx index 72d27bd381..5e5cb626ea 100644 --- a/packages/react-openapi/src/OpenAPISecurities.tsx +++ b/packages/react-openapi/src/OpenAPISecurities.tsx @@ -167,7 +167,7 @@ function OpenAPISchemaOAuth2Item(props: { >]; name: string; context: OpenAPIClientContext; - security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean }; + security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean; scopes?: string[] }; }) { const { flow, context, security, name } = props; @@ -175,7 +175,8 @@ function OpenAPISchemaOAuth2Item(props: { return null; } - const scopes = flow.scopes ? Object.entries(flow.scopes) : []; + // If the security scheme has scopes, we don't need to display the scopes from the flow + const scopes = !security.scopes?.length && flow.scopes ? Object.entries(flow.scopes) : []; return (
diff --git a/packages/react-openapi/src/resolveOpenAPIOperation.ts b/packages/react-openapi/src/resolveOpenAPIOperation.ts index 2711fb9d11..15dfb6dc6e 100644 --- a/packages/react-openapi/src/resolveOpenAPIOperation.ts +++ b/packages/react-openapi/src/resolveOpenAPIOperation.ts @@ -158,15 +158,22 @@ function resolveSecurityScopes({ securityScheme?: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject; operationScopes?: string[]; }): OpenAPISecurityScope[] | null { - if ( - !securityScheme || - checkIsReference(securityScheme) || - isOAuthSecurityScheme(securityScheme) - ) { + if (!securityScheme || checkIsReference(securityScheme)) { return null; } - return operationScopes?.map((scope) => [scope, undefined]) || []; + // If the security scheme is an OAuth or OpenID Connect security scheme, we first check if the operation scopes are defined in the security scheme + if (isOAuthSecurityScheme(securityScheme) && operationScopes?.length) { + const flows = securityScheme.flows ? Object.entries(securityScheme.flows) : []; + + return flows.flatMap(([_, flow]) => { + return Object.entries(flow.scopes ?? {}).filter(([scope]) => + operationScopes.includes(scope) + ); + }); + } + + return operationScopes?.map((scope) => [scope, undefined]) || null; } /** From 0bd42f306044a35393eae4ea434f0e5ca283af9c Mon Sep 17 00:00:00 2001 From: Nolann Biron Date: Thu, 30 Oct 2025 15:31:23 +0100 Subject: [PATCH 2/4] Update types --- packages/react-openapi/src/OpenAPISecurities.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-openapi/src/OpenAPISecurities.tsx b/packages/react-openapi/src/OpenAPISecurities.tsx index 5e5cb626ea..f4808e69c6 100644 --- a/packages/react-openapi/src/OpenAPISecurities.tsx +++ b/packages/react-openapi/src/OpenAPISecurities.tsx @@ -138,7 +138,7 @@ function getLabelForType(security: OpenAPICustomSecurityScheme, context: OpenAPI function OpenAPISchemaOAuth2Flows(props: { context: OpenAPIClientContext; - security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean }; + security: OpenAPICustomSecurityScheme & { flows?: OpenAPIV3.OAuth2SecurityScheme['flows'] }; }) { const { context, security } = props; @@ -167,7 +167,7 @@ function OpenAPISchemaOAuth2Item(props: { >]; name: string; context: OpenAPIClientContext; - security: OpenAPIV3.OAuth2SecurityScheme & { required?: boolean; scopes?: string[] }; + security: OpenAPICustomSecurityScheme & { flows?: OpenAPIV3.OAuth2SecurityScheme['flows'] }; }) { const { flow, context, security, name } = props; From 7460bf8e1e069d86ac85438d99b113e7f8cb7774 Mon Sep 17 00:00:00 2001 From: Nolann Biron Date: Thu, 30 Oct 2025 15:33:11 +0100 Subject: [PATCH 3/4] Update condition --- packages/react-openapi/src/resolveOpenAPIOperation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-openapi/src/resolveOpenAPIOperation.ts b/packages/react-openapi/src/resolveOpenAPIOperation.ts index 15dfb6dc6e..9a7c1567db 100644 --- a/packages/react-openapi/src/resolveOpenAPIOperation.ts +++ b/packages/react-openapi/src/resolveOpenAPIOperation.ts @@ -158,12 +158,12 @@ function resolveSecurityScopes({ securityScheme?: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject; operationScopes?: string[]; }): OpenAPISecurityScope[] | null { - if (!securityScheme || checkIsReference(securityScheme)) { + if (!operationScopes?.length || !securityScheme || checkIsReference(securityScheme)) { return null; } // If the security scheme is an OAuth or OpenID Connect security scheme, we first check if the operation scopes are defined in the security scheme - if (isOAuthSecurityScheme(securityScheme) && operationScopes?.length) { + if (isOAuthSecurityScheme(securityScheme)) { const flows = securityScheme.flows ? Object.entries(securityScheme.flows) : []; return flows.flatMap(([_, flow]) => { @@ -173,7 +173,7 @@ function resolveSecurityScopes({ }); } - return operationScopes?.map((scope) => [scope, undefined]) || null; + return operationScopes.map((scope) => [scope, undefined]); } /** From 8aa46bfd352d17cf260aeea377ee89a4c2488c39 Mon Sep 17 00:00:00 2001 From: Nolann Biron Date: Thu, 30 Oct 2025 15:33:51 +0100 Subject: [PATCH 4/4] changeset --- .changeset/sunny-cobras-feel.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/sunny-cobras-feel.md diff --git a/.changeset/sunny-cobras-feel.md b/.changeset/sunny-cobras-feel.md new file mode 100644 index 0000000000..6d94fc5e96 --- /dev/null +++ b/.changeset/sunny-cobras-feel.md @@ -0,0 +1,6 @@ +--- +'@gitbook/react-openapi': patch +'gitbook': patch +--- + +Improve OAuth2 scopes handling in OpenAPI