mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-11 03:16:48 +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 = {
|
const headerFontSize = {
|
||||||
'1': '1.85714em',
|
'1': '1.85714em',
|
||||||
'2': '1.57143em',
|
'2': '1.57143em',
|
||||||
|
'3': '1.27em',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const headerCommonMixin = level => css`
|
export const headerCommonMixin = level => css`
|
||||||
|
@ -22,6 +23,11 @@ export const H2 = styled.h2`
|
||||||
color: black;
|
color: black;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const H3 = styled.h2`
|
||||||
|
${headerCommonMixin(3)};
|
||||||
|
color: black;
|
||||||
|
`;
|
||||||
|
|
||||||
export const UnderlinedHeader = styled.h5`
|
export const UnderlinedHeader = styled.h5`
|
||||||
border-bottom: 1px solid rgba(38, 50, 56, 0.3);
|
border-bottom: 1px solid rgba(38, 50, 56, 0.3);
|
||||||
margin: 1em 0 1em 0;
|
margin: 1em 0 1em 0;
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { AppStore } from '../../services/AppStore';
|
||||||
import { OpenAPIExternalDocumentation } from '../../types';
|
|
||||||
|
|
||||||
import { ApiInfoModel } from '../../services/models';
|
|
||||||
import { SecurityDefs } from '../SecurityDefs/SecurityDefs';
|
import { SecurityDefs } from '../SecurityDefs/SecurityDefs';
|
||||||
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { MiddlePanel, DarkRightPanel, Row } from '../../common-elements/';
|
import { MiddlePanel, DarkRightPanel, Row } from '../../common-elements/';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -18,14 +16,14 @@ import {
|
||||||
} from './styled.elements';
|
} from './styled.elements';
|
||||||
|
|
||||||
interface ApiInfoProps {
|
interface ApiInfoProps {
|
||||||
info: ApiInfoModel;
|
store: AppStore;
|
||||||
externalDocs?: OpenAPIExternalDocumentation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ApiInfo extends React.Component<ApiInfoProps> {
|
export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
render() {
|
render() {
|
||||||
const { info, externalDocs } = this.props;
|
const { store } = this.props;
|
||||||
|
const { info, externalDocs } = store.spec;
|
||||||
|
|
||||||
const downloadFilename = info.downloadFileName;
|
const downloadFilename = info.downloadFileName;
|
||||||
const downloadLink = info.downloadLink;
|
const downloadLink = info.downloadLink;
|
||||||
|
@ -100,7 +98,15 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
<Markdown
|
<Markdown
|
||||||
source={info.description || ''}
|
source={info.description || ''}
|
||||||
raw={false}
|
raw={false}
|
||||||
components={{ 'security-definitions': SecurityDefs }}
|
components={{
|
||||||
|
'security-definitions': {
|
||||||
|
component: SecurityDefs,
|
||||||
|
propsSelector: store => ({
|
||||||
|
securitySchemes: store!.spec.security,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
store={store}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</MiddlePanel>
|
</MiddlePanel>
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
import { MarkdownRenderer } from '../../services';
|
import { AppStore, MarkdownRenderer } from '../../services';
|
||||||
import { ComponentWithOptions } from '../OptionsProvider';
|
import { ComponentWithOptions } from '../OptionsProvider';
|
||||||
import * as DOMPurify from 'dompurify';
|
import * as DOMPurify from 'dompurify';
|
||||||
|
|
||||||
import { markdownCss } from './styles';
|
import { markdownCss } from './styles';
|
||||||
|
|
||||||
interface MarkdownProps {
|
export type MDComponent = {
|
||||||
|
component: React.ComponentClass;
|
||||||
|
propsSelector: (store?: AppStore) => any;
|
||||||
|
attrs?: object;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface MarkdownProps {
|
||||||
source: string;
|
source: string;
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
components?: { [name: string]: React.ComponentClass };
|
components?: Dict<MDComponent>;
|
||||||
sanitize?: boolean;
|
sanitize?: boolean;
|
||||||
|
store?: AppStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
||||||
|
@ -27,6 +34,12 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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;
|
let sanitize: (string) => string;
|
||||||
|
|
||||||
if (this.props.sanitize || this.options.untrustedSpec) {
|
if (this.props.sanitize || this.options.untrustedSpec) {
|
||||||
|
@ -36,7 +49,6 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderer = new MarkdownRenderer();
|
const renderer = new MarkdownRenderer();
|
||||||
const { source, raw, className, components, inline, dense } = this.props;
|
|
||||||
const parts = components
|
const parts = components
|
||||||
? renderer.renderMdWithComponents(source, components, raw)
|
? renderer.renderMdWithComponents(source, components, raw)
|
||||||
: [renderer.renderMd(source, raw)];
|
: [renderer.renderMd(source, raw)];
|
||||||
|
@ -63,7 +75,7 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
|
||||||
typeof part === 'string' ? (
|
typeof part === 'string' ? (
|
||||||
<div key={idx} dangerouslySetInnerHTML={{ __html: sanitize(part) }} />
|
<div key={idx} dangerouslySetInnerHTML={{ __html: sanitize(part) }} />
|
||||||
) : (
|
) : (
|
||||||
<part.component key={idx} {...part.attrs} />
|
<part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -88,8 +88,8 @@ export const markdownCss = css`
|
||||||
word-break: keep-all;
|
word-break: keep-all;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
margin-top: 0.5em;
|
margin-top: 1.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr {
|
table tr {
|
||||||
|
|
|
@ -27,6 +27,7 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { store: { spec, menu, options } } = this.props;
|
const { store: { spec, menu, options } } = this.props;
|
||||||
|
const store = this.props.store;
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={options.theme}>
|
<ThemeProvider theme={options.theme}>
|
||||||
<OptionsProvider options={options}>
|
<OptionsProvider options={options}>
|
||||||
|
@ -36,7 +37,7 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
<SideMenu menu={menu} />
|
<SideMenu menu={menu} />
|
||||||
</StickySidebar>
|
</StickySidebar>
|
||||||
<ApiContent className="api-content">
|
<ApiContent className="api-content">
|
||||||
<ApiInfo info={spec.info} externalDocs={spec.externalDocs} />
|
<ApiInfo store={store} />
|
||||||
<ContentItems items={menu.items as any} />
|
<ContentItems items={menu.items as any} />
|
||||||
</ApiContent>
|
</ApiContent>
|
||||||
</RedocWrap>
|
</RedocWrap>
|
||||||
|
|
|
@ -1,7 +1,126 @@
|
||||||
import * as React from 'react';
|
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() {
|
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 * as Remarkable from 'remarkable';
|
||||||
|
|
||||||
|
import { MDComponent } from '../components/Markdown/Markdown';
|
||||||
import { IMenuItem, SECTION_ATTR } from './MenuStore';
|
import { IMenuItem, SECTION_ATTR } from './MenuStore';
|
||||||
import { GroupModel } from './models';
|
import { GroupModel } from './models';
|
||||||
import { highlight } from '../utils';
|
import { highlight } from '../utils';
|
||||||
|
@ -145,9 +147,9 @@ export class MarkdownRenderer {
|
||||||
|
|
||||||
renderMdWithComponents(
|
renderMdWithComponents(
|
||||||
rawText: string,
|
rawText: string,
|
||||||
components: { [name: string]: React.ComponentClass },
|
components: Dict<MDComponent>,
|
||||||
raw: boolean = true,
|
raw: boolean = true,
|
||||||
): (string | { component: React.ComponentClass; attrs: any })[] {
|
): (string | MDComponent)[] {
|
||||||
let componentDefs: string[] = [];
|
let componentDefs: string[] = [];
|
||||||
let match;
|
let match;
|
||||||
let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi');
|
let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi');
|
||||||
|
@ -165,8 +167,9 @@ export class MarkdownRenderer {
|
||||||
}
|
}
|
||||||
if (componentDefs[i]) {
|
if (componentDefs[i]) {
|
||||||
const { componentName, attrs } = parseComponent(componentDefs[i]);
|
const { componentName, attrs } = parseComponent(componentDefs[i]);
|
||||||
|
if (!componentName) continue;
|
||||||
res.push({
|
res.push({
|
||||||
component: componentName && components[componentName],
|
...components[componentName],
|
||||||
attrs: attrs,
|
attrs: attrs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { MenuBuilder } from './MenuBuilder';
|
||||||
import { OpenAPIParser } from './OpenAPIParser';
|
import { OpenAPIParser } from './OpenAPIParser';
|
||||||
import { ApiInfoModel } from './models/ApiInfo';
|
import { ApiInfoModel } from './models/ApiInfo';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||||
/**
|
/**
|
||||||
* Store that containts all the specification related information in the form of tree
|
* Store that containts all the specification related information in the form of tree
|
||||||
*/
|
*/
|
||||||
|
@ -39,7 +39,7 @@ export class SpecStore {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get security() {
|
get security() {
|
||||||
// TODO: implement security
|
const schemes = this.parser.spec.components && this.parser.spec.components.securitySchemes;
|
||||||
throw new Error('Not implemented');
|
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 './Field';
|
||||||
export * from './ApiInfo';
|
export * from './ApiInfo';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
export * from './SecuritySchemes';
|
||||||
|
|
|
@ -201,7 +201,38 @@ export type OpenAPIComponents = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenAPISecurityRequirement = {};
|
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 = {
|
export type OpenAPITag = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user