Skip to content
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

feat: serialize query params passed as argument #34

Merged
merged 3 commits into from
Jan 13, 2021
Merged

feat: serialize query params passed as argument #34

merged 3 commits into from
Jan 13, 2021

Conversation

florianbepunkt
Copy link
Contributor

Axios allows for query params to be passed as part of the config argument (instead of the url). This commit serializes the query params before signing. Before the query params were discarded and the signature did not match.

I added a separate test for this. Typing is rather loose but aligns with the @types/axios package.

Parameter serialization should be independent of whether a relative or absolute url was passed in the req config, therefore I added it as a first step.

Axios allows for query params to be passed as part of the config argument (instead of the url). This commit serializes the query params before signing. Before the query params were discarded and the signature did not match.
@@ -44,6 +45,11 @@ export const aws4Interceptor = (options?: InterceptorOptions, credentials?: Cred
throw new Error("No URL present in request config, unable to sign request");
}

if(config.params) {
config.url = buildUrl(config.url, config.params, config.paramsSerializer);
Copy link

Choose a reason for hiding this comment

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

I think mutating an incoming config is an action at a distance anti-pattern. Better to make a copy (deep clone).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if I can follow regarding the anti-pattern. The params object would need to be deleted. I can create a clone at the beginning and refactor all following operations using the cloned config. But it adds overhead, if people use more complex custom serializer functions and stuff.

Copy link

Choose a reason for hiding this comment

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

As a package consumer, I do not expect the package to make any modifications to my input object.

Imagine I'd want to make two queries, with small modifications:

const req = {
  method: "GET",
  params: {
    foo: 1
  }
}

request(req)

// this request would fail, because `req.params` was clobbered
req.params.foo = 2
request(req)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe axios already uses a copy of the passed req. I added a test for illustration.

Copy link

Choose a reason for hiding this comment

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

You are right! Indeed, axios internals make a deep clone already.

const request: AxiosRequestConfig = {
method: "GET",
url: "https://example.com/foobar",
params: {foo: "bar"},
Copy link

@moltar moltar Jan 5, 2021

Choose a reason for hiding this comment

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

Need to add test data for for alphabetical sort of the params.

Copy link

Choose a reason for hiding this comment

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

Ignore this part. It was related to the comments above.

@@ -44,6 +45,11 @@ export const aws4Interceptor = (options?: InterceptorOptions, credentials?: Cred
throw new Error("No URL present in request config, unable to sign request");
}

if(config.params) {
config.url = buildUrl(config.url, config.params, config.paramsSerializer);
Copy link

Choose a reason for hiding this comment

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

Not sure if buildUrl does the alphabetical sort, but it is part of the signing procedure.

https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

Sort the parameter names by character code point in ascending order. Parameters with duplicate names should be sorted by value. For example, a parameter name that begins with the uppercase letter F precedes a parameter name that begins with a lowercase letter b.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

buildUrl does not sort. I see it's stated in the docs. Testing against the Amazon seller api, which requires AWS4 signature, query param order made no difference. Signature matched, regardless param order.

Copy link

Choose a reason for hiding this comment

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

It definitely makes a difference. I have ran into it, but I don't remember with which call.

I have sorting added to my hack to make this work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not make a difference, since aws4 already sorts query params according to the spec. https://github.com/mhart/aws4/blob/a413aadd9e4b4e58842937a9ad53354be41ef4a1/aws4.js#L260

Copy link

Choose a reason for hiding this comment

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

The parsing of the object happens here:

https://github.com/mhart/aws4/blob/a413aadd9e4b4e58842937a9ad53354be41ef4a1/aws4.js#L123-L125

And it depends on the following param being set: signQuery.

But this package does not set this param, and thus query does not get parsed and does not get sorted.

Copy link

Choose a reason for hiding this comment

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

I was under the impression that the sorting only needs to happen for creating the signature.

Oh I see. So you think that the aws4 code will sort and sign it. And then when Amazon receives the request, they won't try to sign the URI as-is, but they'll parse it, and sort and sign also?

In other words what goes into the actual HTTP request does not matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was my assumption. But it is solely based on good faith in the aws4 author. If the actual http request would matter, implementing query param sorting in a signing lib like aws4 would make no sense to me. It would lead to missmatch errors all the time – like in our case where the url for signing would be correctly sorted but the actual url is not.

But truth be told: After googling about the signing processes it seems that different aws teams indeed implement the signing in various ways and I found a few issues that pointed out that the aws sdk tests are not 100% compliant with the spec as well. So I think you are right, it should be implemented.

Copy link

Choose a reason for hiding this comment

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

Also see #35. Not 100% related to this issue, but somewhat is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, probably better to close this PR in favor of #35

Copy link

Choose a reason for hiding this comment

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

I think #35 is complimentary to this one. As @aws-sdk/signature-v4 replaces aws4 package. But we still need to handle the params.

Copy link
Owner

@jamesmbourne jamesmbourne left a comment

Choose a reason for hiding this comment

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

Great stuff, thanks @florianbepunkt and @moltar for this!

@jamesmbourne jamesmbourne merged commit bb255bc into jamesmbourne:master Jan 13, 2021
@jamesmbourne
Copy link
Owner

@all-contributors please add @moltar for review

Repository owner deleted a comment from allcontributors bot Feb 4, 2021
Repository owner deleted a comment from allcontributors bot Feb 4, 2021
@jamesmbourne
Copy link
Owner

@all-contributors please add @florianbepunkt for code

@allcontributors
Copy link
Contributor

@jamesmbourne

I've put up a pull request to add @florianbepunkt! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants