Skip to content

[typescript-fetch] Support AWSv4 Signature #15929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions bin/configs/typescript-fetch-with-awsv4-signature.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
generatorName: typescript-fetch
outputDir: samples/client/petstore/typescript-fetch/builds/with-awsv4-signature
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
additionalProperties:
npmVersion: 1.0.0
npmName: '@openapitools/typescript-fetch-petstore'
npmRepository: https://skimdb.npmjs.com/registry
withAWSV4Signature: true
snapshot: false
3 changes: 1 addition & 2 deletions bin/configs/typescript-fetch-with-npm-version.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
additionalProperties:
npmVersion: 1.0.0
npmName: '@openapitools/typescript-fetch-petstore'
npmRepository: https://skimdb.npmjs.com/registry
snapshot: false
npmRepository: https://skimdb.npmjs.com/registry
1 change: 1 addition & 0 deletions docs/generators/typescript-fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|stringEnums|Generate string enums instead of objects for enum values.| |false|
|supportsES6|Generate code that conforms to ES6.| |false|
|useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |true|
|withAWSV4Signature|whether to include AWS v4 signature support| |false|
|withInterfaces|Setting this property to true will generate interfaces next to the default class implementations.| |false|
|withoutRuntimeChecks|Setting this property to true will remove any runtime checks on the request and response payloads. Payloads will be casted to their expected types.| |false|

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ protected String nullableQuotedJSString(@Nullable String string) {
protected HashSet<String> languageGenericTypes;
protected String npmName = null;
protected String npmVersion = "1.0.0";
protected Boolean withAWSV4Signature = false;

protected String enumSuffix = "Enum";

Expand Down Expand Up @@ -404,6 +405,10 @@ public void processOpts() {
if (additionalProperties.containsKey(NPM_NAME)) {
this.setNpmName(additionalProperties.get(NPM_NAME).toString());
}

if (additionalProperties.containsKey(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT)) {
this.setWithAWSV4Signature(Boolean.valueOf(additionalProperties.get(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT).toString()));
}
}

@Override
Expand Down Expand Up @@ -1013,6 +1018,14 @@ public void setNpmVersion(String npmVersion) {
this.npmVersion = npmVersion;
}

public Boolean getWithAWSV4Signature() {
return this.withAWSV4Signature;
}

public void setWithAWSV4Signature(Boolean withAWSV4Signature) {
this.withAWSV4Signature = withAWSV4Signature;
}

private void setDiscriminatorValue(CodegenModel model, String baseName, String value) {
for (CodegenProperty prop : model.allVars) {
if (prop.baseName.equals(baseName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
protected boolean addedModelIndex = false;
protected boolean withoutRuntimeChecks = false;
protected boolean stringEnums = false;
private boolean withAWSV4Signature = false;

// "Saga and Record" mode.
public static final String SAGAS_AND_RECORDS = "sagasAndRecords";
Expand Down Expand Up @@ -105,6 +106,7 @@ public TypeScriptFetchClientCodegen() {
this.cliOptions.add(new CliOption(SAGAS_AND_RECORDS, "Setting this property to true will generate additional files for use with redux-saga and immutablejs.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(IMPORT_FILE_EXTENSION_SWITCH, IMPORT_FILE_EXTENSION_SWITCH_DESC).defaultValue(""));
this.cliOptions.add(new CliOption(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT, CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
}

@Override
Expand Down Expand Up @@ -196,6 +198,10 @@ public void setPackageAsSourceOnlyLibrary(boolean packageAsSourceOnlyLibrary) {
this.packageAsSourceOnlyLibrary = packageAsSourceOnlyLibrary;
}

public void setWithAWSV4Signature(boolean withAWSV4Signature) {
this.withAWSV4Signature = withAWSV4Signature;
}

public boolean isUniqueIdAccordingToNameSuffix(String name) {
if (name == null) {
return false;
Expand Down Expand Up @@ -286,6 +292,9 @@ public void processOpts() {
}
}
}
if (additionalProperties.containsKey(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT)) {
this.setWithAWSV4Signature(convertPropertyToBoolean(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT));
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,40 +258,73 @@ export class {{classname}} extends runtime.BaseAPI {
{{/isArray}}
{{/formParams}}
{{/hasFormParams}}
const response = await this.request({


{{#hasBodyParam}}
{{#bodyParam}}
{{#isContainer}}
{{^withoutRuntimeChecks}}
const body: any = requestParameters.{{paramName}}{{#isArray}}{{#items}}{{^isPrimitiveType}}.map({{datatype}}ToJSON){{/isPrimitiveType}}{{/items}}{{/isArray}};
{{/withoutRuntimeChecks}}
{{#withoutRuntimeChecks}}
const body: any = requestParameters.{{paramName}};
{{/withoutRuntimeChecks}}
{{/isContainer}}
{{^isContainer}}
{{^isPrimitiveType}}
{{^withoutRuntimeChecks}}
const body: any = {{dataType}}ToJSON(requestParameters.{{paramName}});
{{/withoutRuntimeChecks}}
{{#withoutRuntimeChecks}}
const body: any = requestParameters.{{paramName}};
{{/withoutRuntimeChecks}}
{{/isPrimitiveType}}
{{#isPrimitiveType}}
const body: any = requestParameters.{{paramName}} as any;
{{/isPrimitiveType}}
{{/isContainer}}
{{/bodyParam}}
{{/hasBodyParam}}
{{#hasFormParams}}
const body: any = formParams;
{{/hasFormParams}}

const request: runtime.RequestOpts = {
path: `{{{path}}}`{{#pathParams}}.replace(`{${"{{baseName}}"}}`, encodeURIComponent(String(requestParameters.{{paramName}}))){{/pathParams}},
method: '{{httpMethod}}',
headers: headerParameters,
query: queryParameters,
{{#hasBodyParam}}
body: body,
{{/hasBodyParam}}
{{#hasFormParams}}
body: body,
{{/hasFormParams}}
}
{{#withAWSV4Signature}}
if (this.configuration && this.configuration.awsV4SignerParameters) {
const SignUrl = this.configuration.basePath + request.path;
{{#bodyParam}}
{{#isContainer}}
{{^withoutRuntimeChecks}}
body: requestParameters.{{paramName}}{{#isArray}}{{#items}}{{^isPrimitiveType}}.map({{datatype}}ToJSON){{/isPrimitiveType}}{{/items}}{{/isArray}},
{{/withoutRuntimeChecks}}
{{#withoutRuntimeChecks}}
body: requestParameters.{{paramName}},
{{/withoutRuntimeChecks}}
{{/isContainer}}
{{^isContainer}}
{{^isPrimitiveType}}
{{#hasBodyParam}}
{{^withoutRuntimeChecks}}
body: {{dataType}}ToJSON(requestParameters.{{paramName}}),
const SignBody = JSON.stringify(request.body);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rename to signBody

{{/withoutRuntimeChecks}}
{{#withoutRuntimeChecks}}
body: requestParameters.{{paramName}},
const SignBody = request.body;
{{/withoutRuntimeChecks}}
{{/isPrimitiveType}}
{{#isPrimitiveType}}
body: requestParameters.{{paramName}} as any,
{{/isPrimitiveType}}
{{/isContainer}}
{{/bodyParam}}
{{/hasBodyParam}}
{{#hasFormParams}}
body: formParams,
{{/hasFormParams}}
}, initOverrides);
{{/bodyParam}}
{{^bodyParam}}
const SignBody = null;
{{/bodyParam}}
const signer = new runtime.AwsV4Signer(this.configuration.awsV4SignerParameters);
const signResult = await signer.sign('{{httpMethod}}', SignUrl, headerParameters, SignBody);
//request.url = signResult.url;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//request.url = signResult.url;

//request.method = signResult.method;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//request.method = signResult.method;

request.headers = signResult.headers;
}
{{/withAWSV4Signature}}
const response = await this.request(request, initOverrides);

{{#returnType}}
{{#isResponseFile}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
},
{{/packageAsSourceOnlyLibrary}}
"devDependencies": {
{{#withAWSV4Signature}}
"aws4fetch": "^1.0.13"
{{/withAWSV4Signature}}
{{#sagasAndRecords}}
"immutable": "^4.0.0-rc.12",
"normalizr": "^3.6.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
/* eslint-disable */
{{>licenseInfo}}

{{#withAWSV4Signature}}
import { AwsV4Signer as AwsV4SignerLib } from "aws4fetch";
{{/withAWSV4Signature}}

export const BASE_PATH = "{{{basePath}}}".replace(/\/+$/, "");

export interface ConfigurationParameters {
Expand All @@ -15,7 +19,42 @@ export interface ConfigurationParameters {
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
headers?: HTTPHeaders; //header params we want to use on every request
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
{{#withAWSV4Signature}}
awsV4SignParameters?: AwsV4SignerParameters; // parameter for aws v4 signature security
{{/withAWSV4Signature}}
}

{{#withAWSV4Signature}}
export interface AwsV4SignerParameters {
accessKeyId: string;
secretAccessKey: string;
region: string;
service?: string;
}

export class AwsV4Signer {
constructor(private configuration: AwsV4SignerParameters) {}
async sign(method: string, url: string, headers: HTTPHeaders, body: any): Promise<{url: URL, headers: HTTPHeaders}> {
const signer = new AwsV4SignerLib({
method: method,
url: url,
headers: headers,
body: body,
accessKeyId: this.configuration.accessKeyId,
secretAccessKey: this.configuration.secretAccessKey,
service: this.configuration.service,
region: this.configuration.region,
});
const signResult = await signer.sign();
// Convert Headers to HTTPHeaders
let newHeaders: HTTPHeaders = {};
for (const [key, value] of signResult.headers.entries()) {
newHeaders[key] = value;
}
return {url: signResult.url, headers: newHeaders};
}
}
{{/withAWSV4Signature}}

export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) {}
Expand Down Expand Up @@ -71,6 +110,11 @@ export class Configuration {
get credentials(): RequestCredentials | undefined {
return this.configuration.credentials;
}
{{#withAWSV4Signature}}
get awsV4SignerParameters(): AwsV4SignerParameters | undefined {
return this.configuration.awsV4SignParameters;
}
{{/withAWSV4Signature}}
}

export const DefaultConfig = new Configuration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class TypeScriptFetchClientOptionsProvider implements OptionsProvider {
public static final String SAGAS_AND_RECORDS = "false";
public static final String ENUM_UNKNOWN_DEFAULT_CASE_VALUE = "false";
public static final String STRING_ENUMS = "false";
public static final String WITH_AWSV4_SIGNATURE = "false";

@Override
public String getLanguage() {
Expand Down Expand Up @@ -78,6 +79,7 @@ public Map<String, String> createOptions() {
.put(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, "true")
.put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, ENUM_UNKNOWN_DEFAULT_CASE_VALUE)
.put(TypeScriptFetchClientCodegen.STRING_ENUMS, STRING_ENUMS)
.put(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT, WITH_AWSV4_SIGNATURE)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ protected void verifyOptions() {
verify(clientCodegen).setSagasAndRecords(Boolean.valueOf(TypeScriptFetchClientOptionsProvider.SAGAS_AND_RECORDS));
verify(clientCodegen).setEnumUnknownDefaultCase(Boolean.parseBoolean(TypeScriptFetchClientOptionsProvider.ENUM_UNKNOWN_DEFAULT_CASE_VALUE));
verify(clientCodegen).setStringEnums(Boolean.parseBoolean(TypeScriptFetchClientOptionsProvider.STRING_ENUMS));
verify(clientCodegen).setWithAWSV4Signature(Boolean.parseBoolean(TypeScriptFetchClientOptionsProvider.WITH_AWSV4_SIGNATURE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,13 @@ public void testNestedReadonlySchemas() {
Assert.assertEquals(schemaBefore.keySet(), Sets.newHashSet("club", "owner"));
}

@Test(description = "Don't generate new schemas for nullable references")
public void testNestedNullableSchemas() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf-nullable.yaml");
@Test(description = "Don't add AWSv4 signature support by default")
public void testWithoutAWSV4SignatureAdditionalProps() {
final Schema model = new Schema()
.additionalProperties(new StringSchema());
final DefaultCodegen codegen = new TypeScriptFetchClientCodegen();
codegen.additionalProperties().put("withAWSV4Signature", false);
codegen.processOpts();
codegen.setOpenAPI(openAPI);
final Map<String, Schema> schemaBefore = openAPI.getComponents().getSchemas();
Assert.assertEquals(schemaBefore.keySet(), Sets.newHashSet("club", "owner"));
Assert.assertEquals(codegen.getTypeDeclaration(model), "{ [key: string]: string; }");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ export class DefaultApi extends runtime.BaseAPI {

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({



const request: runtime.RequestOpts = {
path: `/person/display/{personId}`.replace(`{${"personId"}}`, encodeURIComponent(String(requestParameters.personId))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
}
const response = await this.request(request, initOverrides);

return new runtime.JSONApiResponse(response, (jsonValue) => ClubFromJSON(jsonValue));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/



export const BASE_PATH = "http://api.example.xyz/v1".replace(/\/+$/, "");

export interface ConfigurationParameters {
Expand All @@ -28,6 +29,7 @@ export interface ConfigurationParameters {
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
}


export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ export class DefaultApi extends runtime.BaseAPI {

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({



const request: runtime.RequestOpts = {
path: `/person/display/{personId}`.replace(`{${"personId"}}`, encodeURIComponent(String(requestParameters.personId))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
}
const response = await this.request(request, initOverrides);

return new runtime.JSONApiResponse(response, (jsonValue) => ClubFromJSON(jsonValue));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/



export const BASE_PATH = "http://api.example.xyz/v1".replace(/\/+$/, "");

export interface ConfigurationParameters {
Expand All @@ -28,6 +29,7 @@ export interface ConfigurationParameters {
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
}


export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) {}

Expand Down
Loading