aws-sdk-js-v3
aws-sdk-js-v3 copied to clipboard
@aws-sdk/signature-v4: Can't sign API Gateway requests, SHA256 not provided - TypeError: hashConstructor is not a constructor
Describe the bug
#460 deals with the issue for the Cognito Identity Provider, but the issue that the sha256 field is not set with a default implementation remains in the SDK when used for signing requests for API Gateway or other purposes.
Your environment
SDK version number
@aws-sdk/[email protected]
Is the issue in the browser/Node.js/ReactNative?
Node.js
Details of the browser/Node.js/ReactNative version
Node 16.x
Steps to reproduce
Please share code or minimal repo, and steps to reproduce the behavior.
const axios = require("axios");
const { SignatureV4 } = require("@aws-sdk/signature-v4");
const { HttpRequest } = require("@aws-sdk/protocol-http");
// const { Sha256 } = require("@aws-crypto/sha256-js");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const signer = new SignatureV4({
credentials: defaultProvider(),
// sha256: Sha256,
region: "eu-west-1",
service: "execute-api",
});
async function post(apiUrl, data) {
const parsedUrl = new URL(apiUrl);
const endpoint = parsedUrl.hostname.toString();
const path = parsedUrl.pathname.toString();
const req = new HttpRequest({
hostname: endpoint,
path,
method: "POST",
body: JSON.stringify(data),
headers: {
host: endpoint,
"Content-Type": "application/json",
},
});
const signed = await signer.sign(req, { signingDate: new Date() });
return axios.post(apiUrl, signed.body, { headers: signed.headers });
}
send("https://xxxxx.execute-api.eu-west-1.amazonaws.com/prod/test", { data: "123" })
.then((res) => {
console.log("Success", res);
})
.catch((err) => {
console.log("Error", err);
});
Observed behavior
Error: TypeError: hashConstructor is not a constructor
Expected behavior
Success, since there's a SHA256 implementation built into Node.js. However, that seems not to be compatible directly with this library, requiring a 3rd party to be used.
Workaround
After quite a bit of searching and looking through AWS examples and docs, adapting the example from https://stackoverflow.com/a/72001517 I was able to construct a working version, which uses the @aws-crypto/sha256-js library as an implementation.
const axios = require("axios");
const { SignatureV4 } = require("@aws-sdk/signature-v4");
const { HttpRequest } = require("@aws-sdk/protocol-http");
const { Sha256 } = require("@aws-crypto/sha256-js");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const signer = new SignatureV4({
credentials: defaultProvider(),
sha256: Sha256,
region: "eu-west-1",
service: "execute-api",
});
async function post(apiUrl, data) {
const parsedUrl = new URL(apiUrl);
const endpoint = parsedUrl.hostname.toString();
const path = parsedUrl.pathname.toString();
const req = new HttpRequest({
hostname: endpoint,
path,
method: "POST",
body: JSON.stringify(data),
headers: {
host: endpoint,
"Content-Type": "application/json",
},
});
const signed = await signer.sign(req, { signingDate: new Date() });
return axios.post(apiUrl, signed.body, { headers: signed.headers });
}
send("https://xxxxx.execute-api.eu-west-1.amazonaws.com/prod/test", { data: "123" })
.then((res) => {
console.log("Success", res);
})
.catch((err) => {
console.log("Error", err);
});
Hi @a-h, thank you for reaching out. I was able to reproduce the reported behavior, and also I can confirm that the workaround you tried worked. I can see that the sh256 parameter is marked as optional but not default implementation is defined for when this parameter is not provided. What I am going to do is to mark this issue to review so we can investigate this further. Thank you!
For the time being you can 'polyfill' the missing Sha256 class with the following:
import { createHash, createHmac } from 'crypto';
...
class Sha256 {
private readonly hash;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(secret?: any) {
this.hash = secret ? createHmac('sha256', secret) : createHash('sha256');
}
update(array: Uint8Array) {
this.hash.update(array);
}
digest() {
const buffer = this.hash.digest();
return Promise.resolve(new Uint8Array(buffer.buffer));
}
}
This can be useful on AWS Lambda with Node 18 for inline functions.