Skip to content

Commit 86591f5

Browse files
authored
Fix/1791 validate error leak body (#1800)
* fix: race conditions * refactor(test): centralize integration utils methods * test: body value cannot appear in validate error * fix: prevent leak value in body validate error * chore: lint
1 parent 95f098e commit 86591f5

File tree

12 files changed

+491
-194
lines changed

12 files changed

+491
-194
lines changed

packages/runtime/src/routeGeneration/templates/express/expressTemplateService.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,22 @@ export class ExpressTemplateService extends TemplateService<ExpressApiHandlerPar
7070
return this.validationService.ValidateParam(param, request.params[name], name, fieldErrors, false, undefined);
7171
case 'header':
7272
return this.validationService.ValidateParam(param, request.header(name), name, fieldErrors, false, undefined);
73-
case 'body':
74-
return this.validationService.ValidateParam(param, request.body, name, fieldErrors, true, undefined);
75-
case 'body-prop':
76-
return this.validationService.ValidateParam(param, request.body?.[name], name, fieldErrors, true, 'body.');
73+
case 'body': {
74+
const bodyFieldErrors: FieldErrors = {};
75+
const bodyArgs = this.validationService.ValidateParam(param, request.body, name, bodyFieldErrors, true, undefined);
76+
Object.keys(bodyFieldErrors).forEach(key => {
77+
fieldErrors[key] = { message: bodyFieldErrors[key].message };
78+
});
79+
return bodyArgs;
80+
}
81+
case 'body-prop': {
82+
const bodyPropFieldErrors: FieldErrors = {};
83+
const bodyPropArgs = this.validationService.ValidateParam(param, request.body?.[name], name, bodyPropFieldErrors, true, 'body.');
84+
Object.keys(bodyPropFieldErrors).forEach(key => {
85+
fieldErrors[key] = { message: bodyPropFieldErrors[key].message };
86+
});
87+
return bodyPropArgs;
88+
}
7789
case 'formData': {
7890
const files = Object.values(args).filter(p => p.dataType === 'file' || (p.dataType === 'array' && p.array && p.array.dataType === 'file'));
7991
if ((param.dataType === 'file' || (param.dataType === 'array' && param.array && param.array.dataType === 'file')) && files.length > 0) {

packages/runtime/src/routeGeneration/templates/hapi/hapiTemplateService.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,23 @@ export class HapiTemplateService extends TemplateService<HapiApiHandlerParameter
9393
return this.validationService.ValidateParam(param, request.params[name], name, errorFields, false, undefined);
9494
case 'header':
9595
return this.validationService.ValidateParam(param, request.headers[name], name, errorFields, false, undefined);
96-
case 'body':
97-
return this.validationService.ValidateParam(param, request.payload, name, errorFields, true, undefined);
96+
case 'body': {
97+
const bodyFieldErrors: FieldErrors = {};
98+
const result = this.validationService.ValidateParam(param, request.payload, name, bodyFieldErrors, true, undefined);
99+
Object.keys(bodyFieldErrors).forEach(key => {
100+
errorFields[key] = { message: bodyFieldErrors[key].message };
101+
});
102+
return result;
103+
}
98104
case 'body-prop': {
99105
const descriptor = Object.getOwnPropertyDescriptor(request.payload, name);
100106
const value = descriptor ? descriptor.value : undefined;
101-
return this.validationService.ValidateParam(param, value, name, errorFields, true, 'body.');
107+
const bodyFieldErrors: FieldErrors = {};
108+
const result = this.validationService.ValidateParam(param, value, name, bodyFieldErrors, true, 'body.');
109+
Object.keys(bodyFieldErrors).forEach(key => {
110+
errorFields[key] = { message: bodyFieldErrors[key].message };
111+
});
112+
return result;
102113
}
103114
case 'formData': {
104115
const descriptor = Object.getOwnPropertyDescriptor(request.payload, name);

packages/runtime/src/routeGeneration/templates/koa/koaTemplateService.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,22 @@ export class KoaTemplateService extends TemplateService<KoaApiHandlerParameters,
7575
case 'body': {
7676
const descriptor = Object.getOwnPropertyDescriptor(context.request, 'body');
7777
const value = descriptor ? descriptor.value : undefined;
78-
return this.validationService.ValidateParam(param, value, name, errorFields, true, undefined);
78+
const bodyFieldErrors: FieldErrors = {};
79+
const result = this.validationService.ValidateParam(param, value, name, bodyFieldErrors, true, undefined);
80+
Object.keys(bodyFieldErrors).forEach((key) => {
81+
errorFields[key] = { message: bodyFieldErrors[key].message }
82+
});
83+
return result;
7984
}
8085
case 'body-prop': {
8186
const descriptor = Object.getOwnPropertyDescriptor(context.request, 'body');
8287
const value = descriptor ? descriptor.value[name] : undefined;
83-
return this.validationService.ValidateParam(param, value, name, errorFields, true, 'body.');
88+
const bodyFieldErrors: FieldErrors = {};
89+
const result = this.validationService.ValidateParam(param, value, name, bodyFieldErrors, true, 'body.');
90+
Object.keys(bodyFieldErrors).forEach((key) => {
91+
errorFields[key] = { message: bodyFieldErrors[key].message }
92+
});
93+
return result;
8494
}
8595
case 'formData': {
8696
const files = Object.values(args).filter(p => p.dataType === 'file' || (p.dataType === 'array' && p.array && p.array.dataType === 'file'));

tests/fixtures/controllers/validateController.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Body, Get, Post, Query, Route } from '@tsoa/runtime';
1+
import { Body, BodyProp, Get, Post, Query, Route } from '@tsoa/runtime';
22
import { ValidateMapStringToAny, ValidateMapStringToNumber, ValidateModel } from './../testModel';
33

44
export interface ValidateDateResponse {
@@ -19,6 +19,10 @@ export interface ValidateStringResponse {
1919
quotedPatternValue: string;
2020
}
2121

22+
export interface ValidateBodyPropResponse {
23+
name: string;
24+
}
25+
2226
@Route('Validate')
2327
export class ValidateController {
2428
/**
@@ -134,6 +138,11 @@ export class ValidateController {
134138
return Promise.resolve(body);
135139
}
136140

141+
@Post('body-prop')
142+
public bodyPropValidate(@BodyProp('name') name: string): Promise<ValidateBodyPropResponse> {
143+
return Promise.resolve({ name: `${name}-validated` });
144+
}
145+
137146
@Post('map')
138147
public async getNumberBodyRequest(@Body() map: ValidateMapStringToNumber): Promise<number[]> {
139148
return Object.keys(map).map(key => map[key]);

tests/fixtures/custom/custom-route-generator/templates/models.hbs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,22 @@ export function getValidatedArgs(args: any, event: any): any[] {
4343
return validationService.ValidateParam(args[key], event.pathParameters[name], name, fieldErrors, false, undefined);
4444
case 'header':
4545
return validationService.ValidateParam(args[key], event.headers[name], name, fieldErrors, false, undefined);
46-
case 'body':
47-
return validationService.ValidateParam(args[key], eventBody, name, fieldErrors, true, undefined);
48-
case 'body-prop':
49-
return validationService.ValidateParam(args[key], eventBody[name], name, fieldErrors, true, 'body.');
46+
case 'body': {
47+
const bodyFieldErrors: FieldErrors = {};
48+
const bodyArgs = validationService.ValidateParam(args[key], eventBody, name, bodyFieldErrors, true, undefined);
49+
Object.keys(bodyFieldErrors).forEach((key) => {
50+
fieldErrors[key] = { message: bodyFieldErrors[key].message }
51+
});
52+
return bodyArgs;
53+
}
54+
case 'body-prop': {
55+
const bodyPropFieldErrors: FieldErrors = {};
56+
const bodyPropArgs = validationService.ValidateParam(args[key], eventBody[name], name, bodyPropFieldErrors, true, 'body.');
57+
Object.keys(bodyPropFieldErrors).forEach((key) => {
58+
fieldErrors[key] = { message: bodyPropFieldErrors[key].message }
59+
});
60+
return bodyPropArgs;
61+
}
5062
case 'formData':
5163
throw new Error('Multi-part form data not supported yet');
5264
case 'res':

tests/fixtures/custom/custom-tsoa-template.ts.hbs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,32 @@ export function RegisterRoutes(app: any) {
125125
const values = Object.keys(args).map(function(key) {
126126
const name = args[key].name;
127127
switch (args[key].in) {
128-
case 'request':
129-
return request;
130-
case 'request-prop':
131-
return ValidateParam(args[key], request[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
132-
case 'query':
133-
return ValidateParam(args[key], request.query[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
134-
case 'path':
135-
return ValidateParam(args[key], request.params[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
136-
case 'header':
137-
return ValidateParam(args[key], request.header(name), models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
138-
case 'body':
139-
return ValidateParam(args[key], request.body, models, name, errorFields, true, undefined, {{{json minimalSwaggerConfig}}});
140-
case 'body-prop':
141-
return ValidateParam(args[key], request.body[name], models, name, errorFields, true, undefined, {{{json minimalSwaggerConfig}}});
128+
case 'request':
129+
return request;
130+
case 'request-prop':
131+
return ValidateParam(args[key], request[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
132+
case 'query':
133+
return ValidateParam(args[key], request.query[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
134+
case 'path':
135+
return ValidateParam(args[key], request.params[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
136+
case 'header':
137+
return ValidateParam(args[key], request.header(name), models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}});
138+
case 'body': {
139+
const bodyFieldErrors: FieldErrors = {};
140+
const bodyArgs = ValidateParam(args[key], request.body, models, name, bodyFieldErrors, true, undefined, {{{json minimalSwaggerConfig}}});
141+
Object.keys(bodyFieldErrors).forEach((key) => {
142+
errorFields[key] = { message: bodyFieldErrors[key].message }
143+
});
144+
return bodyArgs;
145+
}
146+
case 'body-prop': {
147+
const bodyPropFieldErrors: FieldErrors = {};
148+
const bodyPropArgs = ValidateParam(args[key], request.body[name], models, name, bodyPropFieldErrors, true, undefined, {{{json minimalSwaggerConfig}}});
149+
Object.keys(bodyPropFieldErrors).forEach((key) => {
150+
errorFields[key] = { message: bodyPropFieldErrors[key].message }
151+
});
152+
return bodyPropArgs;
153+
}
142154
}
143155
});
144156

0 commit comments

Comments
 (0)