mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-23 00:56:33 +03:00
Security Definitions component
This commit is contained in:
parent
ed13bb0528
commit
cec37df7aa
|
@ -3,6 +3,7 @@ import styled, { css } from '../styled-components';
|
|||
const headerFontSize = {
|
||||
'1': '1.85714em',
|
||||
'2': '1.57143em',
|
||||
'3': '1.27em',
|
||||
};
|
||||
|
||||
export const headerCommonMixin = level => css`
|
||||
|
@ -22,6 +23,11 @@ export const H2 = styled.h2`
|
|||
color: black;
|
||||
`;
|
||||
|
||||
export const H3 = styled.h2`
|
||||
${headerCommonMixin(3)};
|
||||
color: black;
|
||||
`;
|
||||
|
||||
export const UnderlinedHeader = styled.h5`
|
||||
border-bottom: 1px solid rgba(38, 50, 56, 0.3);
|
||||
margin: 1em 0 1em 0;
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { OpenAPIExternalDocumentation } from '../../types';
|
||||
import { AppStore } from '../../services/AppStore';
|
||||
|
||||
import { ApiInfoModel } from '../../services/models';
|
||||
import { SecurityDefs } from '../SecurityDefs/SecurityDefs';
|
||||
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { MiddlePanel, DarkRightPanel, Row } from '../../common-elements/';
|
||||
|
||||
import {
|
||||
|
@ -18,14 +16,14 @@ import {
|
|||
} from './styled.elements';
|
||||
|
||||
interface ApiInfoProps {
|
||||
info: ApiInfoModel;
|
||||
externalDocs?: OpenAPIExternalDocumentation;
|
||||
store: AppStore;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||
render() {
|
||||
const { info, externalDocs } = this.props;
|
||||
const { store } = this.props;
|
||||
const { info, externalDocs } = store.spec;
|
||||
|
||||
const downloadFilename = info.downloadFileName;
|
||||
const downloadLink = info.downloadLink;
|
||||
|
@ -100,7 +98,15 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
<Markdown
|
||||
source={info.description || ''}
|
||||
raw={false}
|
||||
components={{ 'security-definitions': SecurityDefs }}
|
||||
components={{
|
||||
'security-definitions': {
|
||||
component: SecurityDefs,
|
||||
propsSelector: store => ({
|
||||
securitySchemes: store!.spec.security,
|
||||
}),
|
||||
},
|
||||
}}
|
||||
store={store}
|
||||
/>
|
||||
</div>
|
||||
</MiddlePanel>
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
import * as React from 'react';
|
||||
import styled from '../../styled-components';
|
||||
|
||||
import { MarkdownRenderer } from '../../services';
|
||||
import { AppStore, MarkdownRenderer } from '../../services';
|
||||
import { ComponentWithOptions } from '../OptionsProvider';
|
||||
import * as DOMPurify from 'dompurify';
|
||||
|
||||
import { markdownCss } from './styles';
|
||||
|
||||
interface MarkdownProps {
|
||||
export type MDComponent = {
|
||||
component: React.ComponentClass;
|
||||
propsSelector: (store?: AppStore) => any;
|
||||
attrs?: object;
|
||||
};
|
||||
|
||||
export interface MarkdownProps {
|
||||
source: string;
|
||||
dense?: boolean;
|
||||
inline?: boolean;
|
||||
className?: string;
|
||||
raw?: boolean;
|
||||
components?: { [name: string]: React.ComponentClass };
|
||||
components?: Dict<MDComponent>;
|
||||
sanitize?: boolean;
|
||||
store?: AppStore;
|
||||
}
|
||||
|
||||
class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
||||
|
@ -27,6 +34,12 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { source, raw, className, components, inline, dense, store } = this.props;
|
||||
|
||||
if (components && !store) {
|
||||
throw new Error('When using components with Markdwon in ReDoc, store prop must be provided');
|
||||
}
|
||||
|
||||
let sanitize: (string) => string;
|
||||
|
||||
if (this.props.sanitize || this.options.untrustedSpec) {
|
||||
|
@ -36,7 +49,6 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
|||
}
|
||||
|
||||
const renderer = new MarkdownRenderer();
|
||||
const { source, raw, className, components, inline, dense } = this.props;
|
||||
const parts = components
|
||||
? renderer.renderMdWithComponents(source, components, raw)
|
||||
: [renderer.renderMd(source, raw)];
|
||||
|
@ -63,7 +75,7 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
|||
typeof part === 'string' ? (
|
||||
<div key={idx} dangerouslySetInnerHTML={{ __html: sanitize(part) }} />
|
||||
) : (
|
||||
<part.component key={idx} {...part.attrs} />
|
||||
<part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -88,8 +88,8 @@ export const markdownCss = css`
|
|||
word-break: keep-all;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
table tr {
|
||||
|
|
|
@ -27,6 +27,7 @@ export class Redoc extends React.Component<RedocProps> {
|
|||
|
||||
render() {
|
||||
const { store: { spec, menu, options } } = this.props;
|
||||
const store = this.props.store;
|
||||
return (
|
||||
<ThemeProvider theme={options.theme}>
|
||||
<OptionsProvider options={options}>
|
||||
|
@ -36,7 +37,7 @@ export class Redoc extends React.Component<RedocProps> {
|
|||
<SideMenu menu={menu} />
|
||||
</StickySidebar>
|
||||
<ApiContent className="api-content">
|
||||
<ApiInfo info={spec.info} externalDocs={spec.externalDocs} />
|
||||
<ApiInfo store={store} />
|
||||
<ContentItems items={menu.items as any} />
|
||||
</ApiContent>
|
||||
</RedocWrap>
|
||||
|
|
|
@ -1,7 +1,126 @@
|
|||
import * as React from 'react';
|
||||
|
||||
export class SecurityDefs extends React.PureComponent {
|
||||
import { SecuritySchemesModel } from '../../services/models/';
|
||||
|
||||
import styled from '../../styled-components';
|
||||
import { H2 } from '../../common-elements';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { OpenAPISecurityScheme } from '../../types';
|
||||
|
||||
const AUTH_TYPES = {
|
||||
oauth2: 'OAuth2',
|
||||
apiKey: 'API Key',
|
||||
basic: 'Basic Authorization',
|
||||
openIdConnect: 'Open ID Connect',
|
||||
};
|
||||
|
||||
export interface OAuthFlowProps {
|
||||
type: string;
|
||||
flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']];
|
||||
}
|
||||
|
||||
const AuthTable = styled.table`
|
||||
ul > li {
|
||||
margin: 0.5em 0 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
|
||||
render() {
|
||||
return <h1> Security Definitions here </h1>;
|
||||
const { type, flow } = this.props;
|
||||
return (
|
||||
<tr>
|
||||
<th> {type} OAuth Flow </th>
|
||||
<td>
|
||||
{type === 'implicit' || type === 'authorizationCode' ? (
|
||||
<div>
|
||||
<strong> Authorization URL: </strong>
|
||||
{(flow as any).authorizationUrl}
|
||||
</div>
|
||||
) : null}
|
||||
{type === 'password' || type === 'clientCredentials' || type === 'authorizationCode' ? (
|
||||
<div>
|
||||
<strong> Token URL: </strong>
|
||||
{(flow as any).tokenUrl}
|
||||
</div>
|
||||
) : null}
|
||||
{flow!.refreshUrl && (
|
||||
<div>
|
||||
<strong> Refresh URL: </strong>
|
||||
{flow!.refreshUrl}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<strong> Scopes: </strong>
|
||||
</div>
|
||||
<ul>
|
||||
{Object.keys(flow!.scopes).map(scope => (
|
||||
<li>
|
||||
<code>{scope}</code> - {flow!.scopes[scope]}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface SecurityDefsProps {
|
||||
securitySchemes?: SecuritySchemesModel;
|
||||
}
|
||||
|
||||
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||
render() {
|
||||
if (!this.props.securitySchemes) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.securitySchemes.schemes.map(scheme => (
|
||||
<div key={scheme.id}>
|
||||
<H2>{scheme.id}</H2>
|
||||
<Markdown source={scheme.description || ''} />
|
||||
<AuthTable className="security-details">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> Security scheme type: </th>
|
||||
<td> {AUTH_TYPES[scheme.type]} </td>
|
||||
</tr>
|
||||
{scheme.apiKey ? (
|
||||
<tr>
|
||||
<th> {scheme.apiKey.in} parameter name:</th>
|
||||
<td> {scheme.apiKey.name} </td>
|
||||
</tr>
|
||||
) : scheme.http ? (
|
||||
[
|
||||
<tr>
|
||||
<th> HTTP Authorization Scheme </th>
|
||||
<th> {scheme.http.scheme} </th>
|
||||
</tr>,
|
||||
<tr>
|
||||
<th> Bearer format </th>
|
||||
<th> "{scheme.http.bearerFormat}" </th>
|
||||
</tr>,
|
||||
]
|
||||
) : scheme.openId ? (
|
||||
<tr>
|
||||
<th> Connect URL </th>
|
||||
<td>
|
||||
<a target="_blank" href={scheme.openId.connectUrl}>
|
||||
{scheme.openId.connectUrl}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
) : scheme.flows ? (
|
||||
Object.keys(scheme.flows).map(type => (
|
||||
<OAuthFlow key={type} type={type} flow={scheme.flows[type]} />
|
||||
))
|
||||
) : null}
|
||||
</tbody>
|
||||
</AuthTable>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import * as Remarkable from 'remarkable';
|
||||
|
||||
import { MDComponent } from '../components/Markdown/Markdown';
|
||||
import { IMenuItem, SECTION_ATTR } from './MenuStore';
|
||||
import { GroupModel } from './models';
|
||||
import { highlight } from '../utils';
|
||||
|
@ -145,9 +147,9 @@ export class MarkdownRenderer {
|
|||
|
||||
renderMdWithComponents(
|
||||
rawText: string,
|
||||
components: { [name: string]: React.ComponentClass },
|
||||
components: Dict<MDComponent>,
|
||||
raw: boolean = true,
|
||||
): (string | { component: React.ComponentClass; attrs: any })[] {
|
||||
): (string | MDComponent)[] {
|
||||
let componentDefs: string[] = [];
|
||||
let match;
|
||||
let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi');
|
||||
|
@ -165,8 +167,9 @@ export class MarkdownRenderer {
|
|||
}
|
||||
if (componentDefs[i]) {
|
||||
const { componentName, attrs } = parseComponent(componentDefs[i]);
|
||||
if (!componentName) continue;
|
||||
res.push({
|
||||
component: componentName && components[componentName],
|
||||
...components[componentName],
|
||||
attrs: attrs,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { MenuBuilder } from './MenuBuilder';
|
|||
import { OpenAPIParser } from './OpenAPIParser';
|
||||
import { ApiInfoModel } from './models/ApiInfo';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
||||
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||
/**
|
||||
* Store that containts all the specification related information in the form of tree
|
||||
*/
|
||||
|
@ -39,7 +39,7 @@ export class SpecStore {
|
|||
|
||||
@computed
|
||||
get security() {
|
||||
// TODO: implement security
|
||||
throw new Error('Not implemented');
|
||||
const schemes = this.parser.spec.components && this.parser.spec.components.securitySchemes;
|
||||
return schemes && new SecuritySchemesModel(this.parser);
|
||||
}
|
||||
}
|
||||
|
|
63
src/services/models/SecuritySchemes.ts
Normal file
63
src/services/models/SecuritySchemes.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { OpenAPISecurityScheme, Referenced } from '../../types';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
|
||||
export class SecuritySchemeModel {
|
||||
id: string;
|
||||
type: OpenAPISecurityScheme['type'];
|
||||
description: string;
|
||||
apiKey?: {
|
||||
name: string;
|
||||
in: OpenAPISecurityScheme['in'];
|
||||
};
|
||||
|
||||
http?: {
|
||||
scheme: string;
|
||||
bearerFormat?: string;
|
||||
};
|
||||
|
||||
flows: OpenAPISecurityScheme['flows'];
|
||||
openId?: {
|
||||
connectUrl: string;
|
||||
};
|
||||
|
||||
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
|
||||
const info = parser.deref(scheme);
|
||||
this.id = id;
|
||||
this.type = info.type;
|
||||
this.description = info.description || '';
|
||||
if (info.type === 'apiKey') {
|
||||
this.apiKey = {
|
||||
name: info.name!,
|
||||
in: info.in,
|
||||
};
|
||||
}
|
||||
|
||||
if (info.type === 'http') {
|
||||
this.http = {
|
||||
scheme: info.scheme!,
|
||||
bearerFormat: info.bearerFormat,
|
||||
};
|
||||
}
|
||||
|
||||
if (info.type === 'openIdConnect') {
|
||||
this.openId = {
|
||||
connectUrl: info.openIdConnectUrl!,
|
||||
};
|
||||
}
|
||||
|
||||
if (info.type === 'oauth2' && info.flows) {
|
||||
this.flows = info.flows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SecuritySchemesModel {
|
||||
schemes: SecuritySchemeModel[];
|
||||
|
||||
constructor(public parser: OpenAPIParser) {
|
||||
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
|
||||
this.schemes = Object.keys(schemes).map(
|
||||
name => new SecuritySchemeModel(parser, name, schemes[name]),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,3 +10,4 @@ export * from './Schema';
|
|||
export * from './Field';
|
||||
export * from './ApiInfo';
|
||||
export * from './types';
|
||||
export * from './SecuritySchemes';
|
||||
|
|
|
@ -201,7 +201,38 @@ export type OpenAPIComponents = {
|
|||
};
|
||||
|
||||
export type OpenAPISecurityRequirement = {};
|
||||
export type OpenAPISecurityScheme = {};
|
||||
|
||||
export type OpenAPISecurityScheme = {
|
||||
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
|
||||
description?: string;
|
||||
name?: string;
|
||||
in?: 'query' | 'header' | 'cookie';
|
||||
scheme?: string;
|
||||
bearerFormat: string;
|
||||
flows: {
|
||||
implicit?: {
|
||||
refreshUrl?: string;
|
||||
scopes: Dict<string>;
|
||||
authorizationUrl: string;
|
||||
};
|
||||
password?: {
|
||||
refreshUrl?: string;
|
||||
scopes: Dict<string>;
|
||||
tokenUrl: string;
|
||||
};
|
||||
clientCredentials?: {
|
||||
refreshUrl?: string;
|
||||
scopes: Dict<string>;
|
||||
tokenUrl: string;
|
||||
};
|
||||
authorizationCode?: {
|
||||
refreshUrl?: string;
|
||||
scopes: Dict<string>;
|
||||
tokenUrl: string;
|
||||
};
|
||||
};
|
||||
openIdConnectUrl?: string;
|
||||
};
|
||||
|
||||
export type OpenAPITag = {
|
||||
name: string;
|
||||
|
|
Loading…
Reference in New Issue
Block a user