Skip to content

Commit

Permalink
feat: impl path security
Browse files Browse the repository at this point in the history
  • Loading branch information
JuMastro committed Aug 8, 2023
1 parent d1eab87 commit a2f9551
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 14 deletions.
25 changes: 23 additions & 2 deletions src/generator/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,31 @@ 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]: {
operationId: procedurePath.replace(/\./g, '-'),
summary,
description,
tags: tags,
security: protect ? securitySchemeNames.map((name) => ({ [name]: [] })) : undefined,
security: securityNames?.map((name) => ({ [name]: [] })),
...(acceptsRequestBody(method)
? {
requestBody: getRequestBodyObject(
Expand Down Expand Up @@ -94,7 +111,11 @@ export const getOpenApiPathsObject = (
) || []),
],
}),
responses: getResponsesObject(outputParser, openapi.example?.response, openapi.responseHeaders),
responses: getResponsesObject(
outputParser,
openapi.example?.response,
openapi.responseHeaders,
),
...(openapi.deprecated ? { deprecated: openapi.deprecated } : {}),
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type OpenApiMeta<TMeta = TRPCMeta> = TMeta & {
path: `/${string}`;
summary?: string;
description?: string;
protect?: boolean;
protect?: boolean | string[];
tags?: string[];
headers?: (OpenAPIV3.ParameterBaseObject & { name: string; in?: 'header' })[];
contentTypes?: OpenApiContentType[];
Expand Down
70 changes: 59 additions & 11 deletions test/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2807,26 +2855,26 @@ describe('generator', () => {
method: 'GET',
path: '/query-example/{name}',
responseHeaders: {
"X-RateLimit-Limit": {
description: "Request limit per hour.",
'X-RateLimit-Limit': {
description: 'Request limit per hour.',
schema: {
type: "integer"
}
type: 'integer',
},
},
"X-RateLimit-Remaining": {
description: "The number of requests left for the time window.",
'X-RateLimit-Remaining': {
description: 'The number of requests left for the time window.',
schema: {
type: "integer"
}
}
}
type: 'integer',
},
},
},
},
})
.input(z.object({ name: z.string(), greeting: z.string() }))
.output(z.object({ output: z.string() }))
.query(({ input }) => ({
output: `${input.greeting} ${input.name}`,
}))
})),
});

const openApiDocument = generateOpenApiDocument(appRouter, defaultDocOpts);
Expand Down

0 comments on commit a2f9551

Please sign in to comment.