diff --git a/README.md b/README.md index c0e386501b22..3ff9aef0a148 100644 --- a/README.md +++ b/README.md @@ -698,6 +698,48 @@ import "@aws-sdk/signature-v4-crt"; Only the import statement is needed. The implementation then registers itself with `@aws-sdk/signature-v4-multi-region` and becomes available for its use. You do not need to use any imported objects directly. +Note that you can also use the native JavaScript implementation of SigV4a that does not depend on AWS CRT and can be used in browsers (unlike the CRT version). The instructions for using that package are below. + +### Using JavaScript (non-CRT) implementation of SigV4a + +AWS SDK for JavaScript v3 now supports a native JavaScript version of SigV4a that can be used in browsers and Node, and does not depend on [AWS Common Runtime (CRT)](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html). This is an alternative to the AWS CRT version, so you don't need to have both packages together. + +If neither the CRT components nor the JavaScript SigV4a implementation are installed, you will receive an error like: + +```console +Neither CRT nor JS SigV4a implementation is available. +Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a. +``` + +indicating that at least one of the required dependencies is missing to use the associated functionality. To install the JavaScript SigV4a dependency, follow the provided instructions. + +#### Installing the JavaScript SigV4a Dependency + +You can install the JavaScript SigV4a dependency with different commands depending on the package management tool you are using. +If you are using NPM: + +```console +npm install @smithy/signature-v4a +``` + +If you are using Yarn: + +```console +yarn add @smithy/signature-v4a +``` + +Additionally, load the signature-v4a package by importing it. + +```js +require("@smithy/signature-v4a"); +// or ESM +import "@smithy/signature-v4a"; +``` + +Only the import statement is needed. The implementation then registers itself with `@aws-sdk/signature-v4-multi-region` +and becomes available for its use. You do not need to use any imported objects directly. +Note that if both the CRT-based implementation and the JavaScript implementation are available, the SDK will prefer to use the CRT-based implementation for better performance. If you specifically want to use the JavaScript implementation, ensure that the CRT package is not installed or imported. + #### Related issues 1. [S3 Multi-Region Access Point(MRAP) is not available unless with additional dependency](https://github.com/aws/aws-sdk-js-v3/issues/2822) diff --git a/clients/client-s3/test/e2e/s3-mrap-sigv4a.e2e.spec.ts b/clients/client-s3/test/e2e/s3-mrap-sigv4a.e2e.spec.ts new file mode 100644 index 000000000000..928f7cadddcc --- /dev/null +++ b/clients/client-s3/test/e2e/s3-mrap-sigv4a.e2e.spec.ts @@ -0,0 +1,208 @@ +// requires a package.json update +import "@smithy/signature-v4a"; + +import { Sha256 } from "@aws-crypto/sha256-js"; +import { + CreateBucketCommand, + DeleteBucketCommand, + ListObjectsV2Command, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { + CreateMultiRegionAccessPointCommand, + DeleteMultiRegionAccessPointCommand, + DescribeMultiRegionAccessPointOperationCommand, + GetMultiRegionAccessPointCommand, + S3ControlClient, +} from "@aws-sdk/client-s3-control"; +import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts"; +import { SignatureV4MultiRegion } from "@aws-sdk/signature-v4-multi-region"; +import { HttpRequest } from "@smithy/protocol-http"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; + +// Setting timeout for long-running tests +const LONG_TIMEOUT = 1800000; // 30 minutes + +describe("S3 Multi-Region Access Point with SignatureV4a (JS Implementation)", () => { + let s3Client: S3Client; + let s3ControlClient: S3ControlClient; + let accountId: string; + let signer: SignatureV4MultiRegion; + let mrapName: string; + let bucketName1: string; + let bucketName2: string; + let mrapArn: string; + + beforeAll(async () => { + // Set timeout for setup + vi.setConfig({ testTimeout: LONG_TIMEOUT }); + + const stsClient = new STSClient({}); + const { Account } = await stsClient.send(new GetCallerIdentityCommand({})); + accountId = Account!; + const timestamp = Date.now(); + mrapName = `test-mrap-${timestamp}`; + bucketName1 = `test-bucket1-${timestamp}`; + bucketName2 = `test-bucket2-${timestamp}`; + + signer = new SignatureV4MultiRegion({ + service: "s3", + region: "*", + sha256: Sha256, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + sessionToken: process.env.AWS_SESSION_TOKEN, + }, + }); + + s3Client = new S3Client({ + region: "*", + useArnRegion: true, + signer, + }); + + s3ControlClient = new S3ControlClient({ + region: "*", + signer, + }); + + // Create buckets + await s3Client.send( + new CreateBucketCommand({ Bucket: bucketName1, CreateBucketConfiguration: { LocationConstraint: "us-west-2" } }) + ); + await s3Client.send( + new CreateBucketCommand({ Bucket: bucketName2, CreateBucketConfiguration: { LocationConstraint: "us-east-2" } }) + ); + + // Create MRAP + const createResponse = await s3ControlClient.send( + new CreateMultiRegionAccessPointCommand({ + AccountId: accountId, + ClientToken: `create-${timestamp}`, + Details: { + Name: mrapName, + PublicAccessBlock: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true, + }, + Regions: [ + { Bucket: bucketName1, BucketAccountId: accountId }, + { Bucket: bucketName2, BucketAccountId: accountId }, + ], + }, + }) + ); + + // Wait for MRAP to be created + let mrapReady = false; + let retries = 0; + while (!mrapReady && retries < 60) { + const describeResponse = await s3ControlClient.send( + new DescribeMultiRegionAccessPointOperationCommand({ + AccountId: accountId, + RequestTokenARN: createResponse.RequestTokenARN, + }) + ); + + if (describeResponse.AsyncOperation?.RequestStatus === "SUCCESS") { + mrapReady = true; + } else { + await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait for 30 seconds before retrying + retries++; + } + } + + if (!mrapReady) { + throw new Error("MRAP creation timed out"); + } + + // Get MRAP ARN + const getResponse = await s3ControlClient.send( + new GetMultiRegionAccessPointCommand({ + AccountId: accountId, + Name: mrapName, + }) + ); + mrapArn = getResponse.AccessPoint!.Alias!; + + // Upload a small file to one of the buckets + await s3Client.send( + new PutObjectCommand({ + Bucket: bucketName1, + Key: "testfile", + Body: Buffer.from("test", "utf-8"), + }) + ); + }); + + afterAll(async () => { + // Set timeout for teardown + vi.setConfig({ testTimeout: LONG_TIMEOUT }); + + // Delete MRAP + try { + await s3ControlClient.send( + new DeleteMultiRegionAccessPointCommand({ + AccountId: accountId, + ClientToken: `delete-${Date.now()}`, + Details: { + Name: mrapName, + }, + }) + ); + } catch (error) { + console.error("Failed to initiate deletion of Multi-Region Access Point:", error); + } + + // Delete buckets + try { + await s3Client.send(new DeleteBucketCommand({ Bucket: bucketName1 })); + await s3Client.send(new DeleteBucketCommand({ Bucket: bucketName2 })); + } catch (error) { + console.error("Failed to delete buckets:", error); + } + }); + + it("should use SignatureV4a JS implementation", async () => { + const mockRequest = new HttpRequest({ + method: "GET", + protocol: "https:", + hostname: "s3-global.amazonaws.com", + headers: { + host: "s3-global.amazonaws.com", + }, + path: "/", + }); + + const signSpy = vi.spyOn(signer, "sign"); + + await signer.sign(mockRequest, { signingRegion: "*" }); + + expect(signSpy).toHaveBeenCalled(); + const signArgs = signSpy.mock.calls[0]; + expect(signArgs[1]?.signingRegion).toBe("*"); + + // verify that signed request has the expected SigV4a headers + const signedRequest = await signSpy.mock.results[0].value; + expect(signedRequest.headers["x-amz-region-set"]).toBe("*"); + expect(signedRequest.headers["authorization"]).toContain("AWS4-ECDSA-P256-SHA256"); + + signSpy.mockRestore(); + }); + + it("should list objects through MRAP using SignatureV4a", async () => { + const command = new ListObjectsV2Command({ + Bucket: mrapArn, + }); + + const response = await s3Client.send(command); + + expect(response.Contents).toBeDefined(); + expect(response.Contents?.length).toBeGreaterThan(0); + expect(response.Contents?.some((object) => object.Key === "testfile")).toBe(true); + }); +}); diff --git a/packages/signature-v4-multi-region/package.json b/packages/signature-v4-multi-region/package.json index e37592d2959f..68cc2552bbf3 100644 --- a/packages/signature-v4-multi-region/package.json +++ b/packages/signature-v4-multi-region/package.json @@ -10,6 +10,8 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "test": "yarn g:vitest run", + "test:e2e": "yarn g:vitest run --config vitest.config.e2e.js", + "test:browser": "yarn g:vitest run --config vitest.config.browser.js", "test:watch": "yarn g:vitest watch" }, "main": "./dist-cjs/index.js", @@ -25,6 +27,7 @@ "@aws-sdk/types": "*", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", + "@smithy/signature-v4a": "file:../../../smithy-typescript/packages/signature-v4a/package.tgz", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, @@ -32,6 +35,7 @@ "@tsconfig/recommended": "1.0.1", "concurrently": "7.0.0", "downlevel-dts": "0.10.1", + "jsdom": "^26.0.0", "rimraf": "3.0.2", "typescript": "~5.2.2" }, diff --git a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts index 9beb819c2444..a252c6c883b9 100644 --- a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts +++ b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.spec.ts @@ -2,11 +2,14 @@ import { HttpRequest } from "@smithy/protocol-http"; import { beforeEach, describe, expect, test as it, vi } from "vitest"; vi.mock("@smithy/signature-v4"); +vi.mock("@smithy/signature-v4a"); vi.mock("@aws-sdk/middleware-sdk-s3"); vi.mock("@aws-sdk/signature-v4-crt"); import { SignatureV4S3Express } from "@aws-sdk/middleware-sdk-s3"; import { CrtSignerV4 } from "@aws-sdk/signature-v4-crt"; +import { signatureV4aContainer } from "@smithy/signature-v4"; +import { SignatureV4a } from "@smithy/signature-v4a"; import { Checksum } from "@smithy/types"; import { signatureV4CrtContainer } from "./signature-v4-crt-container"; @@ -44,6 +47,7 @@ describe("SignatureV4MultiRegion", () => { beforeEach(() => { signatureV4CrtContainer.CrtSignerV4 = CrtSignerV4 as any; + signatureV4aContainer.SignatureV4a = SignatureV4a as any; vi.clearAllMocks(); }); @@ -58,51 +62,102 @@ describe("SignatureV4MultiRegion", () => { it("should presign with SigV4 signer", async () => { const signer = new SignatureV4MultiRegion(params); await signer.presign(minimalRequest); + //@ts-ignore expect(SignatureV4S3Express.mock.instances[0].presign).toBeCalledTimes(1); }); - it("should sign with SigV4a signer if mult_region option is set", async () => { + it("should presign with SigV4 signer", async () => { const signer = new SignatureV4MultiRegion(params); await signer.presign(minimalRequest, { signingRegion: "*" }); //@ts-ignore expect(CrtSignerV4.mock.instances[0].presign).toBeCalledTimes(1); }); - it("should presign with SigV4 signer", async () => { + it("should sign with SigV4a signer if signingRegion is '*'", async () => { const signer = new SignatureV4MultiRegion(params); - await signer.presign(minimalRequest, { signingRegion: "*" }); - //@ts-ignore - expect(CrtSignerV4.mock.instances[0].presign).toBeCalledTimes(1); + await signer.sign(minimalRequest, { signingRegion: "*" }); + //@ts-ignore Check the CrtSignerV4 mock instance instead + expect(CrtSignerV4.mock.instances[0].sign).toBeCalledTimes(1); + expect(SignatureV4a.prototype.sign).toBeCalledTimes(0); // Ensure JS signer was not called + }); + + it("should use SignatureV4a if CrtSignerV4 is not available", async () => { + signatureV4CrtContainer.CrtSignerV4 = null; + const signer = new SignatureV4MultiRegion(params); + await signer.sign(minimalRequest, { signingRegion: "*" }); + expect(SignatureV4a).toHaveBeenCalledTimes(1); + }); + + it("should throw an error for presignWithCredentials with star region", async () => { + const signer = new SignatureV4MultiRegion(params); + const testCredentials = { + accessKeyId: "test-access-key", + secretAccessKey: "test-secret-key", + }; + await expect( + signer.presignWithCredentials(minimalRequest, testCredentials, { signingRegion: "*" }) + ).rejects.toThrow("Method presignWithCredentials is not supported for [signingRegion=*]."); }); - it("should throw if sign with SigV4a in unsupported runtime", async () => { - expect.assertions(1); - const signer = new SignatureV4MultiRegion({ ...params, runtime: "browser" }); - await expect(async () => await signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( - "This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js" + it("should THROW when presigning with signingRegion '*' if CRT is NOT available", async () => { + signatureV4CrtContainer.CrtSignerV4 = null; // Simulate CRT not being available + const signer = new SignatureV4MultiRegion(params); + // Expect the new combined error message + await expect(signer.presign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( + `presign with signingRegion '*' is only supported when using the CRT dependency @aws-sdk/signature-v4-crt. ` + + `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. ` + + `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + + `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. ` + + `For more information please go to https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt` ); + expect(CrtSignerV4).not.toHaveBeenCalled(); }); - it("should throw if preSign with SigV4a in unsupported runtime", async () => { - expect.assertions(1); - const signer = new SignatureV4MultiRegion({ ...params, runtime: "browser" }); + it("should THROW when presigning with signingRegion '*' in non-node runtime (CRT unavailable)", async () => { + const nonNodeParams = { ...params, runtime: "browser" }; + const signer = new SignatureV4MultiRegion(nonNodeParams); + // Expect the new combined error message await expect(signer.presign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( - "This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js" + `presign with signingRegion '*' is only supported when using the CRT dependency @aws-sdk/signature-v4-crt. ` + + `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. ` + + `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + + `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. ` + + `For more information please go to https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt` ); + expect(CrtSignerV4).not.toHaveBeenCalled(); }); - it("should throw if sign with SigV4a and signature-v4-crt is not installed", async () => { + it("should throw an error if neither CrtSignerV4 nor JsSigV4aSigner is available in node runtime", async () => { signatureV4CrtContainer.CrtSignerV4 = null; - expect.assertions(1); - const signer = new SignatureV4MultiRegion({ ...params }); - await expect(async () => await signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( - "\n" + - `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. \n` + - `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + - `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. \n` + + signatureV4aContainer.SignatureV4a = null; + const signer = new SignatureV4MultiRegion(params); + await expect(signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( + "Neither CRT nor JS SigV4a implementation is available. " + + "Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a. " + "For more information please go to " + "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt" ); }); + + it("should throw an error if JsSigV4aSigner is not available in non-node runtime", async () => { + const nonNodeParams = { ...params, runtime: "browser" }; + signatureV4aContainer.SignatureV4a = null; + const signer = new SignatureV4MultiRegion(nonNodeParams); + await expect(signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( + "JS SigV4a implementation is not available or not a valid constructor. " + + "Please check whether you have installed the @smithy/signature-v4a package explicitly. " + + "You must also register the package by calling [require('@smithy/signature-v4a');] " + + "or an ESM equivalent such as [import '@smithy/signature-v4a';]. " + + "For more information please go to " + + "https://github.com/aws/aws-sdk-js-v3#using-javascript-non-crt-implementation-of-sigv4a" + ); + }); + + it("should use JsSigV4aSigner in non-node runtime", async () => { + const nonNodeParams = { ...params, runtime: "browser" }; + const signer = new SignatureV4MultiRegion(nonNodeParams); + await signer.sign(minimalRequest, { signingRegion: "*" }); + expect(SignatureV4a).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts index 9fa7d410e1a1..3201e1a190eb 100644 --- a/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts +++ b/packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts @@ -1,5 +1,10 @@ import { SignatureV4S3Express } from "@aws-sdk/middleware-sdk-s3"; -import { SignatureV4CryptoInit, SignatureV4Init } from "@smithy/signature-v4"; +import { + OptionalSigV4aSigner, + signatureV4aContainer, + SignatureV4CryptoInit, + SignatureV4Init, +} from "@smithy/signature-v4"; import { AwsCredentialIdentity, HttpRequest, @@ -24,11 +29,10 @@ export type SignatureV4MultiRegionInit = SignatureV4Init & * dynamically, the signer wraps native module SigV4a signer and JS SigV4 signer. It signs the request with SigV4a * algorithm if the request needs to be signed with `*` region. Otherwise, it signs the request with normal SigV4 * signer. - * Note that SigV4a signer is only supported in Node.js now because it depends on a native dependency. * @internal */ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner { - private sigv4aSigner?: InstanceType; + private sigv4aSigner?: InstanceType | InstanceType; private readonly sigv4Signer: SignatureV4S3Express; private readonly signerOptions: SignatureV4MultiRegionInit; @@ -39,8 +43,6 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner { public async sign(requestToSign: HttpRequest, options: RequestSigningArguments = {}): Promise { if (options.signingRegion === "*") { - if (this.signerOptions.runtime !== "node") - throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"); return this.getSigv4aSigner().sign(requestToSign, options); } return this.sigv4Signer.sign(requestToSign, options); @@ -48,6 +50,7 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner { /** * Sign with alternate credentials to the ones provided in the constructor. + * Note: This is only supported for SigV4a when using the CRT implementation. */ public async signWithCredentials( requestToSign: HttpRequest, @@ -55,18 +58,44 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner { options: RequestSigningArguments = {} ): Promise { if (options.signingRegion === "*") { - if (this.signerOptions.runtime !== "node") - throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"); - return this.getSigv4aSigner().signWithCredentials(requestToSign, credentials, options); + const signer = this.getSigv4aSigner(); + const CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4; + // Check if the SigV4a signer is the CRT implementation, as JS doesn't support signWithCredentials + if (CrtSignerV4 && signer instanceof CrtSignerV4) { + return signer.signWithCredentials(requestToSign, credentials, options); + } else { + throw new Error( + `signWithCredentials with signingRegion '*' is only supported when using the CRT dependency @aws-sdk/signature-v4-crt. ` + + `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. ` + + `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + + `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. ` + + `For more information please go to https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt` + ); + } } return this.sigv4Signer.signWithCredentials(requestToSign, credentials, options); } + /** + * Presign a request. + * Note: This is only supported for SigV4a when using the CRT implementation. + */ public async presign(originalRequest: HttpRequest, options: RequestPresigningArguments = {}): Promise { if (options.signingRegion === "*") { - if (this.signerOptions.runtime !== "node") - throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"); - return this.getSigv4aSigner().presign(originalRequest, options); + const signer = this.getSigv4aSigner(); + const CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4; + // Check if the SigV4a signer is the CRT implementation, as JS doesn't support presign + if (CrtSignerV4 && signer instanceof CrtSignerV4) { + return signer.presign(originalRequest, options); + } else { + throw new Error( + `presign with signingRegion '*' is only supported when using the CRT dependency @aws-sdk/signature-v4-crt. ` + + `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. ` + + `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + + `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. ` + + `For more information please go to https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt` + ); + } } return this.sigv4Signer.presign(originalRequest, options); } @@ -82,28 +111,54 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner { return this.sigv4Signer.presignWithCredentials(originalRequest, credentials, options); } - private getSigv4aSigner(): InstanceType { + private getSigv4aSigner(): InstanceType | InstanceType { if (!this.sigv4aSigner) { - let CrtSignerV4: OptionalCrtSignerV4 | null = null; + const CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4; + const JsSigV4aSigner = signatureV4aContainer.SignatureV4a; - try { - CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4; - if (typeof CrtSignerV4 !== "function") throw new Error(); - } catch (e) { - e.message = - `${e.message}\n` + - `Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. \n` + - `You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` + - `or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. \n` + - "For more information please go to " + - "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"; - throw e; - } + if (this.signerOptions.runtime === "node") { + if (!CrtSignerV4 && !JsSigV4aSigner) { + throw new Error( + "Neither CRT nor JS SigV4a implementation is available. " + + "Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a. " + + "For more information please go to " + + "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt" + ); + } - this.sigv4aSigner = new CrtSignerV4({ - ...this.signerOptions, - signingAlgorithm: 1, - }); + if (CrtSignerV4 && typeof CrtSignerV4 === "function") { + this.sigv4aSigner = new CrtSignerV4({ + ...this.signerOptions, + signingAlgorithm: 1, + }); + } else if (JsSigV4aSigner && typeof JsSigV4aSigner === "function") { + this.sigv4aSigner = new JsSigV4aSigner({ + ...this.signerOptions, + }); + } else { + throw new Error( + "Available SigV4a implementation is not a valid constructor. " + + "Please ensure you've properly imported @aws-sdk/signature-v4-crt or @smithy/signature-v4a." + + "For more information please go to " + + "https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt" + ); + } + } else { + if (!JsSigV4aSigner || typeof JsSigV4aSigner !== "function") { + throw new Error( + "JS SigV4a implementation is not available or not a valid constructor. " + + "Please check whether you have installed the @smithy/signature-v4a package explicitly. " + + "You must also register the package by calling [require('@smithy/signature-v4a');] " + + "or an ESM equivalent such as [import '@smithy/signature-v4a';]. " + + "For more information please go to " + + "https://github.com/aws/aws-sdk-js-v3#using-javascript-non-crt-implementation-of-sigv4a" + ); + } + + this.sigv4aSigner = new JsSigV4aSigner({ + ...this.signerOptions, + }); + } } return this.sigv4aSigner; } diff --git a/yarn.lock b/yarn.lock index ffe216939662..c4a52165e3ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23524,7 +23524,9 @@ __metadata: version: 0.0.0-use.local resolution: "@aws-sdk/signature-v4-multi-region@workspace:packages/signature-v4-multi-region" dependencies: + "@aws-sdk/client-s3": "npm:*" "@aws-sdk/middleware-sdk-s3": "npm:*" + "@aws-sdk/signature-v4-crt": "npm:*" "@aws-sdk/types": "npm:*" "@smithy/protocol-http": "npm:^5.1.0" "@smithy/signature-v4": "npm:^5.0.2"