Skip to content

Commit

Permalink
Fix dangerous destructuration in typescript-nestjs services (#20157)
Browse files Browse the repository at this point in the history
* refactor: remove requestParameters destructuration

* feat: add reserved param names sample

* feat: quote params

* feat: improve with reservedWords

* feat: use vendorExtensions instead of extending CodegenParameter
  • Loading branch information
GregoryMerlet authored Dec 3, 2024
1 parent 26609e9 commit cf78f10
Show file tree
Hide file tree
Showing 20 changed files with 659 additions and 5 deletions.
6 changes: 6 additions & 0 deletions bin/configs/typescript-nestjs-reserved-param-names.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
generatorName: typescript-nestjs
outputDir: samples/client/petstore/typescript-nestjs/builds/reservedParamNames
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript-nestjs/reserved-param-names.yaml
templateDir: modules/openapi-generator/src/main/resources/typescript-nestjs
additionalProperties:
"useSingleRequestParameter" : true
2 changes: 2 additions & 0 deletions docs/generators/typescript-nestjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ These options may be applied as additional-properties (cli) or configOptions (pl
<li>float</li>
<li>for</li>
<li>formParams</li>
<li>from</li>
<li>function</li>
<li>goto</li>
<li>headerParams</li>
<li>headers</li>
<li>if</li>
<li>implements</li>
<li>import</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ public TypeScriptNestjsClientCodegen() {
apiPackage = "api";
modelPackage = "model";

reservedWords.addAll(Arrays.asList("from", "headers"));

this.cliOptions.add(new CliOption(NPM_REPOSITORY,
"Use this property to set an url your private npmRepo in the package.json"));
this.cliOptions.add(CliOption.newBoolean(WITH_INTERFACES,
Expand Down Expand Up @@ -327,6 +329,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L

// Overwrite path to TypeScript template string, after applying everything we just did.
op.path = pathBuffer.toString();

for (CodegenParameter param : op.allParams) {
param.vendorExtensions.putIfAbsent("x-param-has-sanitized-name", !param.baseName.equals(param.paramName));
}
}

operations.put("hasSomeFormParams", hasSomeFormParams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface {{classname}}{{operationIdCamelCase}}Request {
* @type {{=<% %>=}}{<%&dataType%>}<%={{ }}=%>
* @memberof {{classname}}{{operationIdCamelCase}}
*/
readonly {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}
readonly {{#vendorExtensions.x-param-has-sanitized-name}}'{{{baseName}}}'{{/vendorExtensions.x-param-has-sanitized-name}}{{^vendorExtensions.x-param-has-sanitized-name}}{{{paramName}}}{{/vendorExtensions.x-param-has-sanitized-name}}{{^required}}?{{/required}}: {{{dataType}}}
{{^-last}}

{{/-last}}
Expand Down Expand Up @@ -106,7 +106,7 @@ export class {{classname}} {
{{#useSingleRequestParameter}}
const {
{{#allParams}}
{{paramName}},
{{#vendorExtensions.x-param-has-sanitized-name}}'{{{baseName}}}': {{/vendorExtensions.x-param-has-sanitized-name}}{{paramName}},
{{/allParams}}
} = requestParameters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export interface {{classname}}{{#allParents}}{{#-first}} extends {{/-first}}{{{.
* {{{.}}}
*/
{{/description}}
{{#isReadOnly}}readonly {{/isReadOnly}}{{{name}}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
{{#isReadOnly}}readonly {{/isReadOnly}}{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
{{/vars}}
}{{>modelGenericEnums}}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}}
* {{{.}}}
*/
{{/description}}
{{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
{{/allVars}}
}
{{>modelGenericEnums}}
{{/parent}}
{{^parent}}
{{>modelGeneric}}
{{/parent}}
{{/discriminator}}
{{/discriminator}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
openapi: 3.0.0
info:
description: Test reserved param names
version: 1.0.0
title: Reserved param names
paths:
/test:
post:
security:
- bearerAuth: []
summary: Test reserved param names
description: ''
operationId: testReservedParamNames
parameters:
- name: notReserved
in: query
description: Should not be treated as a reserved param name
required: true
schema:
type: string
- name: from
in: query
description: Might conflict with rxjs import
required: true
schema:
type: string
- name: headers
in: header
description: Might conflict with headers const
required: true
schema:
type: string
responses:
'200':
description: successful operation
'405':
description: Invalid input
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.gitignore
README.md
api.module.ts
api/api.ts
api/default.service.ts
configuration.ts
git_push.sh
index.ts
model/models.ts
variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.11.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
## @

### Building

To install the required dependencies and to build the typescript sources run:
```
npm install
npm run build
```

#### General usage

In your Nestjs project:


```
// without configuring providers
import { ApiModule } from '';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [
ApiModule,
HttpModule
],
providers: []
})
export class AppModule {}
```

```
// configuring providers
import { ApiModule, Configuration, ConfigurationParameters } from '';
export function apiConfigFactory (): Configuration => {
const params: ConfigurationParameters = {
// set configuration parameters here.
}
return new Configuration(params);
}
@Module({
imports: [ ApiModule.forRoot(apiConfigFactory) ],
declarations: [ AppComponent ],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {}
```

```
import { DefaultApi } from '';
export class AppComponent {
constructor(private apiGateway: DefaultApi) { }
}
```

Note: The ApiModule a dynamic module and instantiated once app wide.
This is to ensure that all services are treated as singletons.

#### Using multiple swagger files / APIs / ApiModules
In order to use multiple `ApiModules` generated from different swagger files,
you can create an alias name when importing the modules
in order to avoid naming conflicts:
```
import { ApiModule } from 'my-api-path';
import { ApiModule as OtherApiModule } from 'my-other-api-path';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [
ApiModule,
OtherApiModule,
HttpModule
]
})
export class AppModule {
}
```


### Set service base path
If different than the generated base path, during app bootstrap, you can provide the base path to your service.

```
import { BASE_PATH } from '';
bootstrap(AppComponent, [
{ provide: BASE_PATH, useValue: 'https://your-web-service.com' },
]);
```
or

```
import { BASE_PATH } from '';
@Module({
imports: [],
declarations: [ AppComponent ],
providers: [ provide: BASE_PATH, useValue: 'https://your-web-service.com' ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
```

### Configuring the module with `forRootAsync`

You can also use the Nestjs Config Module/Service to configure your app with `forRootAsync`.

```
@Module({
imports: [
ApiModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService): Configuration => {
const params: ConfigurationParameters = {
// set configuration parameters here.
basePath: config.get('API_URL'),
};
return new Configuration(params);
},
})
],
declarations: [ AppComponent ],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {}
```

#### Using @nestjs/cli
First extend your `src/environments/*.ts` files by adding the corresponding base path:

```
export const environment = {
production: false,
API_BASE_PATH: 'http://127.0.0.1:8080'
};
```

In the src/app/app.module.ts:
```
import { BASE_PATH } from '';
import { environment } from '../environments/environment';
@Module({
declarations: [
AppComponent
],
imports: [ ],
providers: [
{
provide: 'BASE_PATH',
useValue: environment.API_BASE_PATH
}
]
})
export class AppModule { }
```
Loading

0 comments on commit cf78f10

Please sign in to comment.