diff --git a/src/common-elements/fields-layout.ts b/src/common-elements/fields-layout.ts index 54252b2d3b..f59f1a2235 100644 --- a/src/common-elements/fields-layout.ts +++ b/src/common-elements/fields-layout.ts @@ -73,7 +73,7 @@ export const PropertyNameCell = PropertyCell.extend` export const PropertyDetailsCell = styled.td` border-bottom: 1px solid #9fb4be; padding: 10px 0; - width: 75%; + width: ${props => props.theme.schemaView.defaultDetailsWidth}; box-sizing: border-box; tr.expanded & { diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index a973350f35..a1ddafb220 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import styled from '../../styled-components'; +import { SecurityRequirements } from '../SecurityRequirement/SecuirityRequirement'; import { observer } from 'mobx-react'; -import { H2, MiddlePanel, DarkRightPanel, Badge, Row } from '../../common-elements'; +import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements'; import { ComponentWithOptions } from '../OptionsProvider'; @@ -53,6 +54,7 @@ export class Operation extends ComponentWithOptions { {pathInMiddle && } {description !== undefined && } + diff --git a/src/components/SecurityRequirement/SecuirityRequirement.tsx b/src/components/SecurityRequirement/SecuirityRequirement.tsx new file mode 100644 index 0000000000..2860377363 --- /dev/null +++ b/src/components/SecurityRequirement/SecuirityRequirement.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import styled from '../../styled-components'; +import { transparentizeHex } from '../../utils/styled'; + +import { SecurityRequirementModel } from '../../services/models/SecurityRequirement'; +import { UnderlinedHeader } from '../../common-elements/headers'; + +const ScopeName = styled.code` + font-size: ${props => props.theme.code.fontSize}; + font-family: ${props => props.theme.code.fontFamily}; + border: 1px solid ${props => transparentizeHex(props.theme.colors.text, 0.15)}; + margin: 0 3px; + padding: 0.2em; + display: inline-block; + line-height: 1; +`; + +export interface SecurityRequirementProps { + security: SecurityRequirementModel; +} + +export class SecurityRequirement extends React.PureComponent { + render() { + const security = this.props.security; + return security.schemes.map((scheme, idx) => { + return ( +
+ {scheme.id} + {scheme.scopes.length > 0 && ' ('} + {scheme.scopes.map(scope => {scope})} + {scheme.scopes.length > 0 && ') '} + {idx < security.schemes.length - 1 && ' and '} +
+ ); + }); + } +} + +const AuthHeaderColumn = styled.div` + display: inline-block; + width: calc(100% - ${props => props.theme.schemaView.defaultDetailsWidth}); +`; + +const SecuritiesColumn = styled.div` + width: ${props => props.theme.schemaView.defaultDetailsWidth}; + display: inline-block; +`; + +const AuthHeader = styled(UnderlinedHeader)` + display: inline-block; +`; + +export interface SecurityRequirementsProps { + securities: SecurityRequirementModel[]; +} + +export class SecurityRequirements extends React.PureComponent { + render() { + const securities = this.props.securities; + if (!securities.length) return null; + return ( +
+ + Authorizations: + + + {securities.map((security, idx) => )} + +
+ ); + } +} diff --git a/src/components/SecuritySchemes/SecuritySchemes.tsx b/src/components/SecuritySchemes/SecuritySchemes.tsx index 99c928f8c1..0282a8c3de 100644 --- a/src/components/SecuritySchemes/SecuritySchemes.tsx +++ b/src/components/SecuritySchemes/SecuritySchemes.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { SecuritySchemesModel } from '../../services/models'; import styled from '../../styled-components'; -import { H2 } from '../../common-elements'; +import { H2, ShareLink } from '../../common-elements'; import { Markdown } from '../Markdown/Markdown'; import { OpenAPISecurityScheme } from '../../types'; @@ -81,8 +81,11 @@ export class SecurityDefs extends React.PureComponent { return (
{this.props.securitySchemes.schemes.map(scheme => ( -
-

{scheme.id}

+
+

+ + {scheme.id} +

diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index aa0f8278a9..659232baca 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -7,7 +7,7 @@ import { JsonPointer } from '../utils/JsonPointer'; import { isNamedDefinition } from '../utils/openapi'; import { COMPONENT_REGEXP, buildComponentComment } from './MarkdownRenderer'; import { RedocNormalizedOptions } from './RedocNormalizedOptions'; -import { appendToMdHeading } from '../utils/index'; +import { appendToMdHeading } from '../utils/'; export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] }; diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index a41a5f8f93..e22437b818 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -3,6 +3,7 @@ import { join as joinPaths } from 'path'; import { parse as urlParse } from 'url'; import { IMenuItem } from '../MenuStore'; +import { SecurityRequirementModel } from './SecurityRequirement'; import { GroupModel } from './Group.model'; import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types'; @@ -16,10 +17,6 @@ import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder'; import { JsonPointer, getOperationSummary, isAbsolutePath, stripTrailingSlash } from '../../utils'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; -function isNumeric(n) { - return !isNaN(parseFloat(n)) && isFinite(n); -} - /** * Operation model ready to be used by components */ @@ -50,6 +47,7 @@ export class OperationModel implements IMenuItem { responses: ResponseModel[]; path: string; servers: OpenAPIServer[]; + security: SecurityRequirementModel[]; codeSamples: CodeSample[]; constructor( @@ -101,6 +99,10 @@ export class OperationModel implements IMenuItem { parser.specUrl, operationSpec.servers || parser.spec.servers || [], ); + + this.security = (operationSpec.security || parser.spec.security || []).map( + security => new SecurityRequirementModel(security, parser), + ); } /** @@ -126,6 +128,10 @@ export class OperationModel implements IMenuItem { } } +function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} + function normalizeServers(specUrl: string, servers: OpenAPIServer[]): OpenAPIServer[] { if (servers.length === 0) { return [ diff --git a/src/services/models/SecurityRequirement.ts b/src/services/models/SecurityRequirement.ts new file mode 100644 index 0000000000..2e41f97ee6 --- /dev/null +++ b/src/services/models/SecurityRequirement.ts @@ -0,0 +1,27 @@ +import { OpenAPISecurityRequirement } from '../../types'; +import { OpenAPIParser } from '../OpenAPIParser'; +import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi'; + +export class SecurityRequirementModel { + schemes: { + id: string; + sectionId: string; + type: string; + scopes: string[]; + }[]; + + constructor(requirement: OpenAPISecurityRequirement, parser: OpenAPIParser) { + const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {}; + + this.schemes = Object.keys(requirement || {}).map(id => { + const scheme = parser.deref(schemes[id]); + const scopes = requirement[id] || []; + return { + id, + sectionId: SECURITY_SCHEMES_SECTION + id, + type: scheme.type, + scopes, + }; + }); + } +} diff --git a/src/services/models/SecuritySchemes.ts b/src/services/models/SecuritySchemes.ts index bd54ad532a..a90bab1622 100644 --- a/src/services/models/SecuritySchemes.ts +++ b/src/services/models/SecuritySchemes.ts @@ -1,8 +1,10 @@ import { OpenAPISecurityScheme, Referenced } from '../../types'; import { OpenAPIParser } from '../OpenAPIParser'; +import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi'; export class SecuritySchemeModel { id: string; + sectionId: string; type: OpenAPISecurityScheme['type']; description: string; apiKey?: { @@ -23,6 +25,7 @@ export class SecuritySchemeModel { constructor(parser: OpenAPIParser, id: string, scheme: Referenced) { const info = parser.deref(scheme); this.id = id; + this.sectionId = SECURITY_SCHEMES_SECTION + id; this.type = info.type; this.description = info.description || ''; if (info.type === 'apiKey') { @@ -54,7 +57,7 @@ export class SecuritySchemeModel { export class SecuritySchemesModel { schemes: SecuritySchemeModel[]; - constructor(public parser: OpenAPIParser) { + constructor(parser: OpenAPIParser) { const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {}; this.schemes = Object.keys(schemes).map( name => new SecuritySchemeModel(parser, name, schemes[name]), diff --git a/src/theme.ts b/src/theme.ts index 512b09da8a..d379b395c7 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -21,6 +21,7 @@ const theme = { }, schemaView: { linesColor: '#7f99cf', + defaultDetailsWidth: '75%', }, baseFont: { size: '14px', @@ -35,7 +36,7 @@ const theme = { }, code: { fontSize: '13px', - fontFamily: '"Lucida Console", Monaco, monospace', + fontFamily: 'Courirer, monospace', }, links: { color: undefined, // by default main color diff --git a/src/types/open-api.ts b/src/types/open-api.ts index 2e3e7feabd..0355519bd4 100644 --- a/src/types/open-api.ts +++ b/src/types/open-api.ts @@ -200,7 +200,9 @@ export type OpenAPIComponents = { callbacks?: { [name: string]: Referenced }; }; -export type OpenAPISecurityRequirement = {}; +export type OpenAPISecurityRequirement = { + [name: string]: string[]; +}; export type OpenAPISecurityScheme = { type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect'; diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 2d7820a9af..34cfc71cab 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -161,3 +161,5 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] { return res; } + +export const SECURITY_SCHEMES_SECTION = 'section/Authentication/';