diff --git a/clients/client-s3/runtimeConfig.shared.ts b/clients/client-s3/runtimeConfig.shared.ts index f649ac862381..6f66110747f0 100644 --- a/clients/client-s3/runtimeConfig.shared.ts +++ b/clients/client-s3/runtimeConfig.shared.ts @@ -1,4 +1,5 @@ import { defaultRegionInfoProvider } from "./endpoints"; +import { S3SignatureV4 } from "@aws-sdk/middleware-sdk-s3"; import { Logger as __Logger } from "@aws-sdk/types"; import { parseUrl } from "@aws-sdk/url-parser"; import { S3ClientConfig } from "./S3Client"; @@ -12,6 +13,7 @@ export const getRuntimeConfig = (config: S3ClientConfig) => ({ logger: config?.logger ?? ({} as __Logger), regionInfoProvider: config?.regionInfoProvider ?? defaultRegionInfoProvider, serviceId: config?.serviceId ?? "S3", + signerConstructor: config?.signerConstructor ?? S3SignatureV4, signingEscapePath: config?.signingEscapePath ?? false, urlParser: config?.urlParser ?? parseUrl, useArnRegion: config?.useArnRegion ?? false, diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java index 78901ba5f5a7..89c041a58957 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java @@ -87,6 +87,10 @@ public Map> getRuntimeConfigWriters(TypeScrip writer.write("false"); }, "useArnRegion", writer -> { writer.write("false"); + }, "signerConstructor", writer -> { + writer.addDependency(AwsDependency.S3_MIDDLEWARE) + .addImport("S3SignatureV4", "S3SignatureV4", AwsDependency.S3_MIDDLEWARE.packageName) + .write("S3SignatureV4"); }); case NODE: return MapUtils.of("useArnRegion", writer -> { diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts index 53bf929a5ce7..b7ff22808770 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.spec.ts @@ -134,6 +134,7 @@ describe("bucketEndpointMiddleware", () => { clientSigningRegion: mockRegion, useArnRegion: false, isCustomEndpoint: false, + disableMultiregionAccessPoints: false, }); expect(previouslyResolvedConfig.region).toBeCalled(); expect(previouslyResolvedConfig.regionInfoProvider).toBeCalled(); diff --git a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts index 6a8654472827..30e39cf465be 100644 --- a/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts +++ b/packages/middleware-bucket-endpoint/src/bucketEndpointMiddleware.ts @@ -15,81 +15,76 @@ import { bucketHostname } from "./bucketHostname"; import { getPseudoRegion } from "./bucketHostnameUtils"; import { BucketEndpointResolvedConfig } from "./configurations"; -export const bucketEndpointMiddleware = - (options: BucketEndpointResolvedConfig): BuildMiddleware => - ( - next: BuildHandler, - context: HandlerExecutionContext - ): BuildHandler => - async (args: BuildHandlerArguments): Promise> => { - const { Bucket: bucketName } = args.input as { Bucket: string }; - let replaceBucketInPath = options.bucketEndpoint; - const request = args.request; - if (HttpRequest.isInstance(request)) { - if (options.bucketEndpoint) { - request.hostname = bucketName; - } else if (validateArn(bucketName)) { - const bucketArn = parseArn(bucketName); - const clientRegion = getPseudoRegion(await options.region()); - const { partition, signingRegion = clientRegion } = (await options.regionInfoProvider(clientRegion)) || {}; - const useArnRegion = await options.useArnRegion(); - const { - hostname, - bucketEndpoint, - signingRegion: modifiedSigningRegion, - signingService, - } = bucketHostname({ - bucketName: bucketArn, - baseHostname: request.hostname, - accelerateEndpoint: options.useAccelerateEndpoint, - dualstackEndpoint: options.useDualstackEndpoint, - pathStyleEndpoint: options.forcePathStyle, - tlsCompatible: request.protocol === "https:", - useArnRegion, - clientPartition: partition, - clientSigningRegion: signingRegion, - clientRegion: clientRegion, - isCustomEndpoint: options.isCustomEndpoint, - }); +export const bucketEndpointMiddleware = (options: BucketEndpointResolvedConfig): BuildMiddleware => < + Output extends MetadataBearer +>( + next: BuildHandler, + context: HandlerExecutionContext +): BuildHandler => async (args: BuildHandlerArguments): Promise> => { + const { Bucket: bucketName } = args.input as { Bucket: string }; + let replaceBucketInPath = options.bucketEndpoint; + const request = args.request; + if (HttpRequest.isInstance(request)) { + if (options.bucketEndpoint) { + request.hostname = bucketName; + } else if (validateArn(bucketName)) { + const bucketArn = parseArn(bucketName); + const clientRegion = getPseudoRegion(await options.region()); + const { partition, signingRegion = clientRegion } = (await options.regionInfoProvider(clientRegion)) || {}; + const useArnRegion = await options.useArnRegion(); + const { hostname, bucketEndpoint, signingRegion: modifiedSigningRegion, signingService } = bucketHostname({ + bucketName: bucketArn, + baseHostname: request.hostname, + accelerateEndpoint: options.useAccelerateEndpoint, + dualstackEndpoint: options.useDualstackEndpoint, + pathStyleEndpoint: options.forcePathStyle, + tlsCompatible: request.protocol === "https:", + useArnRegion, + clientPartition: partition, + clientSigningRegion: signingRegion, + clientRegion: clientRegion, + isCustomEndpoint: options.isCustomEndpoint, + disableMultiregionAccessPoints: await options.disableMultiregionAccessPoints(), + }); - // If the request needs to use a region or service name inferred from ARN that different from client region, we - // need to set them in the handler context so the signer will use them - if (modifiedSigningRegion && modifiedSigningRegion !== signingRegion) { - context["signing_region"] = modifiedSigningRegion; - } - if (signingService && signingService !== "s3") { - context["signing_service"] = signingService; - } + // If the request needs to use a region or service name inferred from ARN that different from client region, we + // need to set them in the handler context so the signer will use them + if (modifiedSigningRegion && modifiedSigningRegion !== signingRegion) { + context["signing_region"] = modifiedSigningRegion; + } + if (signingService && signingService !== "s3") { + context["signing_service"] = signingService; + } - request.hostname = hostname; - replaceBucketInPath = bucketEndpoint; - } else { - const clientRegion = getPseudoRegion(await options.region()); - const { hostname, bucketEndpoint } = bucketHostname({ - bucketName, - clientRegion, - baseHostname: request.hostname, - accelerateEndpoint: options.useAccelerateEndpoint, - dualstackEndpoint: options.useDualstackEndpoint, - pathStyleEndpoint: options.forcePathStyle, - tlsCompatible: request.protocol === "https:", - isCustomEndpoint: options.isCustomEndpoint, - }); + request.hostname = hostname; + replaceBucketInPath = bucketEndpoint; + } else { + const clientRegion = getPseudoRegion(await options.region()); + const { hostname, bucketEndpoint } = bucketHostname({ + bucketName, + clientRegion, + baseHostname: request.hostname, + accelerateEndpoint: options.useAccelerateEndpoint, + dualstackEndpoint: options.useDualstackEndpoint, + pathStyleEndpoint: options.forcePathStyle, + tlsCompatible: request.protocol === "https:", + isCustomEndpoint: options.isCustomEndpoint, + }); - request.hostname = hostname; - replaceBucketInPath = bucketEndpoint; - } + request.hostname = hostname; + replaceBucketInPath = bucketEndpoint; + } - if (replaceBucketInPath) { - request.path = request.path.replace(/^(\/)?[^\/]+/, ""); - if (request.path === "") { - request.path = "/"; - } + if (replaceBucketInPath) { + request.path = request.path.replace(/^(\/)?[^\/]+/, ""); + if (request.path === "") { + request.path = "/"; } } + } - return next({ ...args, request }); - }; + return next({ ...args, request }); +}; export const bucketEndpointMiddlewareOptions: RelativeMiddlewareOptions = { tags: ["BUCKET_ENDPOINT"], diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts index 6b6fb70befa7..7349e43af692 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.spec.ts @@ -424,10 +424,6 @@ describe("bucketHostname", () => { bucketArn: "arn:aws:s3:us-west-2:123456789012:bucket_name:mybucket", message: "ARN resource should begin with 'accesspoint:' or 'outpost:'", }, - { - bucketArn: "arn:aws:s3::123456789012:accesspoint:myendpoint", - message: "ARN region is empty", - }, { bucketArn: "arn:aws:s3:us-west-2::accesspoint:myendpoint", message: "Access point ARN accountID does not match regex '[0-9]{12}'", @@ -481,6 +477,122 @@ describe("bucketHostname", () => { }); }); + describe("from Multi-region Access Point(MRAP) ARN", () => { + ["us-east-1", "us-west-2", "aws-global"].forEach((region) => { + it(`should populate endpoint from MRAP ARN in region "${region}"`, () => { + const { bucketEndpoint, hostname, signingRegion } = bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap"), + baseHostname: `s3.${region}.amazonaws.com`, + disableMultiregionAccessPoints: false, + clientRegion: region, + isCustomEndpoint: false, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com"); + expect(signingRegion).toBe("*"); + }); + }); + + it('should populate endpoint from MRAP ARN in region "cn-north-2"', () => { + const { bucketEndpoint, hostname, signingRegion } = bucketHostname({ + bucketName: parseArn("arn:aws-cn:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap"), + clientPartition: "aws-cn", + baseHostname: `s3.${region}.amazonaws.com.cn`, + disableMultiregionAccessPoints: false, + clientRegion: region, + isCustomEndpoint: false, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com.cn"); + expect(signingRegion).toBe("*"); + }); + + it("should throw if MRAP ARN is supplied but disabled through options", () => { + expect(() => + bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap"), + baseHostname: `s3.us-west-2.amazonaws.com`, + disableMultiregionAccessPoints: true, + clientRegion: region, + isCustomEndpoint: false, + }) + ).toThrow("SDK is attempting to use a MRAP ARN. Please enable to feature."); + }); + + it("should throw if dualstack option is set", () => { + expect(() => + bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap"), + baseHostname: `s3.us-west-2.amazonaws.com`, + dualstackEndpoint: true, + clientRegion: region, + isCustomEndpoint: false, + }) + ).toThrow("Dualstack endpoint is not supported with Outpost or Multi-region Access Point ARN."); + }); + + it("should throw if accelerate endpoint option is set", () => { + expect(() => + bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap"), + baseHostname: `s3.us-west-2.amazonaws.com`, + accelerateEndpoint: true, + clientRegion: region, + isCustomEndpoint: false, + }) + ).toThrow("Accelerate endpoint is not supported when bucket is an ARN"); + }); + + it("should throw if region is empty and disableMultiregionAccessPoints option is set", () => { + expect(() => + bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:myendpoint"), + baseHostname: `s3.us-west-2.amazonaws.com`, + disableMultiregionAccessPoints: true, + clientRegion: region, + isCustomEndpoint: false, + }) + ).toThrow(""); + }); + + it('should populate endpoint from MRAP ARN with access point name "myendpoint"', () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:myendpoint"), + baseHostname: `s3.us-west-2.amazonaws.com`, + disableMultiregionAccessPoints: false, + clientRegion: region, + isCustomEndpoint: false, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("myendpoint.accesspoint.s3-global.amazonaws.com"); + }); + + it('should populate endpoint from MRAP ARN with access point name "my.bucket"', () => { + const { bucketEndpoint, hostname } = bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:my.bucket"), + baseHostname: `s3.us-west-2.amazonaws.com`, + disableMultiregionAccessPoints: false, + clientRegion: region, + isCustomEndpoint: false, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("my.bucket.accesspoint.s3-global.amazonaws.com"); + }); + + it("should populate endpoint from MRAP ARN with custom endpoint", () => { + const { bucketEndpoint, hostname, signingRegion } = bucketHostname({ + bucketName: parseArn("arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap"), + baseHostname: "vpce-123-abc.vpce.s3-global.amazonaws.com", + isCustomEndpoint: true, + clientRegion: "us-west-2", + disableMultiregionAccessPoints: false, + }); + expect(bucketEndpoint).toBe(true); + expect(hostname).toBe("mfzwi23gnjvgw.mrap.vpce-123-abc.vpce.s3-global.amazonaws.com"); + expect(signingRegion).toBe("*"); + }); + }); + describe("from Outpost ARN", () => { describe("populates access point endpoint from ARN", () => { const s3Hostname = "s3.us-west-2.amazonaws.com"; diff --git a/packages/middleware-bucket-endpoint/src/bucketHostname.ts b/packages/middleware-bucket-endpoint/src/bucketHostname.ts index 58af25f36bbd..6fcbfd971741 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostname.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostname.ts @@ -1,3 +1,5 @@ +import { ARN } from "@aws-sdk/util-arn-parser"; + import { ArnHostnameParams, BucketHostnameParams, @@ -11,7 +13,9 @@ import { isFipsRegion, validateAccountId, validateArnEndpointOptions, + validateCustomEndpoint, validateDNSHostLabel, + validateMrapAlias, validateNoDualstack, validateNoFIPS, validateOutpostService, @@ -30,33 +34,54 @@ export interface BucketHostname { } export const bucketHostname = (options: BucketHostnameParams | ArnHostnameParams): BucketHostname => { - const { isCustomEndpoint, baseHostname, dualstackEndpoint, accelerateEndpoint } = options; - - if (isCustomEndpoint) { - if (dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); - if (accelerateEndpoint) throw new Error("Accelerate endpoint is not supported with custom endpoint"); - } - + validateCustomEndpoint(options); return isBucketNameOptions(options) ? // Construct endpoint when bucketName is a string referring to a bucket name - getEndpointFromBucketName({ ...options, isCustomEndpoint }) + getEndpointFromBucketName(options) : // Construct endpoint when bucketName is an ARN referring to an S3 resource like Access Point - getEndpointFromArn({ ...options, isCustomEndpoint }); + getEndpointFromArn(options); +}; + +const getEndpointFromBucketName = ({ + accelerateEndpoint = false, + clientRegion: region, + baseHostname, + bucketName, + dualstackEndpoint = false, + pathStyleEndpoint = false, + tlsCompatible = true, + isCustomEndpoint = false, +}: BucketHostnameParams): BucketHostname => { + const [clientRegion, hostnameSuffix] = isCustomEndpoint ? [region, baseHostname] : getSuffix(baseHostname); + if (pathStyleEndpoint || !isDnsCompatibleBucketName(bucketName) || (tlsCompatible && DOT_PATTERN.test(bucketName))) { + return { + bucketEndpoint: false, + hostname: dualstackEndpoint ? `s3.dualstack.${clientRegion}.${hostnameSuffix}` : baseHostname, + }; + } + + if (accelerateEndpoint) { + baseHostname = `s3-accelerate${dualstackEndpoint ? ".dualstack" : ""}.${hostnameSuffix}`; + } else if (dualstackEndpoint) { + baseHostname = `s3.dualstack.${clientRegion}.${hostnameSuffix}`; + } + + return { + bucketEndpoint: true, + hostname: `${bucketName}.${baseHostname}`, + }; }; -const getEndpointFromArn = (options: ArnHostnameParams & { isCustomEndpoint: boolean }): BucketHostname => { +const getEndpointFromArn = (options: ArnHostnameParams): BucketHostname => { const { isCustomEndpoint, baseHostname, clientRegion } = options; const hostnameSuffix = isCustomEndpoint ? baseHostname : getSuffixForArnEndpoint(baseHostname)[1]; const { pathStyleEndpoint, - dualstackEndpoint = false, accelerateEndpoint = false, tlsCompatible = true, - useArnRegion, bucketName, clientPartition = "aws", - clientSigningRegion = clientRegion, } = options; validateArnEndpointOptions({ pathStyleEndpoint, accelerateEndpoint, tlsCompatible }); @@ -66,82 +91,133 @@ const getEndpointFromArn = (options: ArnHostnameParams & { isCustomEndpoint: boo validateService(service); validatePartition(partition, { clientPartition }); validateAccountId(accountId); - validateRegionalClient(clientRegion); const { accesspointName, outpostId } = getArnResources(resource); + if (service === "s3-object-lambda") { + return getEndpointFromObjectLambdaArn({ ...options, tlsCompatible, bucketName, accesspointName, hostnameSuffix }); + } + if (region === "") { + return getEndpointFromMRAPArn({ ...options, clientRegion, mrapAlias: accesspointName, hostnameSuffix }); + } + if (outpostId) { + return getEndpointFromOutpostArn({ ...options, clientRegion, outpostId, accesspointName, hostnameSuffix }); + } + return getEndpointFromAccessPointArn({ ...options, clientRegion, accesspointName, hostnameSuffix }); +}; + +const getEndpointFromObjectLambdaArn = ({ + dualstackEndpoint = false, + tlsCompatible = true, + useArnRegion, + clientRegion, + clientSigningRegion = clientRegion, + accesspointName, + bucketName, + hostnameSuffix, +}: ArnHostnameParams & { + accesspointName: string; + bucketName: ARN; + hostnameSuffix: string; +}): BucketHostname => { + const { accountId, region, service } = bucketName; + validateRegionalClient(clientRegion); + validateRegion(region, { useArnRegion, clientRegion, clientSigningRegion, allowFipsRegion: true }); + validateNoDualstack(dualstackEndpoint); const DNSHostLabel = `${accesspointName}-${accountId}`; validateDNSHostLabel(DNSHostLabel, { tlsCompatible }); const endpointRegion = useArnRegion ? region : clientRegion; const signingRegion = useArnRegion ? region : clientSigningRegion; - if (service === "s3-object-lambda") { - validateRegion(region, { useArnRegion, clientRegion, clientSigningRegion, allowFipsRegion: true }); - validateNoDualstack(dualstackEndpoint); - return { - bucketEndpoint: true, - hostname: `${DNSHostLabel}.${service}${isFipsRegion(clientRegion) ? "-fips" : ""}.${getPseudoRegion( - endpointRegion - )}.${hostnameSuffix}`, - signingRegion, - signingService: service, - }; - } else if (outpostId) { - // if this is an Outpost ARN - validateRegion(region, { useArnRegion, clientRegion, clientSigningRegion }); - validateOutpostService(service); - validateDNSHostLabel(outpostId, { tlsCompatible }); - validateNoDualstack(dualstackEndpoint); - validateNoFIPS(endpointRegion); - const hostnamePrefix = `${DNSHostLabel}.${outpostId}`; - return { - bucketEndpoint: true, - hostname: `${hostnamePrefix}${isCustomEndpoint ? "" : `.s3-outposts.${endpointRegion}`}.${hostnameSuffix}`, - signingRegion, - signingService: "s3-outposts", - }; - } - // construct endpoint from Accesspoint ARN - validateRegion(region, { useArnRegion, clientRegion, clientSigningRegion, allowFipsRegion: true }); - validateS3Service(service); - const hostnamePrefix = `${DNSHostLabel}`; + return { bucketEndpoint: true, - hostname: `${hostnamePrefix}${ - isCustomEndpoint - ? "" - : `.s3-accesspoint${isFipsRegion(clientRegion) ? "-fips" : ""}${ - dualstackEndpoint ? ".dualstack" : "" - }.${getPseudoRegion(endpointRegion)}` - }.${hostnameSuffix}`, + hostname: `${DNSHostLabel}.${service}${isFipsRegion(clientRegion) ? "-fips" : ""}.${getPseudoRegion( + endpointRegion + )}.${hostnameSuffix}`, signingRegion, + signingService: service, }; }; -const getEndpointFromBucketName = ({ - accelerateEndpoint = false, - clientRegion: region, - baseHostname, - bucketName, +const getEndpointFromMRAPArn = ({ + disableMultiregionAccessPoints, dualstackEndpoint = false, - pathStyleEndpoint = false, - tlsCompatible = true, - isCustomEndpoint = false, -}: BucketHostnameParams & { isCustomEndpoint: boolean }): BucketHostname => { - const [clientRegion, hostnameSuffix] = isCustomEndpoint ? [region, baseHostname] : getSuffix(baseHostname); - if (pathStyleEndpoint || !isDnsCompatibleBucketName(bucketName) || (tlsCompatible && DOT_PATTERN.test(bucketName))) { - return { - bucketEndpoint: false, - hostname: dualstackEndpoint ? `s3.dualstack.${clientRegion}.${hostnameSuffix}` : baseHostname, - }; + isCustomEndpoint, + mrapAlias, + hostnameSuffix, +}: ArnHostnameParams & { mrapAlias: string; hostnameSuffix: string }): BucketHostname => { + // If this is a multi-regional access point, and not explicitly opted out. + if (disableMultiregionAccessPoints === true) { + throw new Error("SDK is attempting to use a MRAP ARN. Please enable to feature."); } + validateMrapAlias(mrapAlias); + validateNoDualstack(dualstackEndpoint); + return { + bucketEndpoint: true, + hostname: `${mrapAlias}${isCustomEndpoint ? "" : `.accesspoint.s3-global`}.${hostnameSuffix}`, + signingRegion: "*", + }; +}; - if (accelerateEndpoint) { - baseHostname = `s3-accelerate${dualstackEndpoint ? ".dualstack" : ""}.${hostnameSuffix}`; - } else if (dualstackEndpoint) { - baseHostname = `s3.dualstack.${clientRegion}.${hostnameSuffix}`; - } +const getEndpointFromOutpostArn = ({ + useArnRegion, + clientRegion, + clientSigningRegion = clientRegion, + bucketName, + outpostId, + dualstackEndpoint = false, + tlsCompatible = true, + accesspointName, + isCustomEndpoint, + hostnameSuffix, +}: ArnHostnameParams & { outpostId: string; accesspointName: string; hostnameSuffix: string }): BucketHostname => { + // if this is an Outpost ARN + validateRegionalClient(clientRegion); + validateRegion(bucketName.region, { useArnRegion, clientRegion, clientSigningRegion }); + const DNSHostLabel = `${accesspointName}-${bucketName.accountId}`; + validateDNSHostLabel(DNSHostLabel, { tlsCompatible }); + const endpointRegion = useArnRegion ? bucketName.region : clientRegion; + const signingRegion = useArnRegion ? bucketName.region : clientSigningRegion; + validateOutpostService(bucketName.service); + validateDNSHostLabel(outpostId, { tlsCompatible }); + validateNoDualstack(dualstackEndpoint); + validateNoFIPS(endpointRegion); + const hostnamePrefix = `${DNSHostLabel}.${outpostId}`; + return { + bucketEndpoint: true, + hostname: `${hostnamePrefix}${isCustomEndpoint ? "" : `.s3-outposts.${endpointRegion}`}.${hostnameSuffix}`, + signingRegion, + signingService: "s3-outposts", + }; +}; +const getEndpointFromAccessPointArn = ({ + useArnRegion, + clientRegion, + clientSigningRegion = clientRegion, + bucketName, + dualstackEndpoint = false, + tlsCompatible = true, + accesspointName, + isCustomEndpoint, + hostnameSuffix, +}: ArnHostnameParams & { accesspointName: string; hostnameSuffix: string }): BucketHostname => { + // construct endpoint from Accesspoint ARN + validateRegionalClient(clientRegion); + validateRegion(bucketName.region, { useArnRegion, clientRegion, clientSigningRegion, allowFipsRegion: true }); + const hostnamePrefix = `${accesspointName}-${bucketName.accountId}`; + validateDNSHostLabel(hostnamePrefix, { tlsCompatible }); + const endpointRegion = useArnRegion ? bucketName.region : clientRegion; + const signingRegion = useArnRegion ? bucketName.region : clientSigningRegion; + validateS3Service(bucketName.service); return { bucketEndpoint: true, - hostname: `${bucketName}.${baseHostname}`, + hostname: `${hostnamePrefix}${ + isCustomEndpoint + ? "" + : `.s3-accesspoint${isFipsRegion(clientRegion) ? "-fips" : ""}${ + dualstackEndpoint ? ".dualstack" : "" + }.${getPseudoRegion(endpointRegion)}` + }.${hostnameSuffix}`, + signingRegion, }; }; diff --git a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts index eaa1c934ef0d..708bca57af7c 100644 --- a/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts +++ b/packages/middleware-bucket-endpoint/src/bucketHostnameUtils.ts @@ -28,6 +28,7 @@ export interface ArnHostnameParams extends Omit= 64 || - !/^[a-z0-9][a-z0-9.-]+[a-z0-9]$/.test(label) || + !/^[a-z0-9][a-z0-9.-]*[a-z0-9]$/.test(label) || /(\d+\.){3}\d+/.test(label) || /[.-]{2}/.test(label) || (options?.tlsCompatible && DOT_PATTERN.test(label)) @@ -193,6 +194,17 @@ export const validateDNSHostLabel = (label: string, options: { tlsCompatible?: b } }; +export const validateCustomEndpoint = (options: { + isCustomEndpoint?: boolean; + dualstackEndpoint?: boolean; + accelerateEndpoint?: boolean; +}) => { + if (options.isCustomEndpoint) { + if (options.dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with custom endpoint"); + if (options.accelerateEndpoint) throw new Error("Accelerate endpoint is not supported with custom endpoint"); + } +}; + /** * Validate and parse an Access Point ARN or Outposts ARN * @internal @@ -232,14 +244,29 @@ export const getArnResources = ( * Throw if dual stack configuration is set to true. * @internal */ -export const validateNoDualstack = (dualstackEndpoint: boolean) => { - if (dualstackEndpoint) throw new Error("Dualstack endpoint is not supported with Outpost"); +export const validateNoDualstack = (dualstackEndpoint?: boolean) => { + if (dualstackEndpoint) + throw new Error("Dualstack endpoint is not supported with Outpost or Multi-region Access Point ARN."); }; /** * Validate region is not appended or prepended with a `fips-` * @internal */ -export const validateNoFIPS = (region: string) => { +export const validateNoFIPS = (region?: string) => { if (isFipsRegion(region ?? "")) throw new Error(`FIPS region is not supported with Outpost, got ${region}`); }; + +/** + * Validate the multi-region access point alias. + * @private + */ +export const validateMrapAlias = (name: string) => { + try { + name.split(".").forEach((label) => { + validateDNSHostLabel(label); + }); + } catch (e) { + throw new Error(`"${name}" is not a DNS compatible name.`); + } +}; diff --git a/packages/middleware-bucket-endpoint/src/configurations.ts b/packages/middleware-bucket-endpoint/src/configurations.ts index 96f523ac5f36..059fce339dea 100644 --- a/packages/middleware-bucket-endpoint/src/configurations.ts +++ b/packages/middleware-bucket-endpoint/src/configurations.ts @@ -15,13 +15,21 @@ export interface BucketEndpointInputConfig { */ useAccelerateEndpoint?: boolean; /** - * Enables IPv6/IPv4 dualstack endpoint. When a DNS lookup is performed on an endpoint of this type, it returns an “A” record with an IPv4 address and an “AAAA” record with an IPv6 address. In most cases the network stack in the client environment will automatically prefer the AAAA record and make a connection using the IPv6 address. Note, however, that currently on Windows, the IPv4 address will be preferred. + * Enables IPv6/IPv4 dualstack endpoint. When a DNS lookup is performed on an endpoint of this type, it returns an “A” + * record with an IPv4 address and an “AAAA” record with an IPv6 address. In most cases the network stack in the + * client environment will automatically prefer the AAAA record and make a connection using the IPv6 address. Note, + * however, that currently on Windows, the IPv4 address will be preferred. */ useDualstackEndpoint?: boolean; /** * Whether to override the request region with the region inferred from requested resource's ARN. Defaults to false */ useArnRegion?: boolean | Provider; + /** + * Whether to prevent SDK from making cross-region request when supplied bucket is a multi-region access point ARN. + * Defaults to false + */ + disableMultiregionAccessPoints?: boolean | Provider; } interface PreviouslyResolved { @@ -65,6 +73,7 @@ export interface BucketEndpointResolvedConfig { * @internal */ regionInfoProvider: RegionInfoProvider; + disableMultiregionAccessPoints: Provider; } export function resolveBucketEndpointConfig( @@ -76,6 +85,7 @@ export function resolveBucketEndpointConfig( useAccelerateEndpoint = false, useDualstackEndpoint = false, useArnRegion = false, + disableMultiregionAccessPoints = false, } = input; return { ...input, @@ -84,12 +94,19 @@ export function resolveBucketEndpointConfig( useAccelerateEndpoint, useDualstackEndpoint, useArnRegion: typeof useArnRegion === "function" ? useArnRegion : () => Promise.resolve(useArnRegion), + disableMultiregionAccessPoints: + typeof disableMultiregionAccessPoints === "function" + ? disableMultiregionAccessPoints + : () => Promise.resolve(disableMultiregionAccessPoints), }; } export const NODE_USE_ARN_REGION_ENV_NAME = "AWS_S3_USE_ARN_REGION"; export const NODE_USE_ARN_REGION_INI_NAME = "s3_use_arn_region"; +export const NODE_DISABLE_MULTIREGION_ACCESS_POINT_ENV_NAME = "AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS"; +export const NODE_DISABLE_MULTIREGION_ACCESS_POINT_INI_NAME = "s3_disable_multiregion_access_points"; + /** * Config to load useArnRegion from environment variables and shared INI files * @@ -114,3 +131,24 @@ export const NODE_USE_ARN_REGION_CONFIG_OPTIONS: LoadedConfigSelectors }, default: false, }; + +export const NODE_DISABLE_MULTIREGION_ACCESS_POINT_CONFIG_OPTIONS: LoadedConfigSelectors = { + environmentVariableSelector: (env: NodeJS.ProcessEnv) => { + if (!Object.prototype.hasOwnProperty.call(env, NODE_DISABLE_MULTIREGION_ACCESS_POINT_ENV_NAME)) return undefined; + if (env[NODE_DISABLE_MULTIREGION_ACCESS_POINT_ENV_NAME] === "true") return true; + if (env[NODE_DISABLE_MULTIREGION_ACCESS_POINT_ENV_NAME] === "false") return false; + throw new Error( + `Cannot load env ${NODE_DISABLE_MULTIREGION_ACCESS_POINT_ENV_NAME}. Expected "true" or "false", got ${env[NODE_DISABLE_MULTIREGION_ACCESS_POINT_ENV_NAME]}.` + ); + }, + configFileSelector: (profile) => { + if (!Object.prototype.hasOwnProperty.call(profile, NODE_DISABLE_MULTIREGION_ACCESS_POINT_INI_NAME)) + return undefined; + if (profile[NODE_DISABLE_MULTIREGION_ACCESS_POINT_INI_NAME] === "true") return true; + if (profile[NODE_DISABLE_MULTIREGION_ACCESS_POINT_INI_NAME] === "false") return false; + throw new Error( + `Cannot load shared config entry ${NODE_DISABLE_MULTIREGION_ACCESS_POINT_INI_NAME}. Expected "true" or "false", got ${profile[NODE_DISABLE_MULTIREGION_ACCESS_POINT_INI_NAME]}.` + ); + }, + default: false, +}; diff --git a/packages/middleware-sdk-s3/package.json b/packages/middleware-sdk-s3/package.json index e185bff3d372..ef5e8afde93f 100644 --- a/packages/middleware-sdk-s3/package.json +++ b/packages/middleware-sdk-s3/package.json @@ -11,6 +11,12 @@ "main": "./dist/cjs/index.js", "module": "./dist/es/index.js", "types": "./dist/types/index.d.ts", + "browser": { + "@aws-sdk/signature-v4-crt": false + }, + "react-native": { + "@aws-sdk/signature-v4-crt": false + }, "author": { "name": "AWS SDK for JavaScript Team", "url": "https://aws.amazon.com/javascript/" @@ -18,6 +24,8 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/protocol-http": "3.32.0", + "@aws-sdk/signature-v4": "3.32.0", + "@aws-sdk/signature-v4-crt": "3.29.0", "@aws-sdk/types": "3.32.0", "@aws-sdk/util-arn-parser": "3.32.0", "tslib": "^2.3.0" diff --git a/packages/middleware-sdk-s3/src/S3SignatureV4.spec.ts b/packages/middleware-sdk-s3/src/S3SignatureV4.spec.ts new file mode 100644 index 000000000000..985e3219b183 --- /dev/null +++ b/packages/middleware-sdk-s3/src/S3SignatureV4.spec.ts @@ -0,0 +1,75 @@ +import { HttpRequest } from "@aws-sdk/protocol-http"; + +jest.mock("@aws-sdk/signature-v4"); + +jest.mock("@aws-sdk/signature-v4-crt"); + +import { SignatureV4 } from "@aws-sdk/signature-v4"; +import { CrtSignerV4 } from "@aws-sdk/signature-v4-crt"; + +import { S3SignatureV4, S3SignerV4Init } from "./S3SignatureV4"; + +describe("S3SignatureV4", () => { + const params: S3SignerV4Init = { + service: "s3", + region: "us-foo-1", + credentials: { + accessKeyId: "akid", + secretAccessKey: "secret", + }, + sha256: (() => {}) as any, + runtime: "node", + }; + const minimalRequest = new HttpRequest({ + hostname: "bucket.s3.us-foo-1.amazonaws.com", + path: "/key", + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should sign with SigV4 signer", async () => { + const signer = new S3SignatureV4(params); + await signer.sign(minimalRequest); + //@ts-ignore + expect(SignatureV4.mock.instances[0].sign).toBeCalledTimes(1); + }); + + it("should presign with SigV4 signer", async () => { + const signer = new S3SignatureV4(params); + await signer.presign(minimalRequest); + //@ts-ignore + expect(SignatureV4.mock.instances[0].presign).toBeCalledTimes(1); + }); + + it("should sign with SigV4a signer if mult_region option is set", async () => { + const signer = new S3SignatureV4(params); + await signer.presign(minimalRequest, { signingRegion: "*" }); + //@ts-ignore + expect(CrtSignerV4.mock.instances[0].presign).toBeCalledTimes(1); + }); + + it("should presign with SigV4 signer", async () => { + const signer = new S3SignatureV4(params); + await signer.presign(minimalRequest, { signingRegion: "*" }); + //@ts-ignore + expect(CrtSignerV4.mock.instances[0].presign).toBeCalledTimes(1); + }); + + it("should throw if sign with SigV4a in unsupported runtime", async () => { + expect.assertions(1); + const signer = new S3SignatureV4({ ...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 if preSign with SigV4a in unsupported runtime", async () => { + expect.assertions(1); + const signer = new S3SignatureV4({ ...params, runtime: "browser" }); + await expect(signer.presign(minimalRequest, { signingRegion: "*" })).rejects.toThrow( + "This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js" + ); + }); +}); diff --git a/packages/middleware-sdk-s3/src/S3SignatureV4.ts b/packages/middleware-sdk-s3/src/S3SignatureV4.ts new file mode 100644 index 000000000000..bb230f9477a0 --- /dev/null +++ b/packages/middleware-sdk-s3/src/S3SignatureV4.ts @@ -0,0 +1,52 @@ +import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@aws-sdk/signature-v4"; +import { CrtSignerV4 } from "@aws-sdk/signature-v4-crt"; +import { + HttpRequest, + RequestPresigner, + RequestPresigningArguments, + RequestSigner, + RequestSigningArguments, +} from "@aws-sdk/types"; + +export type S3SignerV4Init = SignatureV4Init & + SignatureV4CryptoInit & { + runtime?: string; + }; + +/** + * A SigV4-compatible signer for S3 service. In order to support SigV4a algorithm according to the operation input + * 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. + * @private + */ +export class S3SignatureV4 implements RequestPresigner, RequestSigner { + private readonly sigv4Signer: SignatureV4; + private readonly sigv4aSigner?: CrtSignerV4; + + constructor(options: S3SignerV4Init) { + this.sigv4Signer = new SignatureV4(options); + if (options.runtime === "node") { + this.sigv4aSigner = new CrtSignerV4({ ...options, signingAlgorithm: 1 }); + } + } + + public async sign(requestToSign: HttpRequest, options: RequestSigningArguments = {}): Promise { + if (options.signingRegion === "*") { + if (!this.sigv4aSigner) + throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"); + return this.sigv4aSigner.sign(requestToSign, options); + } + return this.sigv4Signer.sign(requestToSign, options); + } + + public async presign(originalRequest: HttpRequest, options: RequestPresigningArguments = {}): Promise { + if (options.signingRegion === "*") { + if (!this.sigv4aSigner) + throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"); + return this.sigv4aSigner.presign(originalRequest, options); + } + return this.sigv4Signer.presign(originalRequest, options); + } +} diff --git a/packages/middleware-sdk-s3/src/index.ts b/packages/middleware-sdk-s3/src/index.ts index c8479917f61d..86c89fc0ddcf 100644 --- a/packages/middleware-sdk-s3/src/index.ts +++ b/packages/middleware-sdk-s3/src/index.ts @@ -1,3 +1,4 @@ export * from "./validate-bucket-name"; export * from "./use-regional-endpoint"; export * from "./throw-200-exceptions"; +export * from "./S3SignatureV4"; diff --git a/packages/middleware-signing/src/configurations.ts b/packages/middleware-signing/src/configurations.ts index 0af184488ddc..ae99d2e91802 100644 --- a/packages/middleware-signing/src/configurations.ts +++ b/packages/middleware-signing/src/configurations.ts @@ -1,5 +1,5 @@ import { memoize } from "@aws-sdk/property-provider"; -import { SignatureV4 } from "@aws-sdk/signature-v4"; +import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@aws-sdk/signature-v4"; import { Credentials, HashConstructor, Provider, RegionInfo, RegionInfoProvider, RequestSigner } from "@aws-sdk/types"; // 5 minutes buffer time the refresh the credential before it really expires @@ -35,6 +35,13 @@ export interface AwsAuthInputConfig { * can be different to the region in the endpoint. */ signingRegion?: string; + + /** + * The injectable SigV4-compatible signer class constructor. If not supplied, + * regular SignatureV4 constructor will be used. + * @private + */ + signerConstructor?: new (options: SignatureV4Init & SignatureV4CryptoInit) => RequestSigner; } export interface SigV4AuthInputConfig { @@ -121,13 +128,16 @@ export const resolveAwsAuthConfig = ( //user supplied signingName -> endpoints.json inferred (credential scope -> model arnNamespace) -> model service id input.signingName = input.signingName || signingService || input.serviceId; - return new SignatureV4({ + const params: SignatureV4Init & SignatureV4CryptoInit = { + ...input, credentials: normalizedCreds, region: input.signingRegion, service: input.signingName, sha256, uriEscapePath: signingEscapePath, - }); + }; + const signerConstructor = input.signerConstructor || SignatureV4; + return new signerConstructor(params); }); } diff --git a/packages/s3-request-presigner/package.json b/packages/s3-request-presigner/package.json index f356b1dc1137..3d5bbce09d87 100644 --- a/packages/s3-request-presigner/package.json +++ b/packages/s3-request-presigner/package.json @@ -17,6 +17,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.32.0", "@aws-sdk/protocol-http": "3.32.0", "@aws-sdk/signature-v4": "3.32.0", "@aws-sdk/smithy-client": "3.32.0", diff --git a/packages/s3-request-presigner/src/getSignedUrl.spec.ts b/packages/s3-request-presigner/src/getSignedUrl.spec.ts index bb940ffccb28..d9e83426f3fb 100644 --- a/packages/s3-request-presigner/src/getSignedUrl.spec.ts +++ b/packages/s3-request-presigner/src/getSignedUrl.spec.ts @@ -4,8 +4,10 @@ const mockV4 = jest.fn().mockReturnValue({ presign: mockV4Presign, sign: mockV4Sign, }); -jest.mock("@aws-sdk/signature-v4", () => ({ - SignatureV4: mockV4, +jest.mock("@aws-sdk/middleware-sdk-s3", () => ({ + //@ts-ignore + ...jest.requireActual("@aws-sdk/middleware-sdk-s3"), + S3SignatureV4: mockV4, })); import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; @@ -132,4 +134,32 @@ describe("getSignedUrl", () => { expect(mockPresign.mock.calls[0][0].headers[header]).toBeUndefined(); } ); + + it("should presign request with MRAP ARN", async () => { + const mockPresigned = "a presigned url"; + mockPresign.mockReturnValue(mockPresigned); + const client = new S3Client(clientParams); + const command = new GetObjectCommand({ + Bucket: "arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap", + Key: "Key", + }); + await getSignedUrl(client, command); + expect(mockPresign).toBeCalled(); + expect(mockPresign.mock.calls[0][0]).toMatchObject({ + hostname: "mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com", + }); + }); + + it("should throw if presign request with MRAP ARN and disableMultiregionAccessPoints option", () => { + const mockPresigned = "a presigned url"; + mockPresign.mockReturnValue(mockPresigned); + const client = new S3Client({ ...clientParams, disableMultiregionAccessPoints: true }); + const command = new GetObjectCommand({ + Bucket: "arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap", + Key: "Key", + }); + return expect(getSignedUrl(client, command)).rejects.toMatchObject({ + message: "SDK is attempting to use a MRAP ARN. Please enable to feature.", + }); + }); }); diff --git a/packages/s3-request-presigner/src/presigner.ts b/packages/s3-request-presigner/src/presigner.ts index a3197cca3faa..32da9261325e 100644 --- a/packages/s3-request-presigner/src/presigner.ts +++ b/packages/s3-request-presigner/src/presigner.ts @@ -1,32 +1,27 @@ -import { SignatureV4, SignatureV4CryptoInit, SignatureV4Init } from "@aws-sdk/signature-v4"; +import { S3SignatureV4, S3SignerV4Init } from "@aws-sdk/middleware-sdk-s3"; import { RequestPresigner, RequestPresigningArguments } from "@aws-sdk/types"; import { HttpRequest as IHttpRequest } from "@aws-sdk/types"; import { SHA256_HEADER, UNSIGNED_PAYLOAD } from "./constants"; -/** - * PartialBy makes properties specified in K optional in interface T - * see: https://stackoverflow.com/questions/43159887/make-a-single-property-optional-in-typescript - * */ -type Omit = Pick>; type PartialBy = Omit & Partial>; -export type S3RequestPresignerOptions = PartialBy< - SignatureV4Init & SignatureV4CryptoInit, - "service" | "uriEscapePath" -> & { signingName?: string }; +export type S3RequestPresignerOptions = PartialBy & { + signingName?: string; +}; export class S3RequestPresigner implements RequestPresigner { - private readonly signer: SignatureV4; + private readonly signer: S3SignatureV4; constructor(options: S3RequestPresignerOptions) { const resolvedOptions = { // Allow `signingName` because we want to support usecase of supply client's resolved config // directly. Where service equals signingName. service: options.signingName || options.service || "s3", uriEscapePath: options.uriEscapePath || false, + applyChecksum: options.applyChecksum || false, ...options, }; - this.signer = new SignatureV4(resolvedOptions); + this.signer = new S3SignatureV4(resolvedOptions); } public async presign( diff --git a/packages/signature-v4-crt/CHANGELOG.md b/packages/signature-v4-crt/CHANGELOG.md new file mode 100644 index 000000000000..76dafea1058a --- /dev/null +++ b/packages/signature-v4-crt/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.29.0](https://github.com/aws/aws-sdk-js-v3/compare/v3.28.0...v3.29.0) (2021-09-02) + + +### Features + +* **s3:** support generating endpoints from multi-region access point ([#2742](https://github.com/aws/aws-sdk-js-v3/issues/2742)) ([49da47b](https://github.com/aws/aws-sdk-js-v3/commit/49da47b4dfde0f2c68052409aaf808eddcf4e157)) diff --git a/packages/signature-v4-crt/LICENSE b/packages/signature-v4-crt/LICENSE new file mode 100644 index 000000000000..7b6491ba7876 --- /dev/null +++ b/packages/signature-v4-crt/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/signature-v4-crt/README.md b/packages/signature-v4-crt/README.md new file mode 100644 index 000000000000..321870679a4b --- /dev/null +++ b/packages/signature-v4-crt/README.md @@ -0,0 +1,4 @@ +# @aws-sdk/signature-v4 + +[![NPM version](https://img.shields.io/npm/v/@aws-sdk/signature-v4-crt/latest.svg)](https://www.npmjs.com/package/@aws-sdk/signature-v4-crt) +[![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/signature-v4-crt.svg)](https://www.npmjs.com/package/@aws-sdk/signature-v4-crt) diff --git a/packages/signature-v4-crt/jest.config.js b/packages/signature-v4-crt/jest.config.js new file mode 100644 index 000000000000..64f3d932819c --- /dev/null +++ b/packages/signature-v4-crt/jest.config.js @@ -0,0 +1,7 @@ +const base = require("../../jest.config.base.js"); + +module.exports = { + ...base, + //only test cjs dist, avoid testing the package twice + testPathIgnorePatterns: ["/node_modules/", "/es/"], +}; diff --git a/packages/signature-v4-crt/package.json b/packages/signature-v4-crt/package.json new file mode 100644 index 000000000000..2ed84e5dee1c --- /dev/null +++ b/packages/signature-v4-crt/package.json @@ -0,0 +1,47 @@ +{ + "name": "@aws-sdk/signature-v4-crt", + "version": "3.29.0", + "description": "A revision of AWS Signature V4 request signer based on AWS Common Runtime https://github.com/awslabs/aws-crt-nodejs", + "main": "./dist/cjs/index.js", + "module": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "scripts": { + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:es": "tsc -p tsconfig.es.json", + "build": "yarn build:es && yarn build:cjs", + "downlevel-dts": "downlevel-dts dist/types dist/types/ts3.4", + "test": "jest --coverage" + }, + "author": { + "name": "AWS SDK for JavaScript Team", + "url": "https://aws.amazon.com/javascript/" + }, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/is-array-buffer": "3.32.0", + "@aws-sdk/querystring-parser": "3.32.0", + "@aws-sdk/signature-v4": "3.32.0", + "@aws-sdk/util-hex-encoding": "3.32.0", + "@aws-sdk/util-uri-escape": "3.32.0", + "aws-crt": "^1.9.0", + "tslib": "^2.3.0" + }, + "devDependencies": { + "@aws-crypto/sha256-js": "^1.0.0", + "@aws-sdk/protocol-http": "3.32.0", + "@aws-sdk/types": "3.32.0", + "@aws-sdk/util-buffer-from": "3.32.0", + "@types/jest": "^26.0.4", + "jest": "^26.1.0", + "typescript": "~4.3.5" + }, + "engines": { + "node": ">= 10.0.0" + }, + "homepage": "https://github.com/aws/aws-sdk-js-v3/tree/master/packages/signature-v4-crt", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-js-v3.git", + "directory": "packages/signature-v4-crt" + } +} diff --git a/packages/signature-v4-crt/src/CrtSignerV4.spec.ts b/packages/signature-v4-crt/src/CrtSignerV4.spec.ts new file mode 100644 index 000000000000..31ba57fbf136 --- /dev/null +++ b/packages/signature-v4-crt/src/CrtSignerV4.spec.ts @@ -0,0 +1,142 @@ +import { Sha256 } from "@aws-crypto/sha256-js"; +import { auth as crtAuth } from "aws-crt"; + +import { + ALGORITHM_IDENTIFIER_V4A, + ALGORITHM_QUERY_PARAM, + AMZ_DATE_QUERY_PARAM, + CREDENTIAL_QUERY_PARAM, + EXPIRES_QUERY_PARAM, + REGION_SET_PARAM, + SIGNATURE_QUERY_PARAM, + SIGNED_HEADERS_QUERY_PARAM, +} from "./constants"; +import { CrtSignerV4 } from "./CrtSignerV4"; +export const region = "us-east-1"; +export const service = "service"; +export const suiteCredentials = { + accessKeyId: "AKIDEXAMPLE", + secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", +}; + +export const signingDate = new Date("2015-08-30T12:36:00Z"); + +const AUTH_HEADER = "Authorization"; + +describe("SignatureV4A", () => { + /* SigV4A will have different signature every time. We cannot hard code the signature to test against. */ + const signerV4A = new CrtSignerV4({ + signingAlgorithm: crtAuth.AwsSigningAlgorithm.SigV4Asymmetric, + credentials: suiteCredentials, + region, + service, + sha256: Sha256, + applyChecksum: false, + }); + + const ECC_KEY_PUB = { + X: "b6618f6a65740a99e650b33b6b4b5bd0d43b176d721a3edfea7e7d2d56d936b1", + Y: "865ed22a7eadc9c5cb9d2cbaca1b3699139fedc5043dc6661864218330c8e518", + }; + + const SIGV4ATEST_EXPECTED_CANONICAL_REQUEST: string = + "GET" + + "\n" + + "/" + + "\n" + + "\n" + + "host:example.amazonaws.com\n" + + "my-header1:value2,value2,value1" + + "\n" + + "x-amz-date:20150830T123600Z" + + "\n" + + "x-amz-region-set:us-east-1" + + "\n" + + "\n" + + "host;my-header1;x-amz-date;x-amz-region-set" + + "\n" + + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + const SIGV4ATEST_EXPECTED_CANONICAL_PRESIGNED_REQUEST: string = + "GET" + + "\n" + + "/" + + "\n" + + "X-Amz-Algorithm=AWS4-ECDSA-P256-SHA256&X-Amz-Credential=AKIDEXAMPLE%2F20150830%2Fservice%2Faws4_request&X-Amz-Date=20150830T123600Z&X-Amz-Expires=3600&X-Amz-Region-Set=us-east-1&X-Amz-SignedHeaders=host%3Bmy-header1" + + "\n" + + "host:example.amazonaws.com\n" + + "my-header1:value2,value2,value1" + + "\n" + + "\n" + + "host;my-header1" + + "\n" + + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + /* from a complete authorization string to get the signature for verification */ + function getSignature(authorizationString: string): string { + const separator = "Signature="; + const splits = authorizationString.split(separator); + return splits[splits.length - 1]; + } + + const V4aRequest = { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "value2,value2,value1", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }; + + describe("#presignRequest", () => { + const presigningOptions = { + expiresIn: 3600, + signingDate, + }; + + it("should sign requests without bodies", async () => { + const { query } = await signerV4A.presign(V4aRequest, presigningOptions); + expect(query![ALGORITHM_QUERY_PARAM]).toEqual(ALGORITHM_IDENTIFIER_V4A); + expect(query![CREDENTIAL_QUERY_PARAM]).toEqual("AKIDEXAMPLE/20150830/service/aws4_request"); + expect(query![AMZ_DATE_QUERY_PARAM]).toEqual("20150830T123600Z"); + expect(query![EXPIRES_QUERY_PARAM]).toEqual(presigningOptions.expiresIn.toString()); + expect(query![REGION_SET_PARAM]).toEqual(region); + expect(query![SIGNED_HEADERS_QUERY_PARAM]).toEqual("host;my-header1"); + const result = await signerV4A.verifySigv4aPreSigning( + V4aRequest, + query![SIGNATURE_QUERY_PARAM], + SIGV4ATEST_EXPECTED_CANONICAL_PRESIGNED_REQUEST, + ECC_KEY_PUB.X, + ECC_KEY_PUB.Y, + presigningOptions + ); + expect(result).toBe(true); + }); + }); + + describe("#sign (request)", () => { + it("should sign requests without bodies", async () => { + const option = { + signingDate, + }; + const { headers } = await signerV4A.sign(V4aRequest, option); + expect(headers[AUTH_HEADER]).toMatch( + /^AWS4-ECDSA-P256-SHA256 Credential=AKIDEXAMPLE\/20150830\/service\/aws4_request, SignedHeaders=host;my-header1;x-amz-date;x-amz-region-set, Signature=/ + ); + const signature = getSignature(headers[AUTH_HEADER]); + const result = await signerV4A.verifySigv4aSigning( + V4aRequest, + signature, + SIGV4ATEST_EXPECTED_CANONICAL_REQUEST, + ECC_KEY_PUB.X, + ECC_KEY_PUB.Y, + option + ); + expect(result).toBe(true); + }); + }); +}); diff --git a/packages/signature-v4-crt/src/CrtSignerV4.ts b/packages/signature-v4-crt/src/CrtSignerV4.ts new file mode 100644 index 000000000000..02fc8ac6c675 --- /dev/null +++ b/packages/signature-v4-crt/src/CrtSignerV4.ts @@ -0,0 +1,279 @@ +import { parseQueryString } from "@aws-sdk/querystring-parser"; +import { + getCanonicalQuery, + getPayloadHash, + moveHeadersToQuery, + normalizeCredentialsProvider, + normalizeRegionProvider, + prepareRequest, + SignatureV4CryptoInit, + SignatureV4Init, +} from "@aws-sdk/signature-v4"; +import { + Credentials, + HttpRequest, + Provider, + QueryParameterBag, + RequestPresigner, + RequestPresigningArguments, + RequestSigner, + RequestSigningArguments, +} from "@aws-sdk/types"; +import { auth as crtAuth, http as crtHttp, io as crtIO } from "aws-crt"; + +import { MAX_PRESIGNED_TTL, SHA256_HEADER } from "./constants"; +import { deleteHeader } from "./headerUtil"; + +export type AwsSigningAlgorithm = crtAuth.AwsSigningAlgorithm; + +/* private function to convert sdk Http request to crt Http request */ +function sdkHttpRequest2crtHttpRequest(sdkRequest: HttpRequest): crtHttp.HttpRequest { + /* Remove the x-amz-content-sha256 header, if exists */ + deleteHeader(SHA256_HEADER, sdkRequest.headers); + const headersArray = Object.entries(sdkRequest.headers); + const crtHttpHeaders = new crtHttp.HttpHeaders(headersArray); + const queryString = getCanonicalQuery(sdkRequest); + + /** + * Not converting the body to the crtRequest. For now, it's better to get the payload Hash from SDK. + * The body value will be set from config. + */ + return new crtHttp.HttpRequest(sdkRequest.method, sdkRequest.path + "?" + queryString, crtHttpHeaders); +} + +export interface CrtSignerV4Init extends SignatureV4Init { + /** + * The Algorithm used for the signer. Includes: SigV4, SigV4Asymmetric. + * + * @default [SigV4] + */ + signingAlgorithm?: AwsSigningAlgorithm; +} + +/** + * Based aws-crt, with the same API as signing the request from SignatureV4, compatible with request Signer from SDK. + * The difference between them is CrtSignerV4 only supports signing/presigning the request. The behavior of two signers + * are slightly different, includes the case of headers name after signing and the CrtSignerV4 does NOT support overwrite + * the internal check against (x-amzn-trace-id, user-agent), which will always be skipped. + * Most importantly, CrtSignerV4 supports Signature V4 Asymmetric. + * + * Note: aws-crt that supports SigV4A is still a private repo https://github.com/awslabs/aws-crt-nodejs-staging/tree/sigv4a-binding + */ +export class CrtSignerV4 implements RequestPresigner, RequestSigner { + private readonly service: string; + private readonly regionProvider: Provider; + private readonly credentialProvider: Provider; + private readonly sha256: any; + private readonly uriEscapePath: boolean; + private readonly applyChecksum: boolean; + private readonly signingAlgorithm: AwsSigningAlgorithm; + + constructor({ + credentials, + region, + service, + sha256, + applyChecksum = true, + uriEscapePath = true, + signingAlgorithm = crtAuth.AwsSigningAlgorithm.SigV4, + }: CrtSignerV4Init & SignatureV4CryptoInit) { + this.service = service; + this.sha256 = sha256; + this.uriEscapePath = uriEscapePath; + this.signingAlgorithm = signingAlgorithm; + this.applyChecksum = applyChecksum; + this.regionProvider = normalizeRegionProvider(region); + this.credentialProvider = normalizeCredentialsProvider(credentials); + crtIO.enable_logging(crtIO.LogLevel.ERROR); + } + + private async options2crtConfigure( + { + signingDate = new Date(), + signableHeaders, + unsignableHeaders, + signingRegion, + signingService, + }: RequestSigningArguments | RequestPresigningArguments | undefined = {}, + viaHeader: Boolean, + payloadHash: string, + expiresIn?: number + ): Promise { + const credentials = await this.credentialProvider(); + const region = signingRegion ?? (await this.regionProvider()); + const service = signingService ?? this.service; + if (signableHeaders?.has("x-amzn-trace-id") || signableHeaders?.has("user-agent")) { + throw new Error("internal check (x-amzn-trace-id, user-agent) is not supported to be included to sign with CRT."); + } + const headersUnsignable = getHeadersUnsignable(unsignableHeaders, signableHeaders); + return { + algorithm: this.signingAlgorithm, + signature_type: viaHeader + ? crtAuth.AwsSignatureType.HttpRequestViaHeaders + : crtAuth.AwsSignatureType.HttpRequestViaQueryParams, + provider: sdk2crtCredentialsProvider(credentials), + region: region, + service: service, + date: new Date(signingDate), + header_blacklist: headersUnsignable, + use_double_uri_encode: this.uriEscapePath, + /* Always set the body value by the result from SDK */ + signed_body_value: payloadHash, + signed_body_header: + this.applyChecksum && viaHeader + ? crtAuth.AwsSignedBodyHeaderType.XAmzContentSha256 + : crtAuth.AwsSignedBodyHeaderType.None, + expiration_in_seconds: expiresIn, + }; + } + + public async presign(originalRequest: HttpRequest, options: RequestPresigningArguments = {}): Promise { + if (options.expiresIn && options.expiresIn > MAX_PRESIGNED_TTL) { + return Promise.reject( + "Signature version 4 presigned URLs" + " must have an expiration date less than one week in" + " the future" + ); + } + const request = moveHeadersToQuery(prepareRequest(originalRequest)); + + const crtSignedRequest = await this.signRequest( + request, + await this.options2crtConfigure( + options, + false /* viaHeader */, + await getPayloadHash(originalRequest, this.sha256), + options.expiresIn ? options.expiresIn : 3600 + ) + ); + request.query = this.getQueryParam(crtSignedRequest.path); + return request; + } + + public async sign(toSign: HttpRequest, options?: RequestSigningArguments): Promise { + const request = prepareRequest(toSign); + const crtSignedRequest = await this.signRequest( + request, + await this.options2crtConfigure(options, true /* viaHeader */, await getPayloadHash(toSign, this.sha256)) + ); + request.headers = crtSignedRequest.headers._flatten().reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + return request; + } + + /* Get the query parameters from crtPath */ + private getQueryParam(crtPath: string): QueryParameterBag { + const start = crtPath.search(/\?/); + const startHash = crtPath.search(/\#/); + const end = startHash == -1 ? undefined : startHash; + const queryParam = {} as QueryParameterBag; + if (start == -1) { + return queryParam; + } + const queryString = crtPath.slice(start + 1, end); + return parseQueryString(queryString); + } + + private async signRequest( + requestToSign: HttpRequest, + crtConfig: crtAuth.AwsSigningConfig + ): Promise { + const request = sdkHttpRequest2crtHttpRequest(requestToSign); + // if (requestToSign.headers[TOKEN_HEADER]) + try { + return await crtAuth.aws_sign_request(request, crtConfig); + } catch (error) { + throw new Error(error); + } + } + + /** + * Test-only API used for cross-library signing verification tests. Verify sign. + * + * Verifies: + * (1) The canonical request generated during sigv4a signing of the request matches what is passed in + * (2) The signature passed in is a valid ECDSA signature of the hashed string-to-sign derived from the + * canonical request + * + * @param request The original request used for signing + * @param signature the actual signature computed from a previous signing of the signable + * @param expectedCanonicalRequest expected result when building the canonical request + * @param eccPubKeyX the x coordinate of the public part of the ecc key to verify the signature + * @param eccPubKeyY the y coordinate of the public part of the ecc key to verify the signature + * @param options the RequestSigningArguments used for signing + * + * @return True, if the verification succeed. Otherwise, false. + */ + public async verifySigv4aSigning( + request: HttpRequest, + signature: string, + expectedCanonicalRequest: string, + eccPubKeyX: string, + eccPubKeyY: string, + options: RequestSigningArguments = {} + ): Promise { + const sdkRequest = prepareRequest(request); + const crtRequest = sdkHttpRequest2crtHttpRequest(sdkRequest); + const payloadHash = await getPayloadHash(request, this.sha256); + const crtConfig = await this.options2crtConfigure(options, true /* viaHeader */, payloadHash); + return crtAuth.aws_verify_sigv4a_signing( + crtRequest, + crtConfig, + expectedCanonicalRequest, + signature, + eccPubKeyX, + eccPubKeyY + ); + } + + /* Verify presign */ + public async verifySigv4aPreSigning( + request: HttpRequest, + signature: string | Array | null, + expectedCanonicalRequest: string, + eccPubKeyX: string, + eccPubKeyY: string, + options: RequestPresigningArguments = {} + ): Promise { + if (typeof signature != "string") { + return false; + } + const sdkRequest = prepareRequest(request); + const crtRequest = sdkHttpRequest2crtHttpRequest(sdkRequest); + const crtConfig = await this.options2crtConfigure( + options, + false /* viaHeader */, + await getPayloadHash(request, this.sha256), + options.expiresIn ? options.expiresIn : 3600 + ); + return crtAuth.aws_verify_sigv4a_signing( + crtRequest, + crtConfig, + expectedCanonicalRequest, + signature, + eccPubKeyX, + eccPubKeyY + ); + } +} + +function sdk2crtCredentialsProvider(credentials: Credentials): crtAuth.AwsCredentialsProvider { + return crtAuth.AwsCredentialsProvider.newStatic( + credentials.accessKeyId, + credentials.secretAccessKey, + credentials.sessionToken + ); +} + +function getHeadersUnsignable(unsignableHeaders?: Set, signableHeaders?: Set): string[] { + if (!unsignableHeaders) { + return []; + } + if (!signableHeaders) { + return [...unsignableHeaders]; + } + const result = new Set([...unsignableHeaders]); + for (let it = signableHeaders.values(), val = null; (val = it.next().value); ) { + if (result.has(val)) { + result.delete(val); + } + } + return [...result]; +} diff --git a/packages/signature-v4-crt/src/constants.ts b/packages/signature-v4-crt/src/constants.ts new file mode 100644 index 000000000000..e66b35d6367f --- /dev/null +++ b/packages/signature-v4-crt/src/constants.ts @@ -0,0 +1,53 @@ +export const ALGORITHM_QUERY_PARAM = "X-Amz-Algorithm"; +export const CREDENTIAL_QUERY_PARAM = "X-Amz-Credential"; +export const AMZ_DATE_QUERY_PARAM = "X-Amz-Date"; +export const SIGNED_HEADERS_QUERY_PARAM = "X-Amz-SignedHeaders"; +export const EXPIRES_QUERY_PARAM = "X-Amz-Expires"; +export const SIGNATURE_QUERY_PARAM = "X-Amz-Signature"; +export const TOKEN_QUERY_PARAM = "X-Amz-Security-Token"; +export const REGION_SET_PARAM = "X-Amz-Region-Set"; + +export const AUTH_HEADER = "authorization"; +export const AMZ_DATE_HEADER = AMZ_DATE_QUERY_PARAM.toLowerCase(); +export const DATE_HEADER = "date"; +export const GENERATED_HEADERS = [AUTH_HEADER, AMZ_DATE_HEADER, DATE_HEADER]; +export const SIGNATURE_HEADER = SIGNATURE_QUERY_PARAM.toLowerCase(); +export const SHA256_HEADER = "x-amz-content-sha256"; +export const TOKEN_HEADER = TOKEN_QUERY_PARAM.toLowerCase(); +export const HOST_HEADER = "host"; + +export const ALWAYS_UNSIGNABLE_HEADERS = { + authorization: true, + "cache-control": true, + connection: true, + expect: true, + from: true, + "keep-alive": true, + "max-forwards": true, + pragma: true, + referer: true, + te: true, + trailer: true, + "transfer-encoding": true, + upgrade: true, + "user-agent": true, + "x-amzn-trace-id": true, +}; + +export const PROXY_HEADER_PATTERN = /^proxy-/; + +export const SEC_HEADER_PATTERN = /^sec-/; + +export const UNSIGNABLE_PATTERNS = [/^proxy-/i, /^sec-/i]; + +export const ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256"; +export const ALGORITHM_IDENTIFIER_V4A = "AWS4-ECDSA-P256-SHA256"; + +export const EVENT_ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256-PAYLOAD"; + +export const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; + +export const MAX_CACHE_SIZE = 50; +export const KEY_TYPE_IDENTIFIER = "aws4_request"; + +export const MAX_PRESIGNED_TTL = 60 * 60 * 24 * 7; diff --git a/packages/signature-v4-crt/src/crtSuite.spec.ts b/packages/signature-v4-crt/src/crtSuite.spec.ts new file mode 100644 index 000000000000..edf94183e2a7 --- /dev/null +++ b/packages/signature-v4-crt/src/crtSuite.spec.ts @@ -0,0 +1,30 @@ +import { Sha256 } from "@aws-crypto/sha256-js"; +import { HttpRequest } from "@aws-sdk/protocol-http"; + +import { CrtSignerV4 } from "./CrtSignerV4"; +import { getHeaderValue } from "./headerUtil"; +import { credentials, region, requests, service, signingDate } from "./suite.fixture"; + +/** + * Executes the official AWS Signature Version 4 test suite. + * + * @link http://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html + */ +describe("AWS Signature Version 4 Test Suite against CrtSignerV4", () => { + const signer = new CrtSignerV4({ + credentials, + region, + service, + sha256: Sha256, + applyChecksum: false, + }); + + for (const { name, request, authorization } of requests) { + it(`should calculate the correct signature for ${name}`, async () => { + const signed = await signer.sign(new HttpRequest(request), { + signingDate, + }); + expect(getHeaderValue("authorization", signed.headers)).toBe(authorization); + }); + } +}); diff --git a/packages/signature-v4-crt/src/headerUtil.ts b/packages/signature-v4-crt/src/headerUtil.ts new file mode 100644 index 000000000000..001f2ddbf44c --- /dev/null +++ b/packages/signature-v4-crt/src/headerUtil.ts @@ -0,0 +1,34 @@ +import { HeaderBag } from "@aws-sdk/types"; + +export function hasHeader(soughtHeader: string, headers: HeaderBag): boolean { + soughtHeader = soughtHeader.toLowerCase(); + for (const headerName of Object.keys(headers)) { + if (soughtHeader === headerName.toLowerCase()) { + return true; + } + } + + return false; +} + +/* Get the value of one request header, ignore the case. Return string if header is in the headers, else return undefined */ +export function getHeaderValue(soughtHeader: string, headers: HeaderBag): string | undefined { + soughtHeader = soughtHeader.toLowerCase(); + for (const headerName of Object.keys(headers)) { + if (soughtHeader === headerName.toLowerCase()) { + return headers[headerName]; + } + } + + return undefined; +} + +/* Delete the one request header, ignore the case. Do nothing if it's not there */ +export function deleteHeader(soughtHeader: string, headers: HeaderBag) { + soughtHeader = soughtHeader.toLowerCase(); + for (const headerName of Object.keys(headers)) { + if (soughtHeader === headerName.toLowerCase()) { + delete headers[headerName]; + } + } +} diff --git a/packages/signature-v4-crt/src/index.ts b/packages/signature-v4-crt/src/index.ts new file mode 100644 index 000000000000..5d68f8571d2d --- /dev/null +++ b/packages/signature-v4-crt/src/index.ts @@ -0,0 +1 @@ +export * from "./CrtSignerV4"; diff --git a/packages/signature-v4-crt/src/suite.fixture.ts b/packages/signature-v4-crt/src/suite.fixture.ts new file mode 100644 index 000000000000..a1cf7ddf3bc6 --- /dev/null +++ b/packages/signature-v4-crt/src/suite.fixture.ts @@ -0,0 +1,437 @@ +import { HttpRequest } from "@aws-sdk/types"; + +export interface TestCase { + name: string; + request: HttpRequest; + authorization: string; +} + +export const region = "us-east-1"; +export const service = "service"; +export const credentials = { + accessKeyId: "AKIDEXAMPLE", + secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", +}; + +export const signingDate = new Date("2015-08-30T12:36:00Z"); + +export const requests: Array = [ + { + name: "get-header-key-duplicate", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "value2,value2,value1", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c9d5ea9f3f72853aea855b47ea873832890dbdd183b4468f858259531a5138ea", + }, + { + name: "get-header-value-multiline", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "value1,value2,value3", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=ba17b383a53190154eb5fa66a1b836cc297cc0a3d70a5d00705980573d8ff790", + }, + { + name: "get-header-value-order", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "value4,value1,value3,value2", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=08c7e5a9acfcfeb3ab6b2185e75ce8b1deb5e634ec47601a50643f830c755c01", + }, + { + name: "get-header-value-trim", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "value1", + "my-header2": '"a b c"', + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;my-header2;x-amz-date, Signature=acc3ed3afb60bb290fc8d2dd0098b9911fcaa05412b367055dee359757a9c736", + }, + { + name: "get-unreserved", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=07ef7494c76fa4850883e2b006601f940f8a34d404d0cfa977f52a65bbf5f24f", + }, + { + name: "get-utf8", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/ሴ", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=8318018e0b0f223aa2bbf98705b62bb787dc9c0e678f255a891fd03141be5d85", + }, + { + name: "get-vanilla", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31", + }, + { + name: "get-vanilla-empty-query-key", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: { + Param1: "value1", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=a67d582fa61cc504c4bae71f336f98b97f1ea3c7a6bfe1b6e45aec72011b9aeb", + }, + { + name: "get-vanilla-query", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31", + }, + { + name: "get-vanilla-query-order-key-case", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: { + Param2: "value2", + Param1: "value1", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500", + }, + { + name: "get-vanilla-query-unreserved", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: { + "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz": + "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=9c3e54bfcdf0b19771a7f523ee5669cdf59bc7cc0884027167c21bb143a40197", + }, + { + name: "get-vanilla-utf8-query", + request: { + protocol: "https:", + method: "GET", + hostname: "example.amazonaws.com", + query: { + ሴ: "bar", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=2cdec8eed098649ff3a119c94853b13c643bcf08f8b0a1d91e12c9027818dd04", + }, + { + name: "post-header-key-case", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b", + }, + { + name: "post-header-key-sort", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "value1", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c5410059b04c1ee005303aed430f6e6645f61f4dc9e1461ec8f8916fdf18852c", + }, + { + name: "post-header-value-case", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "my-header1": "VALUE1", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=cdbc9802e29d2942e5e10b5bccfdd67c5f22c7c4e8ae67b53629efa58b974b7d", + }, + { + name: "post-sts-header-after", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b", + }, + /** This test can be ignored as it's only needed at IoT WebSocket handshake. + { + name: "post-sts-header-before", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + "x-amz-security-token": + "AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=85d96828115b5dc0cfc3bd16ad9e210dd772bbebba041836c64533a82be05ead", + }, + */ + { + name: "post-vanilla", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b", + }, + { + name: "post-vanilla-empty-query-value", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: { + Param1: "value1", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11", + }, + { + name: "post-vanilla-query", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: { + Param1: "value1", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11", + }, + { + name: "post-vanilla-query-nonunreserved", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: { + "@#$%^": "", + "+": '/,?><`";:\\|][{}', + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=66c82657c86e26fb25238d0e69f011edc4c6df5ae71119d7cb98ed9b87393c1e", + }, + { + name: "post-vanilla-query-space", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: { + p: "", + }, + headers: { + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=e71688addb58a26418614085fb730ba3faa623b461c17f48f2fbdb9361b94a9b", + }, + { + name: "post-x-www-form-urlencoded", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + "content-type": "application/x-www-form-urlencoded", + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + body: "Param1=value1", + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ff11897932ad3f4e8b18135d722051e5ac45fc38421b1da7b9d196a0fe09473a", + }, + { + name: "post-x-www-form-urlencoded-parameters", + request: { + protocol: "https:", + method: "POST", + hostname: "example.amazonaws.com", + query: {}, + headers: { + "content-type": "application/x-www-form-urlencoded; charset=utf8", + host: "example.amazonaws.com", + "x-amz-date": "20150830T123600Z", + }, + body: "Param1=value1", + path: "/", + }, + authorization: + "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=1a72ec8f64bd914b0e42e42607c7fbce7fb2c7465f63e3092b3b0d39fa77a6fe", + }, +]; diff --git a/packages/signature-v4-crt/tsconfig.cjs.json b/packages/signature-v4-crt/tsconfig.cjs.json new file mode 100644 index 000000000000..2ac63587c54b --- /dev/null +++ b/packages/signature-v4-crt/tsconfig.cjs.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "stripInternal": true, + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/cjs", + "noUnusedLocals": true, + "lib": ["es2016"], + "baseUrl": "." + }, + "extends": "../../tsconfig.cjs.json", + "include": ["src/"] +} diff --git a/packages/signature-v4-crt/tsconfig.es.json b/packages/signature-v4-crt/tsconfig.es.json new file mode 100644 index 000000000000..7d0cab3b0ca1 --- /dev/null +++ b/packages/signature-v4-crt/tsconfig.es.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "stripInternal": true, + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/es", + "noUnusedLocals": true, + "baseUrl": ".", + "target": "es5", + "module": "esNext", + "moduleResolution": "node", + "lib": ["es5", "es2015.promise", "es2015.collection"], + "incremental": true + }, + "extends": "../../tsconfig.es.json", + "include": ["src/"] +} diff --git a/packages/signature-v4/src/SignatureV4.ts b/packages/signature-v4/src/SignatureV4.ts index b2fec373f1b7..4f02a58ab67b 100644 --- a/packages/signature-v4/src/SignatureV4.ts +++ b/packages/signature-v4/src/SignatureV4.ts @@ -37,8 +37,9 @@ import { createScope, getSigningKey } from "./credentialDerivation"; import { getCanonicalHeaders } from "./getCanonicalHeaders"; import { getCanonicalQuery } from "./getCanonicalQuery"; import { getPayloadHash } from "./getPayloadHash"; -import { hasHeader } from "./hasHeader"; +import { hasHeader } from "./headerUtil"; import { moveHeadersToQuery } from "./moveHeadersToQuery"; +import { normalizeCredentialsProvider, normalizeRegionProvider } from "./normalizeProvider"; import { prepareRequest } from "./prepareRequest"; import { iso8601 } from "./utilDate"; @@ -317,21 +318,3 @@ const formatDate = (now: DateInput): { longDate: string; shortDate: string } => }; const getCanonicalHeaderList = (headers: object): string => Object.keys(headers).sort().join(";"); - -const normalizeRegionProvider = (region: string | Provider): Provider => { - if (typeof region === "string") { - const promisified = Promise.resolve(region); - return () => promisified; - } else { - return region; - } -}; - -const normalizeCredentialsProvider = (credentials: Credentials | Provider): Provider => { - if (typeof credentials === "object") { - const promisified = Promise.resolve(credentials); - return () => promisified; - } else { - return credentials; - } -}; diff --git a/packages/signature-v4/src/cloneRequest.ts b/packages/signature-v4/src/cloneRequest.ts index f3fc5fa5ecf8..d21d39cd6ddb 100644 --- a/packages/signature-v4/src/cloneRequest.ts +++ b/packages/signature-v4/src/cloneRequest.ts @@ -3,20 +3,17 @@ import { HttpRequest, QueryParameterBag } from "@aws-sdk/types"; /** * @internal */ -export function cloneRequest({ headers, query, ...rest }: HttpRequest): HttpRequest { - return { - ...rest, - headers: { ...headers }, - query: query ? cloneQuery(query) : undefined, - }; -} +export const cloneRequest = ({ headers, query, ...rest }: HttpRequest): HttpRequest => ({ + ...rest, + headers: { ...headers }, + query: query ? cloneQuery(query) : undefined, +}); -function cloneQuery(query: QueryParameterBag): QueryParameterBag { - return Object.keys(query).reduce((carry: QueryParameterBag, paramName: string) => { +export const cloneQuery = (query: QueryParameterBag): QueryParameterBag => + Object.keys(query).reduce((carry: QueryParameterBag, paramName: string) => { const param = query[paramName]; return { ...carry, [paramName]: Array.isArray(param) ? [...param] : param, }; }, {}); -} diff --git a/packages/signature-v4/src/constants.ts b/packages/signature-v4/src/constants.ts index a0dafa4379d6..e66b35d6367f 100644 --- a/packages/signature-v4/src/constants.ts +++ b/packages/signature-v4/src/constants.ts @@ -5,6 +5,7 @@ export const SIGNED_HEADERS_QUERY_PARAM = "X-Amz-SignedHeaders"; export const EXPIRES_QUERY_PARAM = "X-Amz-Expires"; export const SIGNATURE_QUERY_PARAM = "X-Amz-Signature"; export const TOKEN_QUERY_PARAM = "X-Amz-Security-Token"; +export const REGION_SET_PARAM = "X-Amz-Region-Set"; export const AUTH_HEADER = "authorization"; export const AMZ_DATE_HEADER = AMZ_DATE_QUERY_PARAM.toLowerCase(); @@ -40,6 +41,7 @@ export const SEC_HEADER_PATTERN = /^sec-/; export const UNSIGNABLE_PATTERNS = [/^proxy-/i, /^sec-/i]; export const ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256"; +export const ALGORITHM_IDENTIFIER_V4A = "AWS4-ECDSA-P256-SHA256"; export const EVENT_ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256-PAYLOAD"; diff --git a/packages/signature-v4/src/credentialDerivation.ts b/packages/signature-v4/src/credentialDerivation.ts index 049fbee4b305..bdcc311e077a 100644 --- a/packages/signature-v4/src/credentialDerivation.ts +++ b/packages/signature-v4/src/credentialDerivation.ts @@ -13,9 +13,8 @@ const cacheQueue: Array = []; * @param region The AWS region in which the service resides. * @param service The service to which the signed request is being sent. */ -export function createScope(shortDate: string, region: string, service: string): string { - return `${shortDate}/${region}/${service}/${KEY_TYPE_IDENTIFIER}`; -} +export const createScope = (shortDate: string, region: string, service: string): string => + `${shortDate}/${region}/${service}/${KEY_TYPE_IDENTIFIER}`; /** * Derive a signing key from its composite parts @@ -57,15 +56,15 @@ export const getSigningKey = async ( /** * @internal */ -export function clearCredentialCache(): void { +export const clearCredentialCache = (): void => { cacheQueue.length = 0; Object.keys(signingKeyCache).forEach((cacheKey) => { delete signingKeyCache[cacheKey]; }); -} +}; -function hmac(ctor: HashConstructor, secret: SourceData, data: SourceData): Promise { +const hmac = (ctor: HashConstructor, secret: SourceData, data: SourceData): Promise => { const hash = new ctor(secret); hash.update(data); return hash.digest(); -} +}; diff --git a/packages/signature-v4/src/getCanonicalHeaders.ts b/packages/signature-v4/src/getCanonicalHeaders.ts index b0c0fe230c70..927d2987c814 100644 --- a/packages/signature-v4/src/getCanonicalHeaders.ts +++ b/packages/signature-v4/src/getCanonicalHeaders.ts @@ -3,13 +3,13 @@ import { HeaderBag, HttpRequest } from "@aws-sdk/types"; import { ALWAYS_UNSIGNABLE_HEADERS, PROXY_HEADER_PATTERN, SEC_HEADER_PATTERN } from "./constants"; /** - * @internal + * @private */ -export function getCanonicalHeaders( +export const getCanonicalHeaders = ( { headers }: HttpRequest, unsignableHeaders?: Set, signableHeaders?: Set -): HeaderBag { +): HeaderBag => { const canonical: HeaderBag = {}; for (const headerName of Object.keys(headers).sort()) { const canonicalHeaderName = headerName.toLowerCase(); @@ -28,4 +28,4 @@ export function getCanonicalHeaders( } return canonical; -} +}; diff --git a/packages/signature-v4/src/getCanonicalQuery.ts b/packages/signature-v4/src/getCanonicalQuery.ts index 0e1db62b71c7..b9287de12cf9 100644 --- a/packages/signature-v4/src/getCanonicalQuery.ts +++ b/packages/signature-v4/src/getCanonicalQuery.ts @@ -4,9 +4,9 @@ import { escapeUri } from "@aws-sdk/util-uri-escape"; import { SIGNATURE_HEADER } from "./constants"; /** - * @internal + * @private */ -export function getCanonicalQuery({ query = {} }: HttpRequest): string { +export const getCanonicalQuery = ({ query = {} }: HttpRequest): string => { const keys: Array = []; const serialized: { [key: string]: string } = {}; for (const key of Object.keys(query).sort()) { @@ -34,4 +34,4 @@ export function getCanonicalQuery({ query = {} }: HttpRequest): string { .map((key) => serialized[key]) .filter((serialized) => serialized) // omit any falsy values .join("&"); -} +}; diff --git a/packages/signature-v4/src/getPayloadHash.ts b/packages/signature-v4/src/getPayloadHash.ts index b185600c5634..2846002b2966 100644 --- a/packages/signature-v4/src/getPayloadHash.ts +++ b/packages/signature-v4/src/getPayloadHash.ts @@ -5,12 +5,12 @@ import { toHex } from "@aws-sdk/util-hex-encoding"; import { SHA256_HEADER, UNSIGNED_PAYLOAD } from "./constants"; /** - * @internal + * @private */ -export async function getPayloadHash( +export const getPayloadHash = async ( { headers, body }: HttpRequest, hashConstructor: HashConstructor -): Promise { +): Promise => { for (const headerName of Object.keys(headers)) { if (headerName.toLowerCase() === SHA256_HEADER) { return headers[headerName]; @@ -29,4 +29,4 @@ export async function getPayloadHash( // body is unsignable. Attempt to send the request with an unsigned payload, // which may or may not be accepted by the service. return UNSIGNED_PAYLOAD; -} +}; diff --git a/packages/signature-v4/src/hasHeader.ts b/packages/signature-v4/src/hasHeader.ts deleted file mode 100644 index a8c2ab0f806d..000000000000 --- a/packages/signature-v4/src/hasHeader.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { HeaderBag } from "@aws-sdk/types"; - -export function hasHeader(soughtHeader: string, headers: HeaderBag): boolean { - soughtHeader = soughtHeader.toLowerCase(); - for (const headerName of Object.keys(headers)) { - if (soughtHeader === headerName.toLowerCase()) { - return true; - } - } - - return false; -} diff --git a/packages/signature-v4/src/headerUtil.ts b/packages/signature-v4/src/headerUtil.ts new file mode 100644 index 000000000000..c4423c511fdd --- /dev/null +++ b/packages/signature-v4/src/headerUtil.ts @@ -0,0 +1,34 @@ +import { HeaderBag } from "@aws-sdk/types"; + +export const hasHeader = (soughtHeader: string, headers: HeaderBag): boolean => { + soughtHeader = soughtHeader.toLowerCase(); + for (const headerName of Object.keys(headers)) { + if (soughtHeader === headerName.toLowerCase()) { + return true; + } + } + + return false; +}; + +/* Get the value of one request header, ignore the case. Return string if header is in the headers, else return undefined */ +export const getHeaderValue = (soughtHeader: string, headers: HeaderBag): string | undefined => { + soughtHeader = soughtHeader.toLowerCase(); + for (const headerName of Object.keys(headers)) { + if (soughtHeader === headerName.toLowerCase()) { + return headers[headerName]; + } + } + + return undefined; +}; + +/* Delete the one request header, ignore the case. Do nothing if it's not there */ +export const deleteHeader = (soughtHeader: string, headers: HeaderBag) => { + soughtHeader = soughtHeader.toLowerCase(); + for (const headerName of Object.keys(headers)) { + if (soughtHeader === headerName.toLowerCase()) { + delete headers[headerName]; + } + } +}; diff --git a/packages/signature-v4/src/index.ts b/packages/signature-v4/src/index.ts index 516324e6ff76..e8dc0d15a642 100644 --- a/packages/signature-v4/src/index.ts +++ b/packages/signature-v4/src/index.ts @@ -1,2 +1,8 @@ export * from "./credentialDerivation"; +export { getCanonicalHeaders } from "./getCanonicalHeaders"; +export { getCanonicalQuery } from "./getCanonicalQuery"; +export { getPayloadHash } from "./getPayloadHash"; +export { moveHeadersToQuery } from "./moveHeadersToQuery"; +export { prepareRequest } from "./prepareRequest"; +export { normalizeCredentialsProvider, normalizeRegionProvider } from "./normalizeProvider"; export * from "./SignatureV4"; diff --git a/packages/signature-v4/src/moveHeadersToQuery.ts b/packages/signature-v4/src/moveHeadersToQuery.ts index 2404c98315ca..9cf948e00221 100644 --- a/packages/signature-v4/src/moveHeadersToQuery.ts +++ b/packages/signature-v4/src/moveHeadersToQuery.ts @@ -3,12 +3,12 @@ import { HttpRequest, QueryParameterBag } from "@aws-sdk/types"; import { cloneRequest } from "./cloneRequest"; /** - * @internal + * @private */ -export function moveHeadersToQuery( +export const moveHeadersToQuery = ( request: HttpRequest, options: { unhoistableHeaders?: Set } = {} -): HttpRequest & { query: QueryParameterBag } { +): HttpRequest & { query: QueryParameterBag } => { const { headers, query = {} as QueryParameterBag } = typeof (request as any).clone === "function" ? (request as any).clone() : cloneRequest(request); for (const name of Object.keys(headers)) { @@ -24,4 +24,4 @@ export function moveHeadersToQuery( headers, query, }; -} +}; diff --git a/packages/signature-v4/src/normalizeProvider.ts b/packages/signature-v4/src/normalizeProvider.ts new file mode 100644 index 000000000000..3e5d9602718a --- /dev/null +++ b/packages/signature-v4/src/normalizeProvider.ts @@ -0,0 +1,27 @@ +import { Credentials, Provider } from "@aws-sdk/types"; + +/** + * @private + */ +export const normalizeRegionProvider = (region: string | Provider): Provider => { + if (typeof region === "string") { + const promisified = Promise.resolve(region); + return () => promisified; + } else { + return region; + } +}; + +/** + * @private + */ +export const normalizeCredentialsProvider = ( + credentials: Credentials | Provider +): Provider => { + if (typeof credentials === "object") { + const promisified = Promise.resolve(credentials); + return () => promisified; + } else { + return credentials; + } +}; diff --git a/packages/signature-v4/src/prepareRequest.ts b/packages/signature-v4/src/prepareRequest.ts index 4eb34e5be7be..91bf32880496 100644 --- a/packages/signature-v4/src/prepareRequest.ts +++ b/packages/signature-v4/src/prepareRequest.ts @@ -4,9 +4,9 @@ import { cloneRequest } from "./cloneRequest"; import { GENERATED_HEADERS } from "./constants"; /** - * @internal + * @private */ -export function prepareRequest(request: HttpRequest): HttpRequest { +export const prepareRequest = (request: HttpRequest): HttpRequest => { // Create a clone of the request object that does not clone the body request = typeof (request as any).clone === "function" ? (request as any).clone() : cloneRequest(request); @@ -17,4 +17,4 @@ export function prepareRequest(request: HttpRequest): HttpRequest { } return request; -} +}; diff --git a/packages/signature-v4/src/utilDate.ts b/packages/signature-v4/src/utilDate.ts index 80a781139b8b..cbb5ba69088a 100644 --- a/packages/signature-v4/src/utilDate.ts +++ b/packages/signature-v4/src/utilDate.ts @@ -1,10 +1,9 @@ -export function iso8601(time: number | string | Date): string { - return toDate(time) +export const iso8601 = (time: number | string | Date): string => + toDate(time) .toISOString() .replace(/\.\d{3}Z$/, "Z"); -} -export function toDate(time: number | string | Date): Date { +export const toDate = (time: number | string | Date): Date => { if (typeof time === "number") { return new Date(time * 1000); } @@ -17,4 +16,4 @@ export function toDate(time: number | string | Date): Date { } return time; -} +}; diff --git a/packages/signature-v4/tsconfig.cjs.json b/packages/signature-v4/tsconfig.cjs.json index 89682bee9296..1db0b864fe99 100644 --- a/packages/signature-v4/tsconfig.cjs.json +++ b/packages/signature-v4/tsconfig.cjs.json @@ -4,7 +4,6 @@ "declarationDir": "./dist/types", "rootDir": "./src", "outDir": "./dist/cjs", - "noUnusedLocals": true, "baseUrl": "." }, "extends": "../../tsconfig.cjs.json", diff --git a/yarn.lock b/yarn.lock index 050911ebaf1e..b5f1a880bb8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2366,6 +2366,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi@^0.3.0, ansi@~0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21" + integrity sha1-DELU+xcWDVqa8eSEus4cZpIsGyE= + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -2402,6 +2407,14 @@ aproba@^2.0.0: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +are-we-there-yet@~1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.0.6.tgz#a2d28c93102aa6cc96245a26cb954de06ec53f0c" + integrity sha1-otKMkxAqpsyWJFomy5VN4G7FPww= + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.0 || ^1.1.13" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -2578,6 +2591,11 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async@3.2.0, async@^3.0.1: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" @@ -2610,6 +2628,18 @@ available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" +aws-crt@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/aws-crt/-/aws-crt-1.9.0.tgz#227f68711b02c7da9cad60655f8ea262231b1ba0" + integrity sha512-LTjO3BRrPRcVw1gwXYAEMW7rG1u2rb7dUGscC/+wAAecbbZ6wTmETHteSdC184nq0MnrM0s9G7RaoTuFSAXhwQ== + dependencies: + axios "^0.21.1" + cmake-js "6.1.0" + crypto-js "^4.0.0" + fastestsmallesttextencoderdecoder "^1.0.22" + mqtt "^4.2.8" + websocket-stream "^5.5.2" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2620,6 +2650,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + babel-jest@^26.6.3: version "26.6.3" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" @@ -2741,6 +2778,11 @@ before-after-hook@^2.0.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c" integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw== +big-integer@^1.6.17: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -2756,6 +2798,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -2763,7 +2813,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.0.3, bl@^4.1.0: +bl@^4.0.2, bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -2782,6 +2832,11 @@ bluebird@^3.0.6, bluebird@^3.4.1, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3. resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -2972,6 +3027,16 @@ buffer-from@1.x, buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + +buffer-shims@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + integrity sha1-mXjOMXOIxkmth5MCjDR37wRKi1E= + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2994,6 +3059,11 @@ buffer@^5.2.1, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -3133,7 +3203,7 @@ camelcase-keys@^6.2.2: map-obj "^4.0.0" quick-lru "^4.0.1" -camelcase@^2.0.0: +camelcase@^2.0.0, camelcase@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= @@ -3189,6 +3259,13 @@ chai@^4.2.0: pathval "^1.1.1" type-detect "^4.0.5" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.1.0, chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -3340,6 +3417,15 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== +cliui@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3381,6 +3467,27 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +cmake-js@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cmake-js/-/cmake-js-6.1.0.tgz#bec7381b58d454acee09d4fb0047153a005063a6" + integrity sha512-utmukLQftpgrCpGRCaHnkv4K27HZNNFqmBl4vnvccy0xp4c1erxjFU/Lq4wn5ngAhFZmpwBPQfoKWKThjSBiwg== + dependencies: + debug "^4" + fs-extra "^5.0.0" + is-iojs "^1.0.1" + lodash "^4" + memory-stream "0" + npmlog "^1.2.0" + rc "^1.2.7" + request "^2.54.0" + semver "^5.0.3" + splitargs "0" + tar "^4" + unzipper "^0.8.13" + url-join "0" + which "^1.0.9" + yargs "^3.6.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3494,6 +3601,14 @@ commander@^7.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/commist/-/commist-1.1.0.tgz#17811ec6978f6c15ee4de80c45c9beb77cee35d5" + integrity sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg== + dependencies: + leven "^2.1.0" + minimist "^1.1.0" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3878,6 +3993,11 @@ crypto-browserify@^3.11.0, crypto-browserify@^3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + cssom@^0.4.1, cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -4037,7 +4157,7 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@4.3.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: +debug@4, debug@4.3.1, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -4078,7 +4198,7 @@ decamelize-keys@^1.0.0, decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -4110,6 +4230,11 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -4349,12 +4474,19 @@ dtrace-provider@~0.8: dependencies: nan "^2.14.0" +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.4.2, duplexify@^3.6.0: +duplexify@^3.4.2, duplexify@^3.5.1, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== @@ -4364,6 +4496,16 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +duplexify@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + duration@^0.2.1: version "0.2.2" resolved "https://registry.yarnpkg.com/duration/-/duration-0.2.2.tgz#ddf149bc3bc6901150fe9017111d016b3357f529" @@ -5089,6 +5231,11 @@ fast-xml-parser@3.19.0: resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01" integrity sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg== +fastestsmallesttextencoderdecoder@^1.0.22: + version "1.0.22" + resolved "https://registry.yarnpkg.com/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz#59b47e7b965f45258629cc6c127bf783281c5e93" + integrity sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw== + fastq@^1.6.0: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" @@ -5281,6 +5428,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== +follow-redirects@^1.10.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" + integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5335,6 +5487,15 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -5389,6 +5550,16 @@ fsevents@^2.1.2, fsevents@~2.3.1: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fstream@~1.0.10: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -5399,6 +5570,17 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gauge@~1.2.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93" + integrity sha1-6c7FSD09TuDvRLYKfZnkk14TbZM= + dependencies: + ansi "^0.3.0" + has-unicode "^2.0.0" + lodash.pad "^4.1.0" + lodash.padend "^4.1.0" + lodash.padstart "^4.1.0" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -5870,6 +6052,14 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +help-me@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/help-me/-/help-me-3.0.0.tgz#9803c81b5f346ad2bce2c6a0ba01b82257d319e8" + integrity sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ== + dependencies: + glob "^7.1.6" + readable-stream "^3.6.0" + highlight.js@^10.2.0: version "10.7.2" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" @@ -6172,7 +6362,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6187,7 +6377,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.2, ini@^1.3.4, ini@^1.3.5: +ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -6237,6 +6427,11 @@ interpret@^1.0.0, interpret@^1.4.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -6456,6 +6651,11 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-iojs@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-iojs/-/is-iojs-1.1.0.tgz#4c11033b5d5d94d6eab3775dedc9be7d008325f1" + integrity sha1-TBEDO11dlNbqs3dd7cm+fQCDJfE= + is-nan@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" @@ -6634,6 +6834,11 @@ is-wsl@^2.1.0, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7524,6 +7729,13 @@ knuth-shuffle-seeded@^1.0.6: dependencies: seed-random "~2.2.0" +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + lerna@3.22.1: version "3.22.1" resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62" @@ -7632,6 +7844,11 @@ levelup@^4.3.2: level-supports "~1.0.0" xtend "~4.0.0" +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -7679,6 +7896,11 @@ lint-staged@^10.0.1: string-argv "0.3.1" stringify-object "^3.3.0" +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + listr2@^3.2.2: version "3.7.1" resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.7.1.tgz#ff0c410b10eb1c5c76735e4814128ec8f7d2b983" @@ -7842,6 +8064,21 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= +lodash.pad@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70" + integrity sha1-QzCUmoM6fI2iLMIPaibE1Z3runA= + +lodash.padend@^4.1.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" + integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= + +lodash.padstart@^4.1.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b" + integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs= + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -7877,7 +8114,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.21, lodash@4.x, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.2.1, lodash@^4.7.0: +lodash@4.17.21, lodash@4.x, lodash@^4, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.2.1, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8090,6 +8327,13 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +memory-stream@0: + version "0.0.3" + resolved "https://registry.yarnpkg.com/memory-stream/-/memory-stream-0.0.3.tgz#ebe8dd1c3b8bc38c0e7941e9ddd5aebe6b4de83f" + integrity sha1-6+jdHDuLw4wOeUHp3dWuvmtN6D8= + dependencies: + readable-stream "~1.0.26-2" + meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -8264,7 +8508,7 @@ minimist-options@^3.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -8325,7 +8569,7 @@ mkdirp@*, mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@0.5.5, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@0.5.5, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -8399,6 +8643,35 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +mqtt-packet@^6.8.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.10.0.tgz#c8b507832c4152e3e511c0efa104ae4a64cd418f" + integrity sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA== + dependencies: + bl "^4.0.2" + debug "^4.1.1" + process-nextick-args "^2.0.1" + +mqtt@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-4.2.8.tgz#f0e54b138bcdaef6c55c547b3a4de9cf9074208c" + integrity sha512-DJYjlXODVXtSDecN8jnNzi6ItX3+ufGsEs9OB3YV24HtkRrh7kpx8L5M1LuyF0KzaiGtWr2PzDcMGAY60KGOSA== + dependencies: + commist "^1.0.0" + concat-stream "^2.0.0" + debug "^4.1.1" + duplexify "^4.1.1" + help-me "^3.0.0" + inherits "^2.0.3" + minimist "^1.2.5" + mqtt-packet "^6.8.0" + pump "^3.0.0" + readable-stream "^3.6.0" + reinterval "^1.1.0" + split2 "^3.1.0" + ws "^7.5.0" + xtend "^4.0.2" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8741,6 +9014,15 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" +npmlog@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-1.2.1.tgz#28e7be619609b53f7ad1dd300a10d64d716268b6" + integrity sha1-KOe+YZYJtT960d0wChDWTXFiaLY= + dependencies: + ansi "~0.3.0" + are-we-there-yet "~1.0.0" + gauge "~1.2.0" + npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -8935,6 +9217,13 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + os-name@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -9417,11 +9706,16 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -process-nextick-args@~2.0.0: +process-nextick-args@^2.0.1, process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -9672,6 +9966,16 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" @@ -9763,7 +10067,7 @@ read@1, read@~1.0.1: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -9785,6 +10089,29 @@ read@1, read@~1.0.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@~1.0.26-2: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" + integrity sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA= + dependencies: + buffer-shims "^1.0.0" + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -9860,6 +10187,11 @@ regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +reinterval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7" + integrity sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc= + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -9924,7 +10256,7 @@ request@2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@2.88.2, request@^2.88.0, request@^2.88.2: +request@2.88.2, request@^2.54.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -10063,7 +10395,7 @@ rfdc@^1.1.4: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: +rimraf@2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -10203,7 +10535,7 @@ semver-regex@^3.1.2: resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA== -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -10297,7 +10629,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -10621,7 +10953,7 @@ split2@^2.0.0: dependencies: through2 "^2.0.2" -split2@^3.0.0: +split2@^3.0.0, split2@^3.1.0: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== @@ -10635,6 +10967,11 @@ split@^1.0.0: dependencies: through "2" +splitargs@0: + version "0.0.7" + resolved "https://registry.yarnpkg.com/splitargs/-/splitargs-0.0.7.tgz#fe9f7ae657371b33b10cb80da143cf8249cf6b3b" + integrity sha1-/p965lc3GzOxDLgNoUPPgknPazs= + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -10863,6 +11200,11 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -10958,6 +11300,11 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + strong-log-transformer@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" @@ -11051,7 +11398,7 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: +tar@^4, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -11340,6 +11687,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -11562,6 +11914,11 @@ uid-number@0.0.6: resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + umask@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" @@ -11662,6 +12019,21 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unzipper@^0.8.13: + version "0.8.14" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.8.14.tgz#ade0524cd2fc14d11b8de258be22f9d247d3f79b" + integrity sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "~1.0.10" + listenercount "~1.0.1" + readable-stream "~2.1.5" + setimmediate "~1.0.4" + upath@^1.1.1, upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -11684,6 +12056,11 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-join@0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" + integrity sha1-HbSK1CLTQCRpqH99l73r/k+x48g= + url-parse@^1.4.4: version "1.5.3" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" @@ -12027,6 +12404,18 @@ webpack@^4.43.0: watchpack "^1.7.4" webpack-sources "^1.4.1" +websocket-stream@^5.5.2: + version "5.5.2" + resolved "https://registry.yarnpkg.com/websocket-stream/-/websocket-stream-5.5.2.tgz#49d87083d96839f0648f5513bbddd581f496b8a2" + integrity sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ== + dependencies: + duplexify "^3.5.1" + inherits "^2.0.1" + readable-stream "^2.3.3" + safe-buffer "^5.1.2" + ws "^3.2.0" + xtend "^4.0.0" + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -12098,7 +12487,7 @@ which@2.0.2, which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@^1.0.9, which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -12112,6 +12501,11 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +window-size@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= + windows-release@^3.1.0: version "3.3.3" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" @@ -12141,6 +12535,14 @@ workerpool@6.1.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -12224,11 +12626,25 @@ write-pkg@^3.1.0: sort-keys "^2.0.0" write-json-file "^2.2.0" +ws@^3.2.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + ws@^7.0.0, ws@^7.2.3, ws@^7.4.4, ws@~7.4.2: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@^7.5.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -12256,6 +12672,11 @@ xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" @@ -12388,6 +12809,19 @@ yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^3.6.0: + version "3.32.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= + dependencies: + camelcase "^2.0.1" + cliui "^3.0.3" + decamelize "^1.1.1" + os-locale "^1.4.0" + string-width "^1.0.1" + window-size "^0.1.4" + y18n "^3.2.0" + yarn@1.22.10: version "1.22.10" resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.10.tgz#c99daa06257c80f8fa2c3f1490724e394c26b18c"