redoc/src/components/SecurityRequirement/SecurityRequirement.tsx
adamsosterics 04493ba933
feat: show sets of required scopes correctly
It is possible to define alternative required scopes for an operation
and in the security header of the operation it is rendered correctly.
However, in the "Required scopes" part it looks like that all these scopes are required.
With these changes, the "Required scopes" part shows the required scopes per set.

See: https://github.com/OAI/OpenAPI-Specification/discussions/3001#discussioncomment-3456275
2025-09-23 11:30:22 +02:00

103 lines
3.3 KiB
TypeScript

import * as React from 'react';
import { useState } from 'react';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import {
AuthHeader,
AuthHeaderColumn,
SecuritiesColumn,
SecurityDetailsStyle,
Wrap,
} from './styled.elements';
import { useStore } from '../StoreBuilder';
import { SecurityHeader } from './SecurityHeader';
import { RequiredScopesRow } from './RequiredScopesRow';
import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes';
import { Markdown } from '../Markdown/Markdown';
import { SecurityDetails } from './SecurityDetails';
import { ShelfIcon } from '../../common-elements';
export interface SecurityRequirementsProps {
securities: SecurityRequirementModel[];
}
export function SecurityRequirements(props: SecurityRequirementsProps) {
const store = useStore();
const showSecuritySchemeType = store?.options.showSecuritySchemeType;
const [expanded, setExpanded] = useState(false);
const { securities } = props;
if (!securities?.length || store?.options.hideSecuritySection) {
return null;
}
const operationSecuritySchemes = store?.spec.securitySchemes.schemes.filter(({ id }) => {
return securities.find(security => security.schemes.find(scheme => scheme.id === id));
});
return (
<>
<Wrap $expanded={expanded}>
<AuthHeaderColumn onClick={() => setExpanded(!expanded)}>
<AuthHeader>Authorizations:</AuthHeader>
<ShelfIcon size={'1.3em'} direction={expanded ? 'down' : 'right'} />
</AuthHeaderColumn>
<SecuritiesColumn $expanded={expanded}>
{securities.map((security, idx) => (
<SecurityHeader
key={idx}
expanded={expanded}
showSecuritySchemeType={showSecuritySchemeType}
security={security}
/>
))}
</SecuritiesColumn>
</Wrap>
{expanded &&
!!operationSecuritySchemes?.length &&
operationSecuritySchemes.map((scheme, idx) => (
<SecurityDetailsStyle key={idx}>
<h5>
<LockIcon /> {AUTH_TYPES[scheme.type] || scheme.type}: {scheme.id}
</h5>
<Markdown source={scheme.description || ''} />
<SecurityDetails
key={scheme.id}
scheme={scheme}
RequiredScopes={
<RequiredScopesRow scopeSets={getRequiredScopeSets(scheme.id, securities)} />
}
/>
</SecurityDetailsStyle>
))}
</>
);
}
const LockIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11">
<path
fill="currentColor"
d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"
/>
</svg>
);
function getRequiredScopeSets(id: string, securities: SecurityRequirementModel[]): string[][] {
const allScopes: string[][] = [];
let securitiesLength = securities.length;
while (securitiesLength--) {
const security = securities[securitiesLength];
let schemesLength = security.schemes.length;
while (schemesLength--) {
const scheme = security.schemes[schemesLength];
if (scheme.id === id && Array.isArray(scheme.scopes) && scheme.scopes.length) {
allScopes.unshift(scheme.scopes);
}
}
}
return Array.from(new Set(allScopes));
}