Skip to content

Commit 7a207a9

Browse files
authored
fix(middleware-signing): correct clock skew from error response (#3133)
1 parent f6a3ef5 commit 7a207a9

File tree

5 files changed

+60
-12
lines changed

5 files changed

+60
-12
lines changed

Diff for: packages/middleware-serde/src/deserializerMiddleware.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,17 @@ describe("deserializerMiddleware", () => {
6262
expect(mockDeserializer).toHaveBeenCalledTimes(1);
6363
expect(mockDeserializer).toHaveBeenCalledWith(mockNextResponse.response, mockOptions);
6464
});
65+
66+
it("injects $response reference to deserializing exceptions", async () => {
67+
const exception = Object.assign(new Error("MockException"), mockNextResponse.response);
68+
mockDeserializer.mockReset();
69+
mockDeserializer.mockRejectedValueOnce(exception);
70+
try {
71+
await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, {})(mockArgs);
72+
fail("DeserializerMiddleware should throw");
73+
} catch (e) {
74+
expect(e).toMatchObject(exception);
75+
expect(e.$response).toEqual(mockNextResponse.response);
76+
}
77+
});
6578
});

Diff for: packages/middleware-serde/src/deserializerMiddleware.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ export const deserializerMiddleware =
1515
(next: DeserializeHandler<Input, Output>, context: HandlerExecutionContext): DeserializeHandler<Input, Output> =>
1616
async (args: DeserializeHandlerArguments<Input>): Promise<DeserializeHandlerOutput<Output>> => {
1717
const { response } = await next(args);
18-
const parsed = await deserializer(response, options);
19-
return {
20-
response,
21-
output: parsed as Output,
22-
};
18+
try {
19+
const parsed = await deserializer(response, options);
20+
return {
21+
response,
22+
output: parsed as Output,
23+
};
24+
} catch (error) {
25+
throw Object.assign(error, { $response: response });
26+
}
2327
};

Diff for: packages/middleware-signing/src/middleware.spec.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ describe(awsAuthMiddleware.name, () => {
7373
});
7474

7575
describe("should update systemClockOffset if date header is present", () => {
76-
it.each(["date", "Date"])("header[%s]", async (headerName) => {
76+
it.each(["date", "Date"])("header '%s'", async (headerName) => {
7777
const dateHeader = new Date().toString();
7878
const options = { ...mockOptions };
7979
const signingHandler = awsAuthMiddleware(options)(mockNext, {});
8080

81-
const mockResponseWithDateHeader = { response: { headers: { [headerName]: dateHeader } } };
81+
const mockResponseWithDateHeader = { response: { headers: { [headerName]: dateHeader }, statusCode: 200 } };
8282
mockNext.mockResolvedValue(mockResponseWithDateHeader);
8383

8484
const output = await signingHandler(mockSigningHandlerArgs);
@@ -88,6 +88,28 @@ describe(awsAuthMiddleware.name, () => {
8888
expect(getUpdatedSystemClockOffset).toHaveBeenCalledTimes(1);
8989
expect(getUpdatedSystemClockOffset).toHaveBeenCalledWith(dateHeader, mockSystemClockOffset);
9090
});
91+
92+
it.each(["date", "Date"])("error response with header '%s'", async (headerName) => {
93+
const serverTime = new Date().toString();
94+
const options = { ...mockOptions };
95+
const signingHandler = awsAuthMiddleware(options)(mockNext, {});
96+
97+
const mockError = Object.assign(new Error("error"), {
98+
$response: { statusCode: 400, headers: { [headerName]: serverTime } },
99+
});
100+
mockNext.mockRejectedValue(mockError);
101+
102+
try {
103+
await signingHandler(mockSigningHandlerArgs);
104+
fail(`should throw ${mockError}`);
105+
} catch (error) {
106+
expect(error).toStrictEqual(mockError);
107+
}
108+
109+
expect(options.systemClockOffset).toBe(mockUpdatedSystemClockOffset);
110+
expect(getUpdatedSystemClockOffset).toHaveBeenCalledTimes(1);
111+
expect(getUpdatedSystemClockOffset).toHaveBeenCalledWith(serverTime, mockSystemClockOffset);
112+
});
91113
});
92114

93115
it("should update systemClockOffset if error contains ServerTime", async () => {

Diff for: packages/middleware-signing/src/middleware.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpRequest } from "@aws-sdk/protocol-http";
1+
import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http";
22
import {
33
FinalizeHandler,
44
FinalizeHandlerArguments,
@@ -29,21 +29,24 @@ export const awsAuthMiddleware =
2929
signingService: context["signing_service"],
3030
}),
3131
}).catch((error) => {
32-
if (error.ServerTime) {
33-
options.systemClockOffset = getUpdatedSystemClockOffset(error.ServerTime, options.systemClockOffset);
32+
const serverTime: string | undefined = error.ServerTime ?? getDateHeader(error.$response);
33+
if (serverTime) {
34+
options.systemClockOffset = getUpdatedSystemClockOffset(serverTime, options.systemClockOffset);
3435
}
3536
throw error;
3637
});
3738

38-
const { headers } = output.response as any;
39-
const dateHeader = headers && (headers.date || headers.Date);
39+
const dateHeader = getDateHeader(output.response);
4040
if (dateHeader) {
4141
options.systemClockOffset = getUpdatedSystemClockOffset(dateHeader, options.systemClockOffset);
4242
}
4343

4444
return output;
4545
};
4646

47+
const getDateHeader = (response: unknown): string | undefined =>
48+
HttpResponse.isInstance(response) ? response.headers?.date ?? response.headers?.Date : undefined;
49+
4750
export const awsAuthMiddlewareOptions: RelativeMiddlewareOptions = {
4851
name: "awsAuthMiddleware",
4952
tags: ["SIGNATURE", "AWSAUTH"],

Diff for: packages/types/src/shapes.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { HttpResponse } from "./http";
12
import { MetadataBearer } from "./response";
23

34
/**
@@ -45,6 +46,11 @@ export interface SmithyException {
4546
* Indicates that an error MAY be retried by the client.
4647
*/
4748
readonly $retryable?: RetryableTrait;
49+
50+
/**
51+
* Reference to low-level HTTP response object.
52+
*/
53+
readonly $response?: HttpResponse;
4854
}
4955

5056
export type SdkError = Error & Partial<SmithyException> & Partial<MetadataBearer>;

0 commit comments

Comments
 (0)