Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(document-builder): improved types of security scheme methods #2068

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion e2e/fastify.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('Fastify Swagger', () => {
.addOAuth2()
.addApiKey()
.addApiKey({ type: 'apiKey' }, 'key1')
.addApiKey({ type: 'apiKey' }, 'key2')
.addApiKey({}, 'key2')
.addApiKey('key3', { type: 'apiKey' })
.addCookieAuth()
.addSecurityRequirements('bearer')
.addSecurityRequirements({ basic: [], cookie: [] })
Expand Down
4 changes: 2 additions & 2 deletions e2e/validate-schema.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ describe('Validate OpenAPI schema', () => {
.addBearerAuth()
.addOAuth2()
.addApiKey()
.addApiKey({ type: 'apiKey' }, 'key1')
.addApiKey({ type: 'apiKey' }, 'key2')
.addApiKey({}, 'key1')
.addApiKey({}, 'key2')
.addCookieAuth()
.addSecurityRequirements('bearer')
.addSecurityRequirements({ basic: [], cookie: [] })
Expand Down
154 changes: 112 additions & 42 deletions lib/document-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import { clone, isString, isUndefined, negate, pickBy } from 'lodash';
import { buildDocumentBase } from './fixtures/document.base';
import { OpenAPIObject } from './interfaces';
import {
ApiKeySchemeObject,
HttpSchemeObject,
OAuth2SchemeObject,
SecuritySchemeObject as SSObject,
ExternalDocumentationObject,
ParameterObject,
SecurityRequirementObject,
SecuritySchemeObject,
ServerVariableObject,
TagObject
} from './interfaces/open-api-spec.interface';
import { GlobalParametersStorage } from './storages/global-parameters.storage';

type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

type OptSObject<T extends Partial<SSObject>> = Partial<Optional<T, 'type'>>;

export class DocumentBuilder {
private readonly logger = new Logger(DocumentBuilder.name);
private readonly document: Omit<OpenAPIObject, 'paths'> = buildDocumentBase();
Expand Down Expand Up @@ -97,7 +104,7 @@ export class DocumentBuilder {
return this;
}

public addSecurity(name: string, options: SecuritySchemeObject): this {
public addSecurity(name: string, options: SSObject): this {
this.document.components.securitySchemes = {
...(this.document.components.securitySchemes || {}),
[name]: options
Expand Down Expand Up @@ -128,75 +135,138 @@ export class DocumentBuilder {
return this;
}

public addBearerAuth(
options: SecuritySchemeObject = {
type: 'http'
},
name = 'bearer'
public addBearerAuth<SO extends SSObject = HttpSchemeObject>(
name?: string,
options?: OptSObject<SO>
): this;
/**
* @deprecated Use `addBearerAuth(name, options)` instead
*/
public addBearerAuth<SO extends SSObject = HttpSchemeObject>(
options?: OptSObject<SO>,
name?: string
): this;
public addBearerAuth<SO extends SSObject = HttpSchemeObject>(
nameOrOptions: string | OptSObject<SO> = 'bearer',
optionsOrName: string | OptSObject<SO> = {}
): this {
this.addSecurity(name, {
if (typeof nameOrOptions === 'object') {
[nameOrOptions, optionsOrName] = [optionsOrName, nameOrOptions];
}
this.addSecurity(nameOrOptions as string, {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
...options
...(optionsOrName as OptSObject<SO>)
});
return this;
}

public addOAuth2(
options: SecuritySchemeObject = {
type: 'oauth2'
},
name = 'oauth2'
public addOAuth2<SO extends SSObject = OAuth2SchemeObject>(
name?: string,
options?: OptSObject<SO>
): this;
/**
* @deprecated Use `addOAuth2(name, options)` instead
*/
public addOAuth2<SO extends SSObject = OAuth2SchemeObject>(
options?: OptSObject<SO>,
name?: string
): this;
public addOAuth2<SO extends SSObject = OAuth2SchemeObject>(
nameOrOptions: string | OptSObject<SO> = 'oauth2',
optionsOrName: string | OptSObject<SO> = {}
): this {
this.addSecurity(name, {
if (typeof nameOrOptions === 'object') {
[nameOrOptions, optionsOrName] = [optionsOrName, nameOrOptions];
}
this.addSecurity(nameOrOptions as string, {
type: 'oauth2',
flows: {},
...options
...(optionsOrName as OptSObject<SO>)
});
return this;
}

public addApiKey(
options: SecuritySchemeObject = {
type: 'apiKey'
},
name = 'api_key'
public addBasicAuth<SO extends SSObject = HttpSchemeObject>(
name?: string,
options?: OptSObject<SO>
): this;
/**
* @deprecated Use `addBasicAuth(name, options)` instead
*/
public addBasicAuth<SO extends SSObject = HttpSchemeObject>(
options?: OptSObject<SO>,
name?: string
): this;
public addBasicAuth<SO extends SSObject = HttpSchemeObject>(
nameOrOptions: string | OptSObject<SO> = 'basic',
optionsOrName: string | OptSObject<SO> = {}
): this {
this.addSecurity(name, {
type: 'apiKey',
in: 'header',
name,
...options
if (typeof nameOrOptions === 'object') {
[nameOrOptions, optionsOrName] = [optionsOrName, nameOrOptions];
}
this.addSecurity(nameOrOptions as string, {
type: 'http',
scheme: 'basic',
...(optionsOrName as OptSObject<SO>)
});
return this;
}

public addBasicAuth(
options: SecuritySchemeObject = {
type: 'http'
},
name = 'basic'
public addApiKey<SO extends SSObject = ApiKeySchemeObject>(
name?: string,
options?: OptSObject<SO>
): this;
/**
* @deprecated Use `addApiKey(name, options)` instead
*/
public addApiKey<SO extends SSObject = ApiKeySchemeObject>(
options?: OptSObject<SO>,
name?: string
): this;
public addApiKey<SO extends SSObject = ApiKeySchemeObject>(
nameOrOptions: string | OptSObject<SO> = 'api_key',
optionsOrName: string | OptSObject<SO> = {}
): this {
this.addSecurity(name, {
type: 'http',
scheme: 'basic',
...options
if (typeof nameOrOptions === 'object') {
[nameOrOptions, optionsOrName] = [optionsOrName, nameOrOptions];
}
this.addSecurity(nameOrOptions as string, {
type: 'apiKey',
in: 'header',
name: nameOrOptions as string,
...(optionsOrName as OptSObject<SO>)
});
return this;
}

public addCookieAuth(
public addCookieAuth<SO extends SSObject = ApiKeySchemeObject>(
cookieName?: string,
name?: string,
options?: OptSObject<ApiKeySchemeObject>
): this;
/**
* @deprecated Use `addCookieAuth(cookieName, name, options)` instead
*/
public addCookieAuth<SO extends SSObject = ApiKeySchemeObject>(
cookieName: string,
options?: OptSObject<ApiKeySchemeObject>,
name?: string
): this;
public addCookieAuth<SO extends SSObject = ApiKeySchemeObject>(
cookieName = 'connect.sid',
options: SecuritySchemeObject = {
type: 'apiKey'
},
securityName = 'cookie'
nameOrOptions: string | OptSObject<ApiKeySchemeObject> = 'cookie',
optionsOrName: string | OptSObject<ApiKeySchemeObject> = {}
): this {
this.addSecurity(securityName, {
if (typeof nameOrOptions === 'object') {
[nameOrOptions, optionsOrName] = [optionsOrName, nameOrOptions];
}
this.addSecurity(nameOrOptions as string, {
type: 'apiKey',
in: 'cookie',
name: cookieName,
...options
...(optionsOrName as OptSObject<SO>)
});
return this;
}
Expand Down
36 changes: 29 additions & 7 deletions lib/interfaces/open-api-spec.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,19 +250,41 @@ export interface XmlObject {
}

export type SecuritySchemeType = 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
export type ApiKeyLocation = 'query' | 'header' | 'cookie';

export interface SecuritySchemeObject {
type: SecuritySchemeType;
export type SecuritySchemeObject =
| ApiKeySchemeObject
| HttpSchemeObject
| OAuth2SchemeObject
| OpenIdConnectSchemeObject;

export interface ApiKeySchemeObject {
type: 'apiKey';
description?: string;
name?: string;
in?: string;
scheme?: string;
name: string;
in: ApiKeyLocation;
}

export interface HttpSchemeObject {
type: 'http';
description?: string;
scheme: string;
bearerFormat?: string;
flows?: OAuthFlowsObject;
openIdConnectUrl?: string;
}

export interface OAuth2SchemeObject {
type: 'oauth2';
description?: string;
flows: OAuthFlowsObject;
'x-tokenName'?: string;
}

export interface OpenIdConnectSchemeObject {
type: 'openIdConnect';
description?: string;
openIdConnectUrl: string;
}

export interface OAuthFlowsObject {
implicit?: OAuthFlowObject;
password?: OAuthFlowObject;
Expand Down