diff --git a/packages/middleware-flexible-checksums/package.json b/packages/middleware-flexible-checksums/package.json index 3a0dbcc9d20d..8254d604d6a4 100644 --- a/packages/middleware-flexible-checksums/package.json +++ b/packages/middleware-flexible-checksums/package.json @@ -28,6 +28,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/node-config-provider": "^3.1.5", "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-sdk/types": "*", diff --git a/packages/middleware-flexible-checksums/src/NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS.ts b/packages/middleware-flexible-checksums/src/NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS.ts new file mode 100644 index 000000000000..ac105c94d97a --- /dev/null +++ b/packages/middleware-flexible-checksums/src/NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS.ts @@ -0,0 +1,16 @@ +import { LoadedConfigSelectors } from "@smithy/node-config-provider"; + +import { RequestChecksumCalculation } from "./constants"; +import { SelectorType, stringUnionSelector } from "./stringUnionSelector"; + +export const ENV_REQUEST_CHECKSUM_CALCULATION = "AWS_REQUEST_CHECKSUM_CALCULATION"; +export const CONFIG_REQUEST_CHECKSUM_CALCULATION = "request_checksum_calculation"; +export const DEFAULT_REQUEST_CHECKSUM_CALCULATION = RequestChecksumCalculation.WHEN_SUPPORTED; + +export const NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS: LoadedConfigSelectors = { + environmentVariableSelector: (env) => + stringUnionSelector(env, ENV_REQUEST_CHECKSUM_CALCULATION, RequestChecksumCalculation, SelectorType.ENV), + configFileSelector: (profile) => + stringUnionSelector(profile, CONFIG_REQUEST_CHECKSUM_CALCULATION, RequestChecksumCalculation, SelectorType.CONFIG), + default: DEFAULT_REQUEST_CHECKSUM_CALCULATION, +}; diff --git a/packages/middleware-flexible-checksums/src/NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS.ts b/packages/middleware-flexible-checksums/src/NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS.ts new file mode 100644 index 000000000000..197b4ea6d052 --- /dev/null +++ b/packages/middleware-flexible-checksums/src/NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS.ts @@ -0,0 +1,16 @@ +import { LoadedConfigSelectors } from "@smithy/node-config-provider"; + +import { RequestChecksumCalculation } from "./constants"; +import { SelectorType, stringUnionSelector } from "./stringUnionSelector"; + +export const ENV_RESPONSE_CHECKSUM_VALIDATION = "AWS_RESPONSE_CHECKSUM_VALIDATION"; +export const CONFIG_RESPONSE_CHECKSUM_VALIDATION = "response_checksum_validation"; +export const DEFAULT_RESPONSE_CHECKSUM_VALIDATION = RequestChecksumCalculation.WHEN_SUPPORTED; + +export const NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS: LoadedConfigSelectors = { + environmentVariableSelector: (env) => + stringUnionSelector(env, ENV_RESPONSE_CHECKSUM_VALIDATION, RequestChecksumCalculation, SelectorType.ENV), + configFileSelector: (profile) => + stringUnionSelector(profile, CONFIG_RESPONSE_CHECKSUM_VALIDATION, RequestChecksumCalculation, SelectorType.CONFIG), + default: DEFAULT_RESPONSE_CHECKSUM_VALIDATION, +}; diff --git a/packages/middleware-flexible-checksums/src/constants.ts b/packages/middleware-flexible-checksums/src/constants.ts index dc68086032c5..ac944f5fe23f 100644 --- a/packages/middleware-flexible-checksums/src/constants.ts +++ b/packages/middleware-flexible-checksums/src/constants.ts @@ -1,3 +1,49 @@ +/** + * Determines when a checksum will be calculated for request payloads. + */ +export const RequestChecksumCalculation = { + /** + * When set, a checksum will be calculated for all request payloads of operations + * modeled with the {@link httpChecksum} trait where `requestChecksumRequired` is `true` + * AND/OR a `requestAlgorithmMember` is modeled. + * {@link https://smithy.io/2.0/aws/aws-core.html#aws-protocols-httpchecksum-trait httpChecksum} + */ + WHEN_SUPPORTED: "WHEN_SUPPORTED", + + /** + * When set, a checksum will only be calculated for request payloads of operations + * modeled with the {@link httpChecksum} trait where `requestChecksumRequired` is `true` + * OR where a `requestAlgorithmMember` is modeled and the user sets it. + * {@link https://smithy.io/2.0/aws/aws-core.html#aws-protocols-httpchecksum-trait httpChecksum} + */ + WHEN_REQUIRED: "WHEN_REQUIRED", +} as const; + +export type RequestChecksumCalculation = (typeof RequestChecksumCalculation)[keyof typeof RequestChecksumCalculation]; + +/** + * Determines when checksum validation will be performed on response payloads. + */ +export const ResponseChecksumValidation = { + /** + * When set, checksum validation MUST be performed on all response payloads of operations + * modeled with the {@link httpChecksum} trait where `responseAlgorithms` is modeled, + * except when no modeled checksum algorithms are supported by an SDK. + * {@link https://smithy.io/2.0/aws/aws-core.html#aws-protocols-httpchecksum-trait httpChecksum} + */ + WHEN_SUPPORTED: "WHEN_SUPPORTED", + + /** + * When set, checksum validation MUST NOT be performed on response payloads of operations UNLESS + * the SDK supports the modeled checksum algorithms AND the user has set the `requestValidationModeMember` to `ENABLED`. + * It is currently impossible to model an operation as requiring a response checksum, + * but this setting leaves the door open for future updates. + */ + WHEN_REQUIRED: "WHEN_REQUIRED", +} as const; + +export type ResponseChecksumValidation = (typeof ResponseChecksumValidation)[keyof typeof ResponseChecksumValidation]; + /** * Checksum Algorithms supported by the SDK. */ diff --git a/packages/middleware-flexible-checksums/src/index.ts b/packages/middleware-flexible-checksums/src/index.ts index 26a8b50c93b2..8ff2b77d48e1 100644 --- a/packages/middleware-flexible-checksums/src/index.ts +++ b/packages/middleware-flexible-checksums/src/index.ts @@ -1,3 +1,5 @@ +export * from "./NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS"; +export * from "./NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS"; export * from "./constants"; export * from "./flexibleChecksumsMiddleware"; export * from "./getFlexibleChecksumsPlugin"; diff --git a/packages/middleware-flexible-checksums/src/stringUnionSelector.spec.ts b/packages/middleware-flexible-checksums/src/stringUnionSelector.spec.ts new file mode 100644 index 000000000000..6bcfae43b0e8 --- /dev/null +++ b/packages/middleware-flexible-checksums/src/stringUnionSelector.spec.ts @@ -0,0 +1,40 @@ +import { SelectorType, stringUnionSelector } from "./stringUnionSelector"; + +describe(stringUnionSelector.name, () => { + const key = "key"; + const value = "VALUE"; + const obj: { [key]: any } = {} as any; + const union = { [key]: value }; + + describe.each(Object.entries(SelectorType))(`Selector %s`, (selectorKey, selectorValue) => { + beforeEach(() => { + delete obj[key]; + }); + + it(`should return undefined if ${key} is not defined`, () => { + // @ts-expect-error Element implicitly has an 'any' type + expect(stringUnionSelector(obj, key, union, SelectorType[selectorKey])).toBeUndefined(); + }); + + it.each([ + [value, value], + [value, value.toLowerCase()], + [value, [...value].map((c, i) => (i % 2 === 0 ? c.toLowerCase() : c.toUpperCase())).join("")], + ])(`should return number %s if ${key}="%s"`, (output, input) => { + obj[key] = input; + // @ts-expect-error Element implicitly has an 'any' type + expect(stringUnionSelector(obj, key, union, SelectorType[selectorKey])).toBe(output); + }); + + // Thows if the value is something other than different case. + it.each(["value1", "1value", [...value].reverse().join("")])(`should throw if ${key}=%s`, (input) => { + obj[key] = input; + // @ts-expect-error Element implicitly has an 'any' type + expect(() => stringUnionSelector(obj, key, union, SelectorType[selectorKey])).toThrow( + new TypeError( + `Cannot load ${selectorValue} '${key}'. Expected one of ${Object.values(union)}, got '${obj[key]}'.` + ) + ); + }); + }); +}); diff --git a/packages/middleware-flexible-checksums/src/stringUnionSelector.ts b/packages/middleware-flexible-checksums/src/stringUnionSelector.ts new file mode 100644 index 000000000000..2ad49baa50e8 --- /dev/null +++ b/packages/middleware-flexible-checksums/src/stringUnionSelector.ts @@ -0,0 +1,27 @@ +export enum SelectorType { + ENV = "env", + CONFIG = "shared config entry", +} + +/** + * Returns undefined, if obj[key] is not defined. + * Returns string value, if the string is defined in obj[key] and it's uppercase matches union value. + * Throws error for all other cases. + * + * @internal + */ +export const stringUnionSelector = ( + obj: Record, + key: string, + union: Record, + type: SelectorType +) => { + if (!(key in obj)) return undefined; + + const value = obj[key]!.toUpperCase(); + if (!Object.values(union).includes(value)) { + throw new TypeError(`Cannot load ${type} '${key}'. Expected one of ${Object.values(union)}, got '${obj[key]}'.`); + } + + return value; +};