diff --git a/src/generator/paths.ts b/src/generator/paths.ts index 2d705608..5fa52a59 100644 --- a/src/generator/paths.ts +++ b/src/generator/paths.ts @@ -56,6 +56,23 @@ export const getOpenApiPathsObject = ( const { inputParser, outputParser } = getInputOutputParsers(procedure); + let securityNames: string[] | undefined; + + if (protect) { + if (Array.isArray(protect)) { + const unexists = protect.filter((name) => !securitySchemeNames.includes(name)); + if (unexists.length) { + throw new TRPCError({ + message: `"${unexists.join(',')}" must exists in "securitySchemes"`, + code: 'INTERNAL_SERVER_ERROR', + }); + } + securityNames = protect; + } else { + securityNames = securitySchemeNames; + } + } + pathsObject[path] = { ...pathsObject[path], [httpMethod]: { @@ -63,7 +80,7 @@ export const getOpenApiPathsObject = ( summary, description, tags: tags, - security: protect ? securitySchemeNames.map((name) => ({ [name]: [] })) : undefined, + security: securityNames?.map((name) => ({ [name]: [] })), ...(acceptsRequestBody(method) ? { requestBody: getRequestBodyObject( diff --git a/src/types.ts b/src/types.ts index 71cec3ee..f832e744 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,7 @@ export type OpenApiMeta = TMeta & { path: `/${string}`; summary?: string; description?: string; - protect?: boolean; + protect?: boolean | string[]; tags?: string[]; headers?: (OpenAPIV3.ParameterBaseObject & { name: string; in?: 'header' })[]; contentTypes?: OpenApiContentType[]; diff --git a/test/generator.test.ts b/test/generator.test.ts index c2a13c77..b82d0f94 100644 --- a/test/generator.test.ts +++ b/test/generator.test.ts @@ -849,6 +849,54 @@ describe('generator', () => { ]); }); + test('with defined security', () => { + const appRouter = t.router({ + protectedEndpoint: t.procedure + .meta({ + openapi: { method: 'POST', path: '/secure/endpoint', protect: ['a', 'b'] }, + }) + .input(z.object({ name: z.string() })) + .output(z.object({ name: z.string() })) + .query(({ input }) => ({ name: input.name })), + }); + + const openApiDocument = generateOpenApiDocument(appRouter, { + ...defaultDocOpts, + securitySchemes: { + a: { + type: 'apiKey', + name: 'a', + in: 'header', + }, + b: { + type: 'apiKey', + name: 'b', + in: 'header', + }, + }, + }); + + expect(openApiSchemaValidator.validate(openApiDocument).errors).toEqual([]); + expect(openApiDocument.paths['/secure/endpoint']!.post!.security).toEqual([ + { a: [] }, + { b: [] }, + ]); + }); + + test('with missing securityScheme', () => { + const appRouter = t.router({ + protectedEndpoint: t.procedure + .meta({ openapi: { method: 'POST', path: '/secure/endpoint', protect: ['a', 'b'] } }) + .input(z.object({ name: z.string() })) + .output(z.object({ name: z.string() })) + .query(({ input }) => ({ name: input.name })), + }); + + expect(() => { + generateOpenApiDocument(appRouter, defaultDocOpts); + }).toThrowError('[query.protectedEndpoint] - "a,b" must exists in "securitySchemes"'); + }); + test('with schema descriptions', () => { const appRouter = t.router({ createUser: t.procedure