Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ app.run(); // Open http://localhost:3030/api/docs
| `enabled` | `true` | Convenience flag—skip calling `registerRoutes` if you want to hide docs. |
| `path` | `'/api/docs'` | Mount path for Swagger UI; value is used as-is. |
| `swaggerJsonPath` | `undefined` | Path relative to mount path where OpenAPI schema is served as JSON. When set, Swagger UI loads the schema from this endpoint instead of embedding it directly. |
| `authPolicy` | `AuthPolicy.disabled` | Controls authentication for the Swagger UI page itself. |
| `securitySchemes` | `undefined` | OpenAPI Security Schemes |
Comment thread
sjorobekov marked this conversation as resolved.

Usage example:

Expand All @@ -94,6 +96,13 @@ const {registerRoutes} = createOpenApiRegistry({
explorer: true,
customCss: '.topbar { display: none; }',
},
securitySchemes: {
myApiKey: {
type: 'apiKey',
in: 'header',
name: 'X-API-Key',
},
},
});
```

Expand Down
7 changes: 5 additions & 2 deletions src/openapi-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {NodeKit} from '@gravity-ui/nodekit';
* @returns An object with methods to register routes, security schemes, and generate the OpenAPI schema
*/
export function createOpenApiRegistry(config: OpenApiRegistryConfig) {
const initialSecuritySchemes = {...(config.securitySchemes || {})};

const openApiSchema: OpenApiSchemaObject = {
openapi: '3.0.3',
info: {
Expand All @@ -43,7 +45,7 @@ export function createOpenApiRegistry(config: OpenApiRegistryConfig) {
paths: {},
components: {
schemas: {},
securitySchemes: {},
securitySchemes: {...initialSecuritySchemes},
},
};

Expand Down Expand Up @@ -272,7 +274,7 @@ export function createOpenApiRegistry(config: OpenApiRegistryConfig) {
openApiSchema.paths = {};
if (openApiSchema.components) {
openApiSchema.components.schemas = {};
openApiSchema.components.securitySchemes = {};
openApiSchema.components.securitySchemes = {...initialSecuritySchemes};
}
}

Expand Down Expand Up @@ -403,6 +405,7 @@ export function createOpenApiRegistry(config: OpenApiRegistryConfig) {
return {
...routes,
[`MOUNT ${mountPath}`]: {
authPolicy: config.authPolicy ?? AuthPolicy.disabled,
handler: ({router}: Parameters<AppMountHandler>[0]) => {
const schema = getOpenApiSchema();

Expand Down
64 changes: 62 additions & 2 deletions src/tests/openapi-registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import {createOpenApiRegistry} from '../openapi-registry';
import {apiKeyAuth, bearerAuth} from '../security-schemas';
import {AppRoutes, AuthPolicy, RouteContract, withContract} from '@gravity-ui/expresskit';
import {
AppMountDescription,
AppRoutes,
AuthPolicy,
RouteContract,
withContract,
} from '@gravity-ui/expresskit';
import {NodeKit} from '@gravity-ui/nodekit';
import {z} from 'zod';

Expand Down Expand Up @@ -41,6 +47,13 @@ describe('openapi-registry', () => {
{url: 'https://staging.example.com', description: 'Staging'},
],
path: '/docs',
securitySchemes: {
customApiKey: {
type: 'apiKey' as const,
in: 'header' as const,
name: 'X-Custom-Key',
},
},
};

const {getOpenApiSchema} = createOpenApiRegistry(config);
Expand All @@ -52,6 +65,13 @@ describe('openapi-registry', () => {
expect(schema.info.contact).toEqual(config.contact);
expect(schema.info.license).toEqual(config.license);
expect(schema.servers).toEqual(config.servers);
expect(schema.components?.securitySchemes).toEqual({
customApiKey: {
type: 'apiKey',
in: 'header',
name: 'X-Custom-Key',
},
});
});

it('should create registry with swaggerUi options', () => {
Expand Down Expand Up @@ -669,6 +689,33 @@ describe('openapi-registry', () => {

const registeredRoutes = registerRoutes(routes, nodekit);
expect(registeredRoutes).toHaveProperty('MOUNT /api/docs');
const mountRoute = registeredRoutes['MOUNT /api/docs'] as AppMountDescription;
expect(mountRoute).toBeDefined();
expect(mountRoute.authPolicy).toBe(AuthPolicy.disabled);
});

it('should apply configured authPolicy to MOUNT route', () => {
const {registerRoutes} = createOpenApiRegistry({
title: 'Test API',
authPolicy: AuthPolicy.required,
});

const routes = {
'GET /test': {
handler: withContract({
request: {},
response: {content: {200: z.object({})}},
})(async (_req, res) => {
res.sendTyped(200, {});
}),
},
};

const registeredRoutes = registerRoutes(routes, nodekit);
const mountRoute = registeredRoutes['MOUNT /api/docs'] as AppMountDescription;

expect(mountRoute).toBeDefined();
expect(mountRoute.authPolicy).toBe(AuthPolicy.required);
});

it('should handle routes with tags and description', () => {
Expand Down Expand Up @@ -757,6 +804,13 @@ describe('openapi-registry', () => {
it('should reset paths and components', () => {
const {registerRoutes, getOpenApiSchema, reset} = createOpenApiRegistry({
title: 'Test API',
securitySchemes: {
initialKey: {
type: 'apiKey',
in: 'header',
name: 'X-Initial-Key',
},
},
});

const handler = withContract({
Expand All @@ -781,7 +835,13 @@ describe('openapi-registry', () => {

expect(schema.paths).toEqual({});
expect(schema.components?.schemas).toEqual({});
expect(schema.components?.securitySchemes).toEqual({});
expect(schema.components?.securitySchemes).toEqual({
initialKey: {
type: 'apiKey',
in: 'header',
name: 'X-Initial-Key',
},
});
});
Comment thread
sjorobekov marked this conversation as resolved.
});

Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {SwaggerUiOptions} from 'swagger-ui-express';
import type {AppRouteDescription} from '@gravity-ui/expresskit';
import type {AppRouteDescription, AuthPolicy} from '@gravity-ui/expresskit';

// OpenAPI Security Scheme Object types
export interface SecuritySchemeObject {
Expand Down Expand Up @@ -64,6 +64,8 @@ export interface OpenApiRegistryConfig {
}[];
swaggerUi?: SwaggerUiOptions;
swaggerJsonPath?: string;
authPolicy?: AuthPolicy;
securitySchemes?: Record<string, SecuritySchemeObject>;
transformOperation?: (
operation: OpenApiOperation,
context: {
Expand Down
Loading