diff --git a/clients/client-rds-data/commands/ExecuteStatementCommand.ts b/clients/client-rds-data/commands/ExecuteStatementCommand.ts index f21fee86e19c..8ee83794f612 100644 --- a/clients/client-rds-data/commands/ExecuteStatementCommand.ts +++ b/clients/client-rds-data/commands/ExecuteStatementCommand.ts @@ -57,7 +57,7 @@ export class ExecuteStatementCommand extends Command< input: ExecuteStatementRequest, protocol: string, context: SerdeContext - ): HttpRequest { + ): Promise { switch (protocol) { case "aws.rest-json-1.1": return executeStatementAwsRestJson1_1Serialize(input, context); @@ -66,7 +66,7 @@ export class ExecuteStatementCommand extends Command< } } - private async deserialize( + private deserialize( output: HttpResponse, protocol: string, context: SerdeContext diff --git a/clients/client-rds-data/protocol/AwsRestJson1_1.ts b/clients/client-rds-data/protocol/AwsRestJson1_1.ts index 171caf7e641a..48f873d15ba4 100644 --- a/clients/client-rds-data/protocol/AwsRestJson1_1.ts +++ b/clients/client-rds-data/protocol/AwsRestJson1_1.ts @@ -13,10 +13,10 @@ import { import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; import { SerdeContext, ResponseMetadata } from "@aws-sdk/types"; -export function executeStatementAwsRestJson1_1Serialize( +export async function executeStatementAwsRestJson1_1Serialize( input: ExecuteStatementRequest, context: SerdeContext -): HttpRequest { +): Promise { let body: any = {}; if (input.resourceArn !== undefined) { body.resourceArn = input.resourceArn; diff --git a/packages/middleware-content-length/src/index.ts b/packages/middleware-content-length/src/index.ts index 3826b46fdcbe..1d8836c89104 100644 --- a/packages/middleware-content-length/src/index.ts +++ b/packages/middleware-content-length/src/index.ts @@ -10,6 +10,8 @@ import { } from "@aws-sdk/types"; import { HttpRequest } from "@aws-sdk/protocol-http"; +const CONTENT_LENGTH_HEADER = "content-length"; + export function contentLengthMiddleware( bodyLengthChecker: BodyLengthCalculator ): BuildMiddleware { @@ -18,21 +20,20 @@ export function contentLengthMiddleware( ): BuildHandler => async ( args: BuildHandlerArguments ): Promise> => { - let request = { ...args.request }; - //TODO: cast request with instanceof + let request = args.request; if (HttpRequest.isInstance(request)) { const { body, headers } = request; if ( body && Object.keys(headers) .map(str => str.toLowerCase()) - .indexOf("content-length") === -1 + .indexOf(CONTENT_LENGTH_HEADER) === -1 ) { const length = bodyLengthChecker(body); if (length !== undefined) { request.headers = { ...request.headers, - "Content-Length": String(length) + CONTENT_LENGTH_HEADER: String(length) }; } } diff --git a/packages/middleware-serde/src/serializerMiddleware.ts b/packages/middleware-serde/src/serializerMiddleware.ts index c574eb611988..9202a597df18 100644 --- a/packages/middleware-serde/src/serializerMiddleware.ts +++ b/packages/middleware-serde/src/serializerMiddleware.ts @@ -28,7 +28,7 @@ export function serializerMiddleware< ...options, endpoint: await options.endpoint() }; - const request = serializer( + const request = await serializer( args.input, options.protocol, endpointResolvedOptions diff --git a/packages/middleware-serde/tsconfig.json b/packages/middleware-serde/tsconfig.json index 38b94cda274e..2cf4ad7301bc 100644 --- a/packages/middleware-serde/tsconfig.json +++ b/packages/middleware-serde/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2016", "module": "commonjs", "declaration": true, "strict": true, diff --git a/packages/middleware-user-agent/src/middleware.ts b/packages/middleware-user-agent/src/middleware.ts index 95a03dd19046..59ec9f0c7b5c 100644 --- a/packages/middleware-user-agent/src/middleware.ts +++ b/packages/middleware-user-agent/src/middleware.ts @@ -9,7 +9,7 @@ import { import { HttpRequest } from "@aws-sdk/protocol-http"; import { UserAgentResolvedConfig } from "./configurations"; -const userAgentHeader = "User-Agent"; +const userAgentHeader = "user-agent"; export function userAgentMiddleware(options: UserAgentResolvedConfig) { return ( diff --git a/packages/node-http-handler/src/server.mock.ts b/packages/node-http-handler/src/server.mock.ts index 637ff913edd9..7cb5c7402964 100644 --- a/packages/node-http-handler/src/server.mock.ts +++ b/packages/node-http-handler/src/server.mock.ts @@ -17,7 +17,7 @@ import { HttpResponse } from "@aws-sdk/types"; const fixturesDir = join(__dirname, "..", "fixtures"); -export function createResponseFunction(httpResp: HttpResponse) { +export function createResponseFunction(httpResp: HttpResponse) { return function(request: IncomingMessage, response: ServerResponse) { response.statusCode = httpResp.statusCode; for (let name of Object.keys(httpResp.headers)) { @@ -32,9 +32,7 @@ export function createResponseFunction(httpResp: HttpResponse) { }; } -export function createContinueResponseFunction( - httpResp: HttpResponse -) { +export function createContinueResponseFunction(httpResp: HttpResponse) { return function(request: IncomingMessage, response: ServerResponse) { response.writeContinue(); setTimeout(() => { diff --git a/packages/node-http-handler/src/write-request-body.ts b/packages/node-http-handler/src/write-request-body.ts index 428d076a3df0..479b8614d810 100644 --- a/packages/node-http-handler/src/write-request-body.ts +++ b/packages/node-http-handler/src/write-request-body.ts @@ -5,7 +5,7 @@ import { HttpRequest } from "@aws-sdk/types"; export function writeRequestBody( httpRequest: ClientRequest | ClientHttp2Stream, - request: HttpRequest + request: HttpRequest ) { const expect = request.headers["Expect"] || request.headers["expect"]; if (expect === "100-continue") { diff --git a/packages/node-http-handler/tsconfig.json b/packages/node-http-handler/tsconfig.json index c7cbb55570b6..e1413b6faf61 100644 --- a/packages/node-http-handler/tsconfig.json +++ b/packages/node-http-handler/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2017", "module": "commonjs", "declaration": true, "strict": true, diff --git a/packages/protocol-http/package.json b/packages/protocol-http/package.json index 34d3f00b501a..7eaa63d4c806 100644 --- a/packages/protocol-http/package.json +++ b/packages/protocol-http/package.json @@ -16,7 +16,7 @@ "license": "Apache-2.0", "dependencies": { "tslib": "^1.8.0", - "@aws-sdk/types": "^0.1.0-preview.4", + "@aws-sdk/types": "^0.1.0-preview.5", "@aws-sdk/util-uri-escape": "^0.1.0-preview.3" }, "devDependencies": { diff --git a/packages/protocol-http/src/http.ts b/packages/protocol-http/src/http.ts deleted file mode 100644 index 949997dc63db..000000000000 --- a/packages/protocol-http/src/http.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * A collection of key/value pairs with case-insensitive keys. - */ -export interface Headers extends Map { - /** - * Returns a new instance of Headers with the specified header set to the - * provided value. Does not modify the original Headers instance. - * - * @param headerName The name of the header to add or overwrite - * @param headerValue The value to which the header should be set - */ - withHeader(headerName: string, headerValue: string): Headers; - - /** - * Returns a new instance of Headers without the specified header. Does not - * modify the original Headers instance. - * - * @param headerName The name of the header to remove - */ - withoutHeader(headerName: string): Headers; -} - -/** - * A mapping of header names to string values. Multiple values for the same - * header should be represented as a single string with values separated by - * `, `. - * - * Keys should be considered case insensitive, even if this is not enforced by a - * particular implementation. For example, given the following HeaderBag, where - * keys differ only in case: - * - * { - * 'x-amz-date': '2000-01-01T00:00:00Z', - * 'X-Amz-Date': '2001-01-01T00:00:00Z' - * } - * - * The SDK may at any point during processing remove one of the object - * properties in favor of the other. The headers may or may not be combined, and - * the SDK will not deterministically select which header candidate to use. - */ -export interface HeaderBag { - [key: string]: string; -} - -/** - * Represents an HTTP message with headers and an optional static or streaming - * body. - */ -export interface HttpMessage { - headers: HeaderBag; - body?: any; -} - -/** - * A mapping of query parameter names to strings or arrays of strings, with the - * second being used when a parameter contains a list of values. Value can be set - * to null when query is not in key-value pairs shape - */ -export interface QueryParameterBag { - [key: string]: string | Array | null; -} - -export interface HttpEndpoint { - protocol: string; - hostname: string; - port?: number; - path: string; - query?: QueryParameterBag; -} diff --git a/packages/protocol-http/src/httpRequest.ts b/packages/protocol-http/src/httpRequest.ts index c003ab551748..73de1351db6f 100644 --- a/packages/protocol-http/src/httpRequest.ts +++ b/packages/protocol-http/src/httpRequest.ts @@ -1,15 +1,18 @@ import { escapeUri } from "@aws-sdk/util-uri-escape"; import { HttpMessage, - HttpEndpoint, + Endpoint, QueryParameterBag, - HeaderBag -} from "./http"; + HeaderBag, + HttpRequest as IHttpRequest +} from "@aws-sdk/types"; type HttpRequestOptions = Partial & - Partial & { method?: string }; + Partial & { method?: string }; -export class HttpRequest implements HttpMessage, HttpEndpoint { +export interface HttpRequest extends IHttpRequest {} + +export class HttpRequest implements HttpMessage, Endpoint { public method: string; public protocol: string; public hostname: string; @@ -18,7 +21,6 @@ export class HttpRequest implements HttpMessage, HttpEndpoint { public query: QueryParameterBag; public headers: HeaderBag; public body?: any; - private readonly isHttpRequest = true; constructor(options: HttpRequestOptions) { this.method = options.method || "GET"; @@ -40,8 +42,15 @@ export class HttpRequest implements HttpMessage, HttpEndpoint { } static isInstance(request: unknown): request is HttpRequest { + //determine if request is a valid httpRequest + const req: any = request; return ( - request !== undefined && (request as HttpRequest).isHttpRequest === true + "method" in req && + "protocol" in req && + "hostname" in req && + "path" in req && + typeof req["query"] === "object" && + typeof req["headers"] === "object" ); } @@ -57,6 +66,28 @@ export class HttpRequest implements HttpMessage, HttpEndpoint { return `${this.protocol}//${hostname}${this.path}${queryString}`; } + clone(): HttpRequest { + const cloned = new HttpRequest({ + ...this, + headers: { ...this.headers } + }); + if (cloned.query) cloned.query = this.cloneQuery(cloned.query); + return cloned; + } + + private cloneQuery(query: QueryParameterBag): QueryParameterBag { + return Object.keys(query).reduce( + (carry: QueryParameterBag, paramName: string) => { + const param = query[paramName]; + return { + ...carry, + [paramName]: Array.isArray(param) ? [...param] : param + }; + }, + {} + ); + } + private buildQueryString(): string { const parts: string[] = []; for (let key of Object.keys(this.query || {}).sort()) { diff --git a/packages/protocol-http/src/httpResponse.ts b/packages/protocol-http/src/httpResponse.ts index 59b343332b70..5b6c1399aea1 100644 --- a/packages/protocol-http/src/httpResponse.ts +++ b/packages/protocol-http/src/httpResponse.ts @@ -1,14 +1,15 @@ import { HttpMessage, - HttpEndpoint, - QueryParameterBag, - HeaderBag -} from "./http"; + HeaderBag, + HttpResponse as IHttpResponse +} from "@aws-sdk/types"; type HttpResponseOptions = Partial & { statusCode: number; }; +export interface HttpResponse extends IHttpResponse {} + export class HttpResponse { public statusCode: number; public headers: HeaderBag; diff --git a/packages/protocol-http/src/index.ts b/packages/protocol-http/src/index.ts index 4753232d2054..b01ad12cfd0a 100644 --- a/packages/protocol-http/src/index.ts +++ b/packages/protocol-http/src/index.ts @@ -1,4 +1,3 @@ export * from "./httpResponse"; export * from "./httpRequest"; export * from "./httpHandler"; -export * from "./http"; diff --git a/packages/protocol-http/tsconfig.json b/packages/protocol-http/tsconfig.json index 38b94cda274e..d34b34707316 100644 --- a/packages/protocol-http/tsconfig.json +++ b/packages/protocol-http/tsconfig.json @@ -9,6 +9,7 @@ "importHelpers": true, "noEmitHelpers": true, "lib": [ + "dom", "es5", "es2015.promise", "es2015.collection", diff --git a/packages/signature-v4/package.json b/packages/signature-v4/package.json index 21b9995667f1..d1972a6408a1 100644 --- a/packages/signature-v4/package.json +++ b/packages/signature-v4/package.json @@ -26,8 +26,8 @@ }, "devDependencies": { "@aws-crypto/sha256-js": "^0.1.0-preview.1", - "@aws-sdk/http-serialization": "^0.1.0-preview.7", "@aws-sdk/util-buffer-from": "^0.1.0-preview.3", + "@aws-sdk/protocol-http": "^0.1.0-preview.1", "@types/jest": "^24.0.12", "jest": "^24.7.1", "typescript": "~3.4.0" diff --git a/packages/signature-v4/src/SignatureV4.spec.ts b/packages/signature-v4/src/SignatureV4.spec.ts index 3a834c16babe..f923ea225808 100644 --- a/packages/signature-v4/src/SignatureV4.spec.ts +++ b/packages/signature-v4/src/SignatureV4.spec.ts @@ -15,7 +15,8 @@ import { UNSIGNED_PAYLOAD } from "./constants"; import { Sha256 } from "@aws-crypto/sha256-js"; -import { Credentials, HttpRequest } from "@aws-sdk/types"; +import { Credentials } from "@aws-sdk/types"; +import { HttpRequest } from "@aws-sdk/protocol-http"; import { iso8601 } from "@aws-sdk/protocol-timestamp"; const signer = new SignatureV4({ @@ -28,7 +29,7 @@ const signer = new SignatureV4({ } }); -const minimalRequest: HttpRequest = { +const minimalRequest = new HttpRequest({ method: "POST", protocol: "https:", path: "/", @@ -36,7 +37,7 @@ const minimalRequest: HttpRequest = { host: "foo.us-bar-1.amazonaws.com" }, hostname: "foo.us-bar-1.amazonaws.com" -}; +}); const credentials: Credentials = { accessKeyId: "foo", @@ -71,10 +72,10 @@ describe("SignatureV4", () => { it("should sign requests with string bodies", async () => { const { query } = await signer.presignRequest( - { + new HttpRequest({ ...minimalRequest, body: "It was the best of times, it was the worst of times" - }, + }), expiration, presigningOptions ); @@ -91,10 +92,10 @@ describe("SignatureV4", () => { it("should sign requests with binary bodies", async () => { const { query } = await signer.presignRequest( - { + new HttpRequest({ ...minimalRequest, body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]) - }, + }), expiration, presigningOptions ); @@ -116,10 +117,10 @@ describe("SignatureV4", () => { class ExoticStream {} const { query } = await signer.presignRequest( - { + new HttpRequest({ ...minimalRequest, - body: new ExoticStream() - }, + body: new ExoticStream() as any + }), expiration, presigningOptions ); @@ -171,14 +172,14 @@ describe("SignatureV4", () => { }); const { query } = await signer.presignRequest( - { + new HttpRequest({ ...minimalRequest, body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), headers: { ...minimalRequest.headers, "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD" } - }, + }), expiration, presigningOptions ); @@ -202,10 +203,10 @@ describe("SignatureV4", () => { "user-agent": "baz" }; const { headers: headersAsSigned, query } = await signer.presignRequest( - { + new HttpRequest({ ...minimalRequest, headers - }, + }), expiration, { ...presigningOptions, @@ -272,7 +273,7 @@ describe("SignatureV4", () => { }); describe("URI encoding paths", () => { - const minimalRequest: HttpRequest = { + const minimalRequest = new HttpRequest({ method: "POST", protocol: "https:", path: "/foo%3Dbar", @@ -280,7 +281,7 @@ describe("SignatureV4", () => { host: "foo.us-bar-1.amazonaws.com" }, hostname: "foo.us-bar-1.amazonaws.com" - }; + }); it("should URI-encode the path by default", async () => { const { query = {} } = await signer.presignRequest( @@ -311,14 +312,14 @@ describe("SignatureV4", () => { }); const { query = {} } = await signer.presignRequest( - { + new HttpRequest({ ...minimalRequest, path: "/foo/bar/baz", headers: { ...minimalRequest.headers, "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD" } - }, + }), expiration, presigningOptions ); @@ -339,12 +340,23 @@ describe("SignatureV4", () => { ); }); + it("should sign requests without host header", async () => { + const request = minimalRequest.clone(); + delete request.headers[HOST_HEADER]; + const { headers } = await signer.sign(request, { + signingDate: new Date("2000-01-01T00:00:00.000Z") + }); + expect(headers[AUTH_HEADER]).toBe( + "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=x-amz-content-sha256;x-amz-date, Signature=36cfca5cdb2c8d094f100663925d408a9608908ffc10b83133e5b25829ef7f5f" + ); + }); + it("should sign requests with string bodies", async () => { const { headers } = await signer.sign( - { + new HttpRequest({ ...minimalRequest, body: "It was the best of times, it was the worst of times" - }, + }), { signingDate: new Date("2000-01-01T00:00:00.000Z") } ); expect(headers[AUTH_HEADER]).toBe( @@ -354,10 +366,10 @@ describe("SignatureV4", () => { it("should sign requests with binary bodies", async () => { const { headers } = await signer.sign( - { + new HttpRequest({ ...minimalRequest, body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]) - }, + }), { signingDate: new Date("2000-01-01T00:00:00.000Z") } ); expect(headers[AUTH_HEADER]).toBe( @@ -371,14 +383,14 @@ describe("SignatureV4", () => { */ class ExoticStream {} const { headers } = await signer.sign( - { + new HttpRequest({ ...minimalRequest, - body: new ExoticStream(), + body: new ExoticStream() as any, headers: { ...minimalRequest.headers, "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD" } - }, + }), { signingDate: new Date("2000-01-01T00:00:00.000Z") } ); @@ -390,14 +402,14 @@ describe("SignatureV4", () => { it("should sign requests with unsigned bodies when so directed", async () => { const { headers } = await signer.sign( - { + new HttpRequest({ ...minimalRequest, body: "It was the best of times, it was the worst of times", headers: { ...minimalRequest.headers, "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD" } - }, + }), { signingDate: new Date("2000-01-01T00:00:00.000Z") } ); @@ -434,14 +446,14 @@ describe("SignatureV4", () => { it("should allow specifying custom unsignable headers", async () => { const { headers } = await signer.sign( - { + new HttpRequest({ ...minimalRequest, headers: { host: "foo.us-bar-1.amazonaws.com", foo: "bar", "user-agent": "baz" } - }, + }), { signingDate: new Date("2000-01-01T00:00:00.000Z"), unsignableHeaders: new Set(["foo"]) @@ -519,7 +531,7 @@ describe("SignatureV4", () => { }); describe("URI encoding paths", () => { - const minimalRequest: HttpRequest = { + const minimalRequest = new HttpRequest({ method: "POST", protocol: "https:", path: "/foo%3Dbar", @@ -527,7 +539,7 @@ describe("SignatureV4", () => { host: "foo.us-bar-1.amazonaws.com" }, hostname: "foo.us-bar-1.amazonaws.com" - }; + }); it("should URI-encode the path by default", async () => { const { headers } = await signer.sign(minimalRequest, { @@ -556,14 +568,14 @@ describe("SignatureV4", () => { }); const { headers } = await signer.sign( - { + new HttpRequest({ ...minimalRequest, headers: { ...minimalRequest.headers, "X-Amz-Content-Sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } - }, + }), { signingDate: new Date("2000-01-01T00:00:00.000Z") } @@ -596,6 +608,50 @@ describe("SignatureV4", () => { }); }); + describe("#signEvent", () => { + //adopt to Ruby SDK: https://github.com/aws/aws-sdk-ruby/blob/3c47c05aa77bdbb7b803a3ff932b3a89c32276ac/gems/aws-sigv4/spec/signer_spec.rb#L274 + it("support event signing", async () => { + const signer = new SignatureV4({ + service: "SERVICE", + region: "REGION", + credentials: { + accessKeyId: "akid", + secretAccessKey: "secret" + }, + sha256: Sha256 + }); + const eventSignature = await signer.signEvent( + { + headers: Uint8Array.from([ + 5, + 58, + 100, + 97, + 116, + 101, + 8, + 0, + 0, + 1, + 103, + 247, + 125, + 87, + 112 + ]), + payload: "foo" as any + }, + { + signingDate: new Date(1369353600000), + priorSignature: "" + } + ); + expect(eventSignature).toEqual( + "204bb5e2713e95354680e9522986d3ac0304aeafd33397f39e6540ca51ffe226" + ); + }); + }); + describe("ambient Date usage", () => { const knownDate = new Date("1999-12-31T23:59:59.999Z"); diff --git a/packages/signature-v4/src/SignatureV4.ts b/packages/signature-v4/src/SignatureV4.ts index c585eb921f92..5196a8f8b08f 100644 --- a/packages/signature-v4/src/SignatureV4.ts +++ b/packages/signature-v4/src/SignatureV4.ts @@ -17,20 +17,24 @@ import { SIGNATURE_QUERY_PARAM, SIGNED_HEADERS_QUERY_PARAM, TOKEN_HEADER, - TOKEN_QUERY_PARAM + TOKEN_QUERY_PARAM, + EVENT_ALGORITHM_IDENTIFIER } from "./constants"; import { Credentials, DateInput, HashConstructor, - HeaderBag, - HttpRequest, Provider, RequestPresigner, RequestSigner, RequestSigningArguments, SigningArguments, - StringSigner + StringSigner, + EventSigner, + FormattedEvent, + EventSigningArguments, + HeaderBag, + HttpRequest } from "@aws-sdk/types"; import { iso8601, toDate } from "@aws-sdk/protocol-timestamp"; import { toHex } from "@aws-sdk/util-hex-encoding"; @@ -85,7 +89,7 @@ export interface SignatureV4CryptoInit { } export class SignatureV4 - implements RequestPresigner, RequestSigner, StringSigner { + implements RequestPresigner, RequestSigner, StringSigner, EventSigner { private readonly service: string; private readonly regionProvider: Provider; private readonly credentialProvider: Provider; @@ -123,11 +127,11 @@ export class SignatureV4 } } - public async presignRequest( - originalRequest: HttpRequest, + public async presignRequest( + originalRequest: HttpRequest, expiration: DateInput, options: RequestSigningArguments = {} - ): Promise> { + ): Promise { const [region, credentials] = await Promise.all([ this.regionProvider(), this.credentialProvider() @@ -189,11 +193,11 @@ export class SignatureV4 stringToSign: string, options?: SigningArguments ): Promise; - public sign( - requestToSign: HttpRequest, + public sign( + requestToSign: HttpRequest, options?: RequestSigningArguments - ): Promise>; - public async sign>( + ): Promise; + public async sign( toSign: T, { signingDate = new Date(), @@ -219,7 +223,7 @@ export class SignatureV4 } = options as RequestSigningArguments; return this.signRequest( - toSign as HttpRequest, + toSign as HttpRequest, signingDate, region, credentials, @@ -229,6 +233,35 @@ export class SignatureV4 } } + public async signEvent( + { headers, payload }: FormattedEvent, + options: EventSigningArguments + ): Promise { + const [region, credentials] = await Promise.all([ + this.regionProvider(), + this.credentialProvider() + ]); + const { signingDate = new Date(), priorSignature } = options; + const { shortDate, longDate } = formatDate(signingDate); + const scope = createScope(shortDate, region, this.service); + const hashedPayload = await getPayloadHash( + { headers: {}, body: payload } as any, + this.sha256 + ); + const hash = new this.sha256(); + hash.update(headers); + const hashedHeaders = toHex(await hash.digest()); + const stringToSign = [ + EVENT_ALGORITHM_IDENTIFIER, + longDate, + scope, + priorSignature, + hashedHeaders, + hashedPayload + ].join("\n"); + return this.signString(stringToSign, signingDate, region, credentials); + } + private async signString( stringToSign: string, signingDate: DateInput, @@ -245,13 +278,13 @@ export class SignatureV4 } private async signRequest( - originalRequest: HttpRequest, + originalRequest: HttpRequest, signingDate: DateInput, region: string, credentials: Credentials, unsignableHeaders?: Set, signableHeaders?: Set - ): Promise> { + ): Promise { const request = prepareRequest(originalRequest); const { longDate, shortDate } = formatDate(signingDate); const scope = createScope(shortDate, region, this.service); @@ -288,7 +321,7 @@ export class SignatureV4 } private createCanonicalRequest( - request: HttpRequest, + request: HttpRequest, canonicalHeaders: HeaderBag, payloadHash: string ): string { @@ -317,7 +350,7 @@ ${credentialScope} ${toHex(hashedRequest)}`; } - private getCanonicalPath({ path }: HttpRequest): string { + private getCanonicalPath({ path }: HttpRequest): string { if (this.uriEscapePath) { const doubleEncoded = encodeURIComponent(path.replace(/^\//, "")); return `/${doubleEncoded.replace(/%2F/g, "/")}`; diff --git a/packages/signature-v4/src/cloneRequest.spec.ts b/packages/signature-v4/src/cloneRequest.spec.ts index 3c119a7cc3eb..9fb09906ff3e 100644 --- a/packages/signature-v4/src/cloneRequest.spec.ts +++ b/packages/signature-v4/src/cloneRequest.spec.ts @@ -2,7 +2,7 @@ import { cloneRequest } from "./cloneRequest"; import { HttpRequest, QueryParameterBag } from "@aws-sdk/types"; describe("cloneRequest", () => { - const request: HttpRequest = Object.freeze({ + const request: HttpRequest = Object.freeze({ method: "GET", protocol: "https:", hostname: "foo.us-west-2.amazonaws.com", diff --git a/packages/signature-v4/src/cloneRequest.ts b/packages/signature-v4/src/cloneRequest.ts index 50a5b2d056a2..7ddd8dcdba9b 100644 --- a/packages/signature-v4/src/cloneRequest.ts +++ b/packages/signature-v4/src/cloneRequest.ts @@ -3,11 +3,11 @@ import { HttpRequest, QueryParameterBag } from "@aws-sdk/types"; /** * @internal */ -export function cloneRequest({ +export function cloneRequest({ headers, query, ...rest -}: HttpRequest): HttpRequest { +}: HttpRequest): HttpRequest { return { ...rest, headers: { ...headers }, diff --git a/packages/signature-v4/src/constants.ts b/packages/signature-v4/src/constants.ts index 8c5a5c42cdb9..e517b6706d1a 100644 --- a/packages/signature-v4/src/constants.ts +++ b/packages/signature-v4/src/constants.ts @@ -41,6 +41,8 @@ export const UNSIGNABLE_PATTERNS = [/^proxy-/i, /^sec-/i]; export const ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256"; +export const EVENT_ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256-PAYLOAD"; + export const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; export const MAX_CACHE_SIZE = 50; diff --git a/packages/signature-v4/src/getCanonicalHeaders.spec.ts b/packages/signature-v4/src/getCanonicalHeaders.spec.ts index a694720c37d5..9fe518864949 100644 --- a/packages/signature-v4/src/getCanonicalHeaders.spec.ts +++ b/packages/signature-v4/src/getCanonicalHeaders.spec.ts @@ -1,21 +1,23 @@ import { getCanonicalHeaders } from "./getCanonicalHeaders"; import { ALWAYS_UNSIGNABLE_HEADERS } from "./constants"; -import { HttpRequest } from "@aws-sdk/types"; +import { HttpRequest } from "@aws-sdk/protocol-http"; describe("getCanonicalHeaders", () => { it("should downcase all headers", () => { expect( - getCanonicalHeaders({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - fOo: "bar", - BaZ: "QUUX", - HoSt: "foo.us-east-1.amazonaws.com" - }, - hostname: "foo.us-east-1.amazonaws.com" - }) + getCanonicalHeaders( + new HttpRequest({ + method: "POST", + protocol: "https:", + path: "/", + headers: { + fOo: "bar", + BaZ: "QUUX", + HoSt: "foo.us-east-1.amazonaws.com" + }, + hostname: "foo.us-east-1.amazonaws.com" + }) + ) ).toEqual({ foo: "bar", baz: "QUUX", @@ -24,7 +26,7 @@ describe("getCanonicalHeaders", () => { }); it("should remove all unsignable headers", () => { - const request: HttpRequest = { + const request = new HttpRequest({ method: "POST", protocol: "https:", path: "/", @@ -33,7 +35,7 @@ describe("getCanonicalHeaders", () => { foo: "bar" }, hostname: "foo.us-east-1.amazonaws.com" - }; + }); for (let headerName of Object.keys(ALWAYS_UNSIGNABLE_HEADERS)) { request.headers[headerName] = "baz"; } @@ -45,7 +47,7 @@ describe("getCanonicalHeaders", () => { }); it("should allow specifying custom unsignable headers", () => { - const request: HttpRequest = { + const request = new HttpRequest({ method: "POST", protocol: "https:", path: "/", @@ -55,7 +57,7 @@ describe("getCanonicalHeaders", () => { "user-agent": "foo-user" }, hostname: "foo.us-east-1.amazonaws.com" - }; + }); expect(getCanonicalHeaders(request, new Set(["foo"]))).toEqual({ host: "foo.us-east-1.amazonaws.com" diff --git a/packages/signature-v4/src/getCanonicalHeaders.ts b/packages/signature-v4/src/getCanonicalHeaders.ts index eb6776c74692..34649f78db91 100644 --- a/packages/signature-v4/src/getCanonicalHeaders.ts +++ b/packages/signature-v4/src/getCanonicalHeaders.ts @@ -9,7 +9,7 @@ import { * @internal */ export function getCanonicalHeaders( - { headers }: HttpRequest, + { headers }: HttpRequest, unsignableHeaders?: Set, signableHeaders?: Set ): HeaderBag { diff --git a/packages/signature-v4/src/getCanonicalQuery.spec.ts b/packages/signature-v4/src/getCanonicalQuery.spec.ts index 70d7637e6fcd..9f664a513e86 100644 --- a/packages/signature-v4/src/getCanonicalQuery.spec.ts +++ b/packages/signature-v4/src/getCanonicalQuery.spec.ts @@ -1,13 +1,13 @@ import { getCanonicalQuery } from "./getCanonicalQuery"; -import { HttpRequest } from "@aws-sdk/types"; +import { HttpRequest } from "@aws-sdk/protocol-http"; -const request: HttpRequest = { +const request = new HttpRequest({ method: "POST", protocol: "https:", path: "/", headers: {}, hostname: "foo.us-east-1.amazonaws.com" -}; +}); describe("getCanonicalQuery", () => { it("should return an empty string for requests with no querystring", () => { @@ -16,77 +16,93 @@ describe("getCanonicalQuery", () => { it("should serialize simple key => value pairs", () => { expect( - getCanonicalQuery({ - ...request, - query: { fizz: "buzz", foo: "bar" } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { fizz: "buzz", foo: "bar" } + }) + ) ).toBe("fizz=buzz&foo=bar"); }); it("should sort query keys alphabetically", () => { expect( - getCanonicalQuery({ - ...request, - query: { foo: "bar", baz: "quux", fizz: "buzz" } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { foo: "bar", baz: "quux", fizz: "buzz" } + }) + ) ).toBe("baz=quux&fizz=buzz&foo=bar"); }); it("should URI-encode keys and values", () => { expect( - getCanonicalQuery({ - ...request, - query: { "🐎": "🦄", "💩": "☃️" } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { "🐎": "🦄", "💩": "☃️" } + }) + ) ).toBe("%F0%9F%90%8E=%F0%9F%A6%84&%F0%9F%92%A9=%E2%98%83%EF%B8%8F"); }); it("should omit the x-amz-signature parameter, regardless of case", () => { expect( - getCanonicalQuery({ - ...request, - query: { - "x-amz-signature": "foo", - "X-Amz-Signature": "bar", - fizz: "buzz" - } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { + "x-amz-signature": "foo", + "X-Amz-Signature": "bar", + fizz: "buzz" + } + }) + ) ).toBe("fizz=buzz"); }); it("should serialize arrays of values", () => { expect( - getCanonicalQuery({ - ...request, - query: { foo: ["bar", "baz"] } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { foo: ["bar", "baz"] } + }) + ) ).toBe("foo=bar&foo=baz"); }); it("should serialize arrays using an alphabetic sort", () => { expect( - getCanonicalQuery({ - ...request, - query: { snap: ["pop", "crackle"] } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { snap: ["pop", "crackle"] } + }) + ) ).toBe("snap=crackle&snap=pop"); }); it("should URI-encode members of query param arrays", () => { expect( - getCanonicalQuery({ - ...request, - query: { "🐎": ["💩", "🦄"] } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { "🐎": ["💩", "🦄"] } + }) + ) ).toBe("%F0%9F%90%8E=%F0%9F%92%A9&%F0%9F%90%8E=%F0%9F%A6%84"); }); it("should omit non-string, non-array values from the serialized query", () => { expect( - getCanonicalQuery({ - ...request, - query: { foo: "bar", baz: new Uint8Array(0) as any } - }) + getCanonicalQuery( + new HttpRequest({ + ...request, + query: { foo: "bar", baz: new Uint8Array(0) as any } + }) + ) ).toBe("foo=bar"); }); }); diff --git a/packages/signature-v4/src/getCanonicalQuery.ts b/packages/signature-v4/src/getCanonicalQuery.ts index 83273ce0b731..a77c0935796f 100644 --- a/packages/signature-v4/src/getCanonicalQuery.ts +++ b/packages/signature-v4/src/getCanonicalQuery.ts @@ -4,7 +4,7 @@ import { HttpRequest } from "@aws-sdk/types"; /** * @internal */ -export function getCanonicalQuery({ query = {} }: HttpRequest): string { +export function getCanonicalQuery({ query = {} }: HttpRequest): string { const keys: Array = []; const serialized: { [key: string]: string } = {}; for (let key of Object.keys(query).sort()) { diff --git a/packages/signature-v4/src/getPayloadHash.spec.ts b/packages/signature-v4/src/getPayloadHash.spec.ts index a8ef6694360a..4840bbf211d0 100644 --- a/packages/signature-v4/src/getPayloadHash.spec.ts +++ b/packages/signature-v4/src/getPayloadHash.spec.ts @@ -1,16 +1,16 @@ import { getPayloadHash } from "./getPayloadHash"; import { SHA256_HEADER, UNSIGNED_PAYLOAD } from "./constants"; -import { HttpRequest } from "@aws-sdk/types"; +import { HttpRequest } from "@aws-sdk/protocol-http"; import { Sha256 } from "@aws-crypto/sha256-js"; describe("getPayloadHash", () => { - const minimalRequest: HttpRequest = { + const minimalRequest = new HttpRequest({ method: "POST", protocol: "https:", path: "/", headers: {}, hostname: "foo.us-east-1.amazonaws.com" - }; + }); it("should return the SHA-256 hash of an empty string if a request has no payload (body)", async () => { await expect(getPayloadHash(minimalRequest, Sha256)).resolves.toBe( @@ -21,12 +21,12 @@ describe("getPayloadHash", () => { it(`should return the value in the '${SHA256_HEADER}' header (if present)`, async () => { await expect( getPayloadHash( - { + new HttpRequest({ ...minimalRequest, headers: { [SHA256_HEADER]: "foo" } - }, + }), jest.fn(() => { throw new Error("I should not have been invoked!"); }) @@ -37,10 +37,10 @@ describe("getPayloadHash", () => { it("should return the hex-encoded hash of a string body", async () => { await expect( getPayloadHash( - { + new HttpRequest({ ...minimalRequest, body: "foo" - }, + }), Sha256 ) ).resolves.toBe( @@ -51,10 +51,10 @@ describe("getPayloadHash", () => { it("should return the hex-encoded hash of a ArrayBufferView body", async () => { await expect( getPayloadHash( - { + new HttpRequest({ ...minimalRequest, body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]) - }, + }), Sha256 ) ).resolves.toBe( @@ -65,10 +65,10 @@ describe("getPayloadHash", () => { it("should return the hex-encoded hash of a ArrayBuffer body", async () => { await expect( getPayloadHash( - { + new HttpRequest({ ...minimalRequest, body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]).buffer - }, + }), Sha256 ) ).resolves.toBe( @@ -84,10 +84,10 @@ describe("getPayloadHash", () => { await expect( getPayloadHash( - { + new HttpRequest({ ...minimalRequest, - body: new ExoticStream() - }, + body: new ExoticStream() as any + }), Sha256 ) ).resolves.toBe(UNSIGNED_PAYLOAD); diff --git a/packages/signature-v4/src/getPayloadHash.ts b/packages/signature-v4/src/getPayloadHash.ts index d67d29cba82f..a59c016d4b34 100644 --- a/packages/signature-v4/src/getPayloadHash.ts +++ b/packages/signature-v4/src/getPayloadHash.ts @@ -7,7 +7,7 @@ import { toHex } from "@aws-sdk/util-hex-encoding"; * @internal */ export async function getPayloadHash( - { headers, body }: HttpRequest, + { headers, body }: HttpRequest, hashConstructor: HashConstructor ): Promise { for (const headerName of Object.keys(headers)) { diff --git a/packages/signature-v4/src/moveHeadersToQuery.spec.ts b/packages/signature-v4/src/moveHeadersToQuery.spec.ts index 9289a429f54c..c196cabebf9e 100644 --- a/packages/signature-v4/src/moveHeadersToQuery.spec.ts +++ b/packages/signature-v4/src/moveHeadersToQuery.spec.ts @@ -1,7 +1,7 @@ import { moveHeadersToQuery } from "./moveHeadersToQuery"; -import { HttpRequest } from "@aws-sdk/types"; +import { HttpRequest } from "@aws-sdk/protocol-http"; -const minimalRequest: HttpRequest = { +const minimalRequest = new HttpRequest({ method: "POST", protocol: "https:", path: "/", @@ -9,21 +9,23 @@ const minimalRequest: HttpRequest = { host: "foo.us-east-1.amazonaws.com" }, hostname: "foo.us-east-1.amazonaws.com" -}; +}); describe("moveHeadersToQuery", () => { it('should hoist "x-amz-" headers to the querystring', () => { - const req = moveHeadersToQuery({ - ...minimalRequest, - headers: { - Host: "www.example.com", - "X-Amz-Website-Redirect-Location": "/index.html", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - "X-Amz-Storage-Class": "STANDARD_IA" - } - }); + const req = moveHeadersToQuery( + new HttpRequest({ + ...minimalRequest, + headers: { + Host: "www.example.com", + "X-Amz-Website-Redirect-Location": "/index.html", + Foo: "bar", + fizz: "buzz", + SNAP: "crackle, pop", + "X-Amz-Storage-Class": "STANDARD_IA" + } + }) + ); expect(req.query).toEqual({ "X-Amz-Website-Redirect-Location": "/index.html", @@ -39,22 +41,24 @@ describe("moveHeadersToQuery", () => { }); it("should not overwrite existing query values with different keys", () => { - const req = moveHeadersToQuery({ - ...minimalRequest, - headers: { - Host: "www.example.com", - "X-Amz-Website-Redirect-Location": "/index.html", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - "X-Amz-Storage-Class": "STANDARD_IA" - }, - query: { - Foo: "buzz", - fizz: "bar", - "X-Amz-Storage-Class": "REDUCED_REDUNDANCY" - } - }); + const req = moveHeadersToQuery( + new HttpRequest({ + ...minimalRequest, + headers: { + Host: "www.example.com", + "X-Amz-Website-Redirect-Location": "/index.html", + Foo: "bar", + fizz: "buzz", + SNAP: "crackle, pop", + "X-Amz-Storage-Class": "STANDARD_IA" + }, + query: { + Foo: "buzz", + fizz: "bar", + "X-Amz-Storage-Class": "REDUCED_REDUNDANCY" + } + }) + ); expect(req.query).toEqual({ Foo: "buzz", diff --git a/packages/signature-v4/src/moveHeadersToQuery.ts b/packages/signature-v4/src/moveHeadersToQuery.ts index 79397d3c4494..d98c1de93824 100644 --- a/packages/signature-v4/src/moveHeadersToQuery.ts +++ b/packages/signature-v4/src/moveHeadersToQuery.ts @@ -4,10 +4,13 @@ import { HttpRequest, QueryParameterBag } from "@aws-sdk/types"; /** * @internal */ -export function moveHeadersToQuery( - request: HttpRequest -): HttpRequest & { query: QueryParameterBag } { - const { headers, query = {} as QueryParameterBag } = cloneRequest(request); +export function moveHeadersToQuery( + request: HttpRequest +): HttpRequest & { query: QueryParameterBag } { + const { headers, query = {} as QueryParameterBag } = + typeof (request as any).clone === "function" + ? (request as any).clone() + : cloneRequest(request); for (let name of Object.keys(headers)) { const lname = name.toLowerCase(); if (lname.substr(0, 6) === "x-amz-") { diff --git a/packages/signature-v4/src/prepareRequest.spec.ts b/packages/signature-v4/src/prepareRequest.spec.ts index 62df14366c4d..01fe58914c60 100644 --- a/packages/signature-v4/src/prepareRequest.spec.ts +++ b/packages/signature-v4/src/prepareRequest.spec.ts @@ -1,13 +1,8 @@ import { prepareRequest } from "./prepareRequest"; -import { HttpRequest } from "@aws-sdk/types"; -import { - AMZ_DATE_HEADER, - AUTH_HEADER, - DATE_HEADER, - HOST_HEADER -} from "./constants"; +import { HttpRequest } from "@aws-sdk/protocol-http"; +import { AMZ_DATE_HEADER, AUTH_HEADER, DATE_HEADER } from "./constants"; -const minimalRequest: HttpRequest = { +const minimalRequest = new HttpRequest({ method: "POST", protocol: "https:", path: "/", @@ -15,7 +10,7 @@ const minimalRequest: HttpRequest = { host: "foo.us-bar-1.amazonaws.com" }, hostname: "foo.us-bar-1.amazonaws.com" -}; +}); describe("prepareRequest", () => { it("should clone requests", () => { @@ -25,24 +20,18 @@ describe("prepareRequest", () => { }); it("should ignore previously set authorization, date, and x-amz-date headers", async () => { - const { headers } = prepareRequest({ - ...minimalRequest, - headers: { - [AUTH_HEADER]: "foo", - [AMZ_DATE_HEADER]: "bar", - [DATE_HEADER]: "baz" - } - }); + const { headers } = prepareRequest( + new HttpRequest({ + ...minimalRequest, + headers: { + [AUTH_HEADER]: "foo", + [AMZ_DATE_HEADER]: "bar", + [DATE_HEADER]: "baz" + } + }) + ); expect(headers[AUTH_HEADER]).toBeUndefined(); expect(headers[AMZ_DATE_HEADER]).toBeUndefined(); expect(headers[DATE_HEADER]).toBeUndefined(); }); - - it(`should set the ${HOST_HEADER} header if not present`, () => { - const { headers } = prepareRequest({ - ...minimalRequest, - headers: {} - }); - expect(headers[HOST_HEADER]).toBe(minimalRequest.hostname); - }); }); diff --git a/packages/signature-v4/src/prepareRequest.ts b/packages/signature-v4/src/prepareRequest.ts index 02c1883844c8..482a71bf7c37 100644 --- a/packages/signature-v4/src/prepareRequest.ts +++ b/packages/signature-v4/src/prepareRequest.ts @@ -1,15 +1,16 @@ import { HttpRequest } from "@aws-sdk/types"; import { cloneRequest } from "./cloneRequest"; -import { GENERATED_HEADERS, HOST_HEADER } from "./constants"; +import { GENERATED_HEADERS } from "./constants"; /** * @internal */ -export function prepareRequest( - request: HttpRequest -): HttpRequest { +export function prepareRequest(request: HttpRequest): HttpRequest { // Create a clone of the request object that does not clone the body - request = cloneRequest(request); + request = + typeof (request as any).clone === "function" + ? (request as any).clone() + : cloneRequest(request); for (let headerName of Object.keys(request.headers)) { if (GENERATED_HEADERS.indexOf(headerName.toLowerCase()) > -1) { @@ -17,9 +18,5 @@ export function prepareRequest( } } - if (!(HOST_HEADER in request.headers)) { - request.headers[HOST_HEADER] = request.hostname; - } - return request; } diff --git a/packages/signature-v4/src/suite.fixture.ts b/packages/signature-v4/src/suite.fixture.ts index d99187776213..16c06e4ef5ce 100644 --- a/packages/signature-v4/src/suite.fixture.ts +++ b/packages/signature-v4/src/suite.fixture.ts @@ -1,8 +1,8 @@ -import { HttpRequest } from "@aws-sdk/types"; +import { HttpRequest } from "@aws-sdk/protocol-http"; export interface TestCase { name: string; - request: HttpRequest; + request: HttpRequest; authorization: string; } @@ -18,7 +18,7 @@ export const signingDate = new Date("2015-08-30T12:36:00Z"); export const requests: Array = [ { name: "get-header-key-duplicate", - request: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -28,13 +28,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -44,13 +44,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -60,13 +60,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -77,13 +77,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -93,13 +93,13 @@ export const requests: Array = [ path: "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", hostname: "example.amazonaws.com" - }, + }), authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=07ef7494c76fa4850883e2b006601f940f8a34d404d0cfa977f52a65bbf5f24f" }, { name: "get-utf8", - request: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -108,13 +108,13 @@ export const requests: Array = [ }, path: "/ሴ", hostname: "example.amazonaws.com" - }, + }), authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=8318018e0b0f223aa2bbf98705b62bb787dc9c0e678f255a891fd03141be5d85" }, { name: "get-vanilla", - request: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -123,13 +123,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -141,13 +141,13 @@ export const requests: Array = [ Param1: "value1" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -156,13 +156,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -175,13 +175,13 @@ export const requests: Array = [ Param1: "value1" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -194,13 +194,13 @@ export const requests: Array = [ "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "GET", headers: { @@ -212,13 +212,13 @@ export const requests: Array = [ ሴ: "bar" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -227,13 +227,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -243,13 +243,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -259,13 +259,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), authorization: "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=cdbc9802e29d2942e5e10b5bccfdd67c5f22c7c4e8ae67b53629efa58b974b7d" }, { name: "post-vanilla", - request: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -274,13 +274,13 @@ export const requests: Array = [ }, path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -292,13 +292,13 @@ export const requests: Array = [ Param1: "value1" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -310,13 +310,13 @@ export const requests: Array = [ Param1: "value1" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -329,13 +329,13 @@ export const requests: Array = [ "+": '/,?><`";:\\|][{}' }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -347,13 +347,13 @@ export const requests: Array = [ p: "" }, hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -364,13 +364,13 @@ export const requests: Array = [ body: "Param1=value1", path: "/", hostname: "example.amazonaws.com" - }, + }), 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: { + request: new HttpRequest({ protocol: "https:", method: "POST", headers: { @@ -381,7 +381,7 @@ export const requests: Array = [ body: "Param1=value1", path: "/", hostname: "example.amazonaws.com" - }, + }), 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/types/src/exception.ts b/packages/types/src/exception.ts deleted file mode 100644 index b0a86b5e96e1..000000000000 --- a/packages/types/src/exception.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ResponseMetadata } from "./response"; -import { MetadataBearer } from "./index"; - -/** - * Exceptions that responded from AWS service containing raw http response - * and parsed exception object - * - * @property {any} details - parsed exception object normalized according to - * its API model - */ -export interface ServiceException
extends Error, MetadataBearer { - details: Details; -} - -/** - * Service exceptions that can not be parsed by unmarshallers' error parser - */ -export interface UnkownServiceException extends ServiceException { - name: "Error"; -} diff --git a/packages/types/src/http.ts b/packages/types/src/http.ts index 44afa7cec2f9..a31c5a5442c6 100644 --- a/packages/types/src/http.ts +++ b/packages/types/src/http.ts @@ -1,4 +1,5 @@ import { AbortSignal } from "./abort"; +import { Readable } from "stream"; /** * A collection of key/value pairs with case-insensitive keys. */ @@ -45,11 +46,11 @@ export interface HeaderBag { /** * Represents an HTTP message with headers and an optional static or streaming - * body. + * body. bode: ArrayBuffer | ArrayBufferView | string | Uint8Array | Readable | ReadableStream; */ -export interface HttpMessage { +export interface HttpMessage { headers: HeaderBag; - body?: ArrayBuffer | ArrayBufferView | string | StreamType; + body?: any; } /** @@ -70,12 +71,10 @@ export interface Endpoint { } /** - * Represents an HTTP message constructed to be sent to a host. Contains + * Interface an HTTP request class. Contains * addressing information in addition to standard message properties. */ -export interface HttpRequest - extends HttpMessage, - Endpoint { +export interface HttpRequest extends HttpMessage, Endpoint { method: string; } @@ -83,8 +82,7 @@ export interface HttpRequest * Represents an HTTP message as received in reply to a request. Contains a * numeric status code in addition to standard message properties. */ -export interface HttpResponse - extends HttpMessage { +export interface HttpResponse extends HttpMessage { statusCode: number; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 77fd6ff678b1..7f7c413ed325 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -3,12 +3,10 @@ export * from "./client"; export * from "./command"; export * from "./credentials"; export * from "./crypto"; -export * from "./exception"; export * from "./http"; export * from "./logger"; export * from "./serde"; export * from "./middleware"; -export * from "./protocol"; export * from "./response"; export * from "./signature"; export * from "./transfer"; diff --git a/packages/types/src/logger.ts b/packages/types/src/logger.ts index 0db6d5718090..6aae059f3e70 100644 --- a/packages/types/src/logger.ts +++ b/packages/types/src/logger.ts @@ -1,5 +1,3 @@ -import { Member } from "./protocol"; - /** * A list of logger's log level. These levels are sorted in * order of increasing severity. Each log level includes itself and all @@ -28,11 +26,3 @@ export interface Logger { warn(content: string): void; error(content: string): void; } - -/** - * A function that removes the sensitive information from input parameters - * and output objects during logging. - */ -export interface SensitiveDataScrubber { - (input: any, shape: Member): string; -} diff --git a/packages/types/src/protocol.ts b/packages/types/src/protocol.ts deleted file mode 100644 index 9da04e3c597b..000000000000 --- a/packages/types/src/protocol.ts +++ /dev/null @@ -1,163 +0,0 @@ -export type SerializationType = - | "blob" - | "boolean" - | "float" - | "integer" - | "list" - | "map" - | "string" - | "structure" - | "timestamp"; - -export type MemberLocation = - | "header" - | "headers" - | "uri" - | "querystring" - | "statusCode"; - -export interface XmlNamespace { - prefix?: string; - uri?: string; -} - -export interface Member { - shape: SerializationModel; - flattened?: boolean; - location?: MemberLocation; - locationName?: string; - sensitive?: boolean; - streaming?: boolean; - xmlAttribute?: boolean; - xmlNamespace?: XmlNamespace; - queryName?: string; - resultWrapper?: string; - timestampFormat?: string; -} - -export interface Shape { - type: SerializationType; - sensitive?: boolean; -} - -export interface Blob extends Shape { - type: "blob"; - streaming?: boolean; -} - -export interface Boolean extends Shape { - type: "boolean"; -} - -export interface Float extends Shape { - type: "float"; - min?: number; -} - -export interface Integer extends Shape { - type: "integer"; - min?: number; -} - -export interface List extends Shape { - type: "list"; - member: Member; - flattened?: boolean; - min?: number; -} - -export interface Map extends Shape { - type: "map"; - key: Member; - value: Member; - flattened?: boolean; -} - -export interface Number extends Shape { - min?: number; -} - -export interface String extends Shape { - type: "string"; - min?: number; - jsonValue?: boolean; - idempotencyToken?: boolean; -} - -export interface Structure extends Shape { - type: "structure"; - required: Array; - members: { [key: string]: Member }; - exceptionType?: string; - exceptionCode?: string; - payload?: string; -} - -export interface Timestamp extends Shape { - type: "timestamp"; - timestampFormat?: string; -} - -export type SerializationModel = - | Blob - | Boolean - | Float - | Integer - | List - | Map - | String - | Structure - | Timestamp; - -export interface HttpTrait { - method: string; - requestUri: string; -} - -export interface OperationModel { - http: HttpTrait; - name: string; - metadata: ServiceMetadata; - input: Member; - output: Member; - errors: Array; -} - -export type SupportedProtocol = - | "json" - | "rest-json" - | "rest-xml" - | "query" - | "ec2"; - -export type SupportedSignatureVersion = - | "v4" - | "s3" - | "s3v4" - | "v4-unsigned-body" - | "none"; - -export interface ServiceMetadata { - apiVersion: string; - endpointPrefix: string; - jsonVersion?: string; - protocol: SupportedProtocol; - serviceAbbreviation?: string; - serviceFullName: string; - serviceId?: string; - signingName?: string; - signatureVersion: SupportedSignatureVersion; - /** - * Required for json-rpc services. - */ - targetPrefix?: string; - /** - * Sometimes specified when format differs from protocol default. - */ - timestampFormat?: string; - uid: string; - /** - * Required for query services. - */ - xmlNamespace?: XmlNamespace; -} diff --git a/packages/types/src/response.ts b/packages/types/src/response.ts index 026898177ef8..3bcbb9759a79 100644 --- a/packages/types/src/response.ts +++ b/packages/types/src/response.ts @@ -1,4 +1,4 @@ -import { HeaderBag, HttpResponse } from "./http"; +import { HeaderBag } from "./http"; export interface ResponseMetadata { /** diff --git a/packages/types/src/serde.ts b/packages/types/src/serde.ts index 2677ae09d391..750cc6033767 100644 --- a/packages/types/src/serde.ts +++ b/packages/types/src/serde.ts @@ -1,5 +1,7 @@ import { Decoder, Encoder, Provider } from "./util"; -import { Endpoint } from "./http"; +import { Endpoint, HttpRequest } from "./http"; +import { RequestHandler } from "./transfer"; +import { RequestSigner } from "./signature"; /** * Interface for object requires an Endpoint set. @@ -26,6 +28,7 @@ export interface SerdeContext extends EndpointBearer { utf8Encoder: Encoder; utf8Decoder: Decoder; streamCollector: StreamCollector; + requestHandler: RequestHandler; } export interface RequestSerializer< @@ -41,7 +44,7 @@ export interface RequestSerializer< * * @param context Context containing runtime-specific util functions. */ - (input: any, transferProtocol: string, context: Context): Request; + (input: any, transferProtocol: string, context: Context): Promise; } export interface ResponseDeserializer< diff --git a/packages/types/src/signature.ts b/packages/types/src/signature.ts index 13fb706c4259..fae8229a475d 100644 --- a/packages/types/src/signature.ts +++ b/packages/types/src/signature.ts @@ -35,6 +35,10 @@ export interface RequestSigningArguments extends SigningArguments { signableHeaders?: Set; } +export interface EventSigningArguments extends RequestSigningArguments { + priorSignature: string; +} + export interface RequestPresigner { /** * Signs a request for future use. @@ -47,11 +51,11 @@ export interface RequestPresigner { * longer be honored. * @param options Additional signing options. */ - presignRequest( - requestToSign: HttpRequest, + presignRequest( + requestToSign: HttpRequest, expiration: DateInput, options?: RequestSigningArguments - ): Promise>; + ): Promise; } /** @@ -62,10 +66,10 @@ export interface RequestSigner { /** * Sign the provided request for immediate dispatch. */ - sign( - requestToSign: HttpRequest, + sign( + requestToSign: HttpRequest, options?: RequestSigningArguments - ): Promise>; + ): Promise; } export interface StringSigner { @@ -75,3 +79,17 @@ export interface StringSigner { */ sign(stringToSign: string, options?: SigningArguments): Promise; } + +export interface FormattedEvent { + headers: Uint8Array; + payload: Uint8Array; +} +export interface EventSigner { + /** + * Sign the individual event of the event stream. + */ + signEvent( + event: FormattedEvent, + options: EventSigningArguments + ): Promise; +} diff --git a/packages/util-user-agent-browser/package.json b/packages/util-user-agent-browser/package.json index fa152e7afdc0..71b6df0ddceb 100644 --- a/packages/util-user-agent-browser/package.json +++ b/packages/util-user-agent-browser/package.json @@ -18,6 +18,7 @@ "tslib": "^1.8.0" }, "devDependencies": { + "@aws-sdk/protocol-http": "^0.1.0-preview.1", "@types/jest": "^24.0.12", "jest": "^24.7.1", "typescript": "~3.4.0" diff --git a/packages/util-user-agent-browser/src/index.spec.ts b/packages/util-user-agent-browser/src/index.spec.ts index 1e282b8bbb17..8fa66e3c7aa1 100644 --- a/packages/util-user-agent-browser/src/index.spec.ts +++ b/packages/util-user-agent-browser/src/index.spec.ts @@ -1,4 +1,5 @@ import { defaultUserAgent, appendToUserAgent } from "."; +import { HttpRequest } from "@aws-sdk/protocol-http"; it("should response basic node default user agent", () => { let originUserAgent = ""; @@ -17,13 +18,13 @@ it("should response basic node default user agent", () => { it("append to user agent", () => { const defaultValue = defaultUserAgent("client-s3-node", "0.1.0"); - const request = { + const request = new HttpRequest({ headers: { "X-Amz-User-Agent": defaultValue }, method: "GET", protocol: "json", hostname: "foo.amazonaws.com", path: "/" - }; + }); appendToUserAgent(request, "http/2.0"); expect(request.headers["X-Amz-User-Agent"]).toBe(`${defaultValue} http/2.0`); }); diff --git a/packages/util-user-agent-node/package.json b/packages/util-user-agent-node/package.json index 3466f9d5f624..508592afaf9e 100644 --- a/packages/util-user-agent-node/package.json +++ b/packages/util-user-agent-node/package.json @@ -18,6 +18,7 @@ "tslib": "^1.8.0" }, "devDependencies": { + "@aws-sdk/protocol-http": "^0.1.0-preview.1", "@types/jest": "^24.0.12", "@types/node": "^10.0.0", "jest": "^24.7.1", diff --git a/packages/util-user-agent-node/src/index.spec.ts b/packages/util-user-agent-node/src/index.spec.ts index 6cc665d47c30..e467de060096 100644 --- a/packages/util-user-agent-node/src/index.spec.ts +++ b/packages/util-user-agent-node/src/index.spec.ts @@ -1,4 +1,5 @@ import { defaultUserAgent, appendToUserAgent } from "."; +import { HttpRequest } from "@aws-sdk/protocol-http"; import * as process from "process"; describe("defaultUserAgent", () => { @@ -22,13 +23,13 @@ describe("defaultUserAgent", () => { it("append to user agent", () => { const defaultValue = defaultUserAgent("client-s3-node", "0.1.0"); - const request = { + const request = new HttpRequest({ headers: { "User-Agent": defaultValue }, method: "GET", protocol: "json", hostname: "foo.amazonaws.com", path: "/" - }; + }); appendToUserAgent(request, "http/2.0"); expect(request.headers["User-Agent"]).toBe(`${defaultValue} http/2.0`); });