Skip to content

Commit 50e2a58

Browse files
committed
feat: initial display security requirements
1 parent 9be4071 commit 50e2a58

File tree

11 files changed

+131
-13
lines changed

11 files changed

+131
-13
lines changed

src/common-elements/fields-layout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const PropertyNameCell = PropertyCell.extend`
7373
export const PropertyDetailsCell = styled.td`
7474
border-bottom: 1px solid #9fb4be;
7575
padding: 10px 0;
76-
width: 75%;
76+
width: ${props => props.theme.schemaView.defaultDetailsWidth};
7777
box-sizing: border-box;
7878
7979
tr.expanded & {

src/components/Operation/Operation.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as React from 'react';
22
import styled from '../../styled-components';
3+
import { SecurityRequirements } from '../SecurityRequirement/SecuirityRequirement';
34

45
import { observer } from 'mobx-react';
56

6-
import { H2, MiddlePanel, DarkRightPanel, Badge, Row } from '../../common-elements';
7+
import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements';
78

89
import { ComponentWithOptions } from '../OptionsProvider';
910

@@ -53,6 +54,7 @@ export class Operation extends ComponentWithOptions<OperationProps> {
5354
</H2>
5455
{pathInMiddle && <Endpoint operation={operation} inverted={true} />}
5556
{description !== undefined && <Markdown source={description} />}
57+
<SecurityRequirements securities={operation.security} />
5658
<Parameters parameters={operation.parameters} body={operation.requestBody} />
5759
<ResponsesList responses={operation.responses} />
5860
</MiddlePanel>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as React from 'react';
2+
import styled from '../../styled-components';
3+
import { transparentizeHex } from '../../utils/styled';
4+
5+
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
6+
import { UnderlinedHeader } from '../../common-elements/headers';
7+
8+
const ScopeName = styled.code`
9+
font-size: ${props => props.theme.code.fontSize};
10+
font-family: ${props => props.theme.code.fontFamily};
11+
border: 1px solid ${props => transparentizeHex(props.theme.colors.text, 0.15)};
12+
margin: 0 3px;
13+
padding: 0.2em;
14+
display: inline-block;
15+
line-height: 1;
16+
`;
17+
18+
export interface SecurityRequirementProps {
19+
security: SecurityRequirementModel;
20+
}
21+
22+
export class SecurityRequirement extends React.PureComponent<SecurityRequirementProps> {
23+
render() {
24+
const security = this.props.security;
25+
return security.schemes.map((scheme, idx) => {
26+
return (
27+
<div key={scheme.id}>
28+
<a href={'#' + scheme.sectionId}>{scheme.id}</a>
29+
{scheme.scopes.length > 0 && ' ('}
30+
{scheme.scopes.map(scope => <ScopeName key={scope}>{scope}</ScopeName>)}
31+
{scheme.scopes.length > 0 && ') '}
32+
{idx < security.schemes.length - 1 && ' and '}
33+
</div>
34+
);
35+
});
36+
}
37+
}
38+
39+
const AuthHeaderColumn = styled.div`
40+
display: inline-block;
41+
width: calc(100% - ${props => props.theme.schemaView.defaultDetailsWidth});
42+
`;
43+
44+
const SecuritiesColumn = styled.div`
45+
width: ${props => props.theme.schemaView.defaultDetailsWidth};
46+
display: inline-block;
47+
`;
48+
49+
const AuthHeader = styled(UnderlinedHeader)`
50+
display: inline-block;
51+
`;
52+
53+
export interface SecurityRequirementsProps {
54+
securities: SecurityRequirementModel[];
55+
}
56+
57+
export class SecurityRequirements extends React.PureComponent<SecurityRequirementsProps> {
58+
render() {
59+
const securities = this.props.securities;
60+
if (!securities.length) return null;
61+
return (
62+
<div>
63+
<AuthHeaderColumn>
64+
<AuthHeader>Authorizations: </AuthHeader>
65+
</AuthHeaderColumn>
66+
<SecuritiesColumn>
67+
{securities.map((security, idx) => <SecurityRequirement key={idx} security={security} />)}
68+
</SecuritiesColumn>
69+
</div>
70+
);
71+
}
72+
}

src/components/SecuritySchemes/SecuritySchemes.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react';
33
import { SecuritySchemesModel } from '../../services/models';
44

55
import styled from '../../styled-components';
6-
import { H2 } from '../../common-elements';
6+
import { H2, ShareLink } from '../../common-elements';
77
import { Markdown } from '../Markdown/Markdown';
88
import { OpenAPISecurityScheme } from '../../types';
99

@@ -81,8 +81,11 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
8181
return (
8282
<div>
8383
{this.props.securitySchemes.schemes.map(scheme => (
84-
<div key={scheme.id}>
85-
<H2>{scheme.id}</H2>
84+
<div data-section-id={scheme.sectionId} key={scheme.id}>
85+
<H2>
86+
<ShareLink href={'#' + scheme.sectionId} />
87+
{scheme.id}
88+
</H2>
8689
<Markdown source={scheme.description || ''} />
8790
<AuthTable className="security-details">
8891
<tbody>

src/services/OpenAPIParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { JsonPointer } from '../utils/JsonPointer';
77
import { isNamedDefinition } from '../utils/openapi';
88
import { COMPONENT_REGEXP, buildComponentComment } from './MarkdownRenderer';
99
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
10-
import { appendToMdHeading } from '../utils/index';
10+
import { appendToMdHeading } from '../utils/';
1111

1212
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
1313

src/services/models/Operation.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { join as joinPaths } from 'path';
33
import { parse as urlParse } from 'url';
44

55
import { IMenuItem } from '../MenuStore';
6+
import { SecurityRequirementModel } from './SecurityRequirement';
67
import { GroupModel } from './Group.model';
78

89
import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types';
@@ -16,10 +17,6 @@ import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
1617
import { JsonPointer, getOperationSummary, isAbsolutePath, stripTrailingSlash } from '../../utils';
1718
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
1819

19-
function isNumeric(n) {
20-
return !isNaN(parseFloat(n)) && isFinite(n);
21-
}
22-
2320
/**
2421
* Operation model ready to be used by components
2522
*/
@@ -50,6 +47,7 @@ export class OperationModel implements IMenuItem {
5047
responses: ResponseModel[];
5148
path: string;
5249
servers: OpenAPIServer[];
50+
security: SecurityRequirementModel[];
5351
codeSamples: CodeSample[];
5452

5553
constructor(
@@ -101,6 +99,10 @@ export class OperationModel implements IMenuItem {
10199
parser.specUrl,
102100
operationSpec.servers || parser.spec.servers || [],
103101
);
102+
103+
this.security = (operationSpec.security || parser.spec.security || []).map(
104+
security => new SecurityRequirementModel(security, parser),
105+
);
104106
}
105107

106108
/**
@@ -126,6 +128,10 @@ export class OperationModel implements IMenuItem {
126128
}
127129
}
128130

131+
function isNumeric(n) {
132+
return !isNaN(parseFloat(n)) && isFinite(n);
133+
}
134+
129135
function normalizeServers(specUrl: string, servers: OpenAPIServer[]): OpenAPIServer[] {
130136
if (servers.length === 0) {
131137
return [
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { OpenAPISecurityRequirement } from '../../types';
2+
import { OpenAPIParser } from '../OpenAPIParser';
3+
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
4+
5+
export class SecurityRequirementModel {
6+
schemes: {
7+
id: string;
8+
sectionId: string;
9+
type: string;
10+
scopes: string[];
11+
}[];
12+
13+
constructor(requirement: OpenAPISecurityRequirement, parser: OpenAPIParser) {
14+
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
15+
16+
this.schemes = Object.keys(requirement || {}).map(id => {
17+
const scheme = parser.deref(schemes[id]);
18+
const scopes = requirement[id] || [];
19+
return {
20+
id,
21+
sectionId: SECURITY_SCHEMES_SECTION + id,
22+
type: scheme.type,
23+
scopes,
24+
};
25+
});
26+
}
27+
}

src/services/models/SecuritySchemes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { OpenAPISecurityScheme, Referenced } from '../../types';
22
import { OpenAPIParser } from '../OpenAPIParser';
3+
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
34

45
export class SecuritySchemeModel {
56
id: string;
7+
sectionId: string;
68
type: OpenAPISecurityScheme['type'];
79
description: string;
810
apiKey?: {
@@ -23,6 +25,7 @@ export class SecuritySchemeModel {
2325
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
2426
const info = parser.deref(scheme);
2527
this.id = id;
28+
this.sectionId = SECURITY_SCHEMES_SECTION + id;
2629
this.type = info.type;
2730
this.description = info.description || '';
2831
if (info.type === 'apiKey') {
@@ -54,7 +57,7 @@ export class SecuritySchemeModel {
5457
export class SecuritySchemesModel {
5558
schemes: SecuritySchemeModel[];
5659

57-
constructor(public parser: OpenAPIParser) {
60+
constructor(parser: OpenAPIParser) {
5861
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
5962
this.schemes = Object.keys(schemes).map(
6063
name => new SecuritySchemeModel(parser, name, schemes[name]),

src/theme.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const theme = {
2121
},
2222
schemaView: {
2323
linesColor: '#7f99cf',
24+
defaultDetailsWidth: '75%',
2425
},
2526
baseFont: {
2627
size: '14px',
@@ -35,7 +36,7 @@ const theme = {
3536
},
3637
code: {
3738
fontSize: '13px',
38-
fontFamily: '"Lucida Console", Monaco, monospace',
39+
fontFamily: 'Courirer, monospace',
3940
},
4041
links: {
4142
color: undefined, // by default main color

src/types/open-api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ export type OpenAPIComponents = {
200200
callbacks?: { [name: string]: Referenced<OpenAPICallback> };
201201
};
202202

203-
export type OpenAPISecurityRequirement = {};
203+
export type OpenAPISecurityRequirement = {
204+
[name: string]: string[];
205+
};
204206

205207
export type OpenAPISecurityScheme = {
206208
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';

0 commit comments

Comments
 (0)