Skip to content

Commit 4687f53

Browse files
authored
fix(eventstream-handler-node): add system clock offset to event signing streams (#6227)
1 parent fb98e0c commit 4687f53

10 files changed

+54
-31
lines changed

Diff for: packages/eventstream-handler-node/src/EventSigningStream.spec.ts

+9-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe("EventSigningStream", () => {
1111
Date = originalDate;
1212
});
1313

14-
it("should sign a eventstream payload properly", (done) => {
14+
it("should sign an eventstream payload properly", (done) => {
1515
const eventStreamCodec = new EventStreamCodec(toUtf8, fromUtf8);
1616
const message1: Message = {
1717
headers: {},
@@ -44,24 +44,22 @@ describe("EventSigningStream", () => {
4444
.fn()
4545
.mockReturnValueOnce({ message: message1, signature: "7369676e617475726531" } as SignedMessage) //'signature1'
4646
.mockReturnValueOnce({ message: message2, signature: "7369676e617475726532" } as SignedMessage); //'signature2'
47-
// mock 'new Date()'
47+
4848
let mockDateCount = 0;
49-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
50-
const mockDate = jest
51-
.spyOn(global, "Date")
52-
//@ts-ignore: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor/60918716#60918716
53-
.mockImplementation((input) => {
54-
if (input) return new originalDate(input);
55-
mockDateCount += 1;
56-
return expected[mockDateCount - 1][":date"].value;
57-
});
49+
function MockDate(input?: any): Date {
50+
return input ? new originalDate(input) : (expected[mockDateCount++][":date"].value as Date);
51+
}
52+
MockDate.now = () => MockDate().getTime();
53+
global.Date = MockDate as any;
54+
5855
const signingStream = new EventSigningStream({
5956
priorSignature: "initial",
6057
messageSigner: {
6158
sign: mockMessageSigner,
6259
signMessage: mockMessageSigner,
6360
},
6461
eventStreamCodec,
62+
systemClockOffsetProvider: async () => 0,
6563
});
6664
const output: Array<MessageHeaders> = [];
6765
signingStream.on("data", (chunk) => {

Diff for: packages/eventstream-handler-node/src/EventSigningStream.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EventStreamCodec } from "@smithy/eventstream-codec";
2-
import { MessageHeaders, MessageSigner } from "@smithy/types";
2+
import { MessageHeaders, MessageSigner, Provider } from "@smithy/types";
33
import { Transform, TransformCallback, TransformOptions } from "stream";
44

55
/**
@@ -9,6 +9,7 @@ export interface EventSigningStreamOptions extends TransformOptions {
99
priorSignature: string;
1010
messageSigner: MessageSigner;
1111
eventStreamCodec: EventStreamCodec;
12+
systemClockOffsetProvider: Provider<number>;
1213
}
1314

1415
/**
@@ -20,6 +21,7 @@ export class EventSigningStream extends Transform {
2021
private priorSignature: string;
2122
private messageSigner: MessageSigner;
2223
private eventStreamCodec: EventStreamCodec;
24+
private readonly systemClockOffsetProvider: Provider<number>;
2325

2426
constructor(options: EventSigningStreamOptions) {
2527
super({
@@ -32,11 +34,12 @@ export class EventSigningStream extends Transform {
3234
this.priorSignature = options.priorSignature;
3335
this.eventStreamCodec = options.eventStreamCodec;
3436
this.messageSigner = options.messageSigner;
37+
this.systemClockOffsetProvider = options.systemClockOffsetProvider;
3538
}
3639

3740
async _transform(chunk: Uint8Array, encoding: string, callback: TransformCallback): Promise<void> {
3841
try {
39-
const now = new Date();
42+
const now = new Date(Date.now() + (await this.systemClockOffsetProvider()));
4043
const dateHeader: MessageHeaders = {
4144
":date": { type: "timestamp", value: now },
4245
};

Diff for: packages/eventstream-handler-node/src/EventStreamPayloadHandler.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ describe(EventStreamPayloadHandler.name, () => {
9090
priorSignature,
9191
eventStreamCodec: expect.anything(),
9292
messageSigner: expect.anything(),
93+
systemClockOffsetProvider: expect.any(Function),
9394
});
9495
});
9596

@@ -121,6 +122,7 @@ describe(EventStreamPayloadHandler.name, () => {
121122
priorSignature,
122123
eventStreamCodec: expect.anything(),
123124
messageSigner: expect.anything(),
125+
systemClockOffsetProvider: expect.any(Function),
124126
});
125127
});
126128

Diff for: packages/eventstream-handler-node/src/EventStreamPayloadHandler.ts

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface EventStreamPayloadHandlerOptions {
2323
messageSigner: Provider<MessageSigner>;
2424
utf8Encoder: Encoder;
2525
utf8Decoder: Decoder;
26+
systemClockOffset?: number;
2627
}
2728

2829
/**
@@ -37,10 +38,12 @@ export interface EventStreamPayloadHandlerOptions {
3738
export class EventStreamPayloadHandler implements IEventStreamPayloadHandler {
3839
private readonly messageSigner: Provider<MessageSigner>;
3940
private readonly eventStreamCodec: EventStreamCodec;
41+
private readonly systemClockOffsetProvider: Provider<number>;
4042

4143
constructor(options: EventStreamPayloadHandlerOptions) {
4244
this.messageSigner = options.messageSigner;
4345
this.eventStreamCodec = new EventStreamCodec(options.utf8Encoder, options.utf8Decoder);
46+
this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0;
4447
}
4548

4649
async handle<T extends MetadataBearer>(
@@ -79,6 +82,7 @@ export class EventStreamPayloadHandler implements IEventStreamPayloadHandler {
7982
priorSignature,
8083
eventStreamCodec: this.eventStreamCodec,
8184
messageSigner: await this.messageSigner(),
85+
systemClockOffsetProvider: this.systemClockOffsetProvider,
8286
});
8387

8488
pipeline(payloadStream, signingStream, request.body, (err: NodeJS.ErrnoException | null) => {

Diff for: packages/eventstream-handler-node/src/provider.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export const eventStreamPayloadHandlerProvider: EventStreamPayloadHandlerProvide
1414
utf8Encoder: Encoder;
1515
utf8Decoder: Decoder;
1616
messageSigner: Provider<MessageSigner>;
17+
systemClockOffset?: number;
1718
}) => new EventStreamPayloadHandler(options);

Diff for: packages/middleware-websocket/src/EventStreamPayloadHandler.spec.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ describe(EventStreamPayloadHandler.name, () => {
9191
});
9292

9393
expect(getEventSigningTransformStream).toHaveBeenCalledTimes(1);
94-
expect(getEventSigningTransformStream).toHaveBeenCalledWith(priorSignature, expect.anything(), expect.anything());
94+
expect(getEventSigningTransformStream).toHaveBeenCalledWith(
95+
priorSignature,
96+
expect.anything(),
97+
expect.anything(),
98+
expect.anything()
99+
);
95100
});
96101

97102
it("should call event signer with request signature from query string if no signature headers are found", async () => {
@@ -118,7 +123,12 @@ describe(EventStreamPayloadHandler.name, () => {
118123
});
119124

120125
expect(getEventSigningTransformStream).toHaveBeenCalledTimes(1);
121-
expect(getEventSigningTransformStream).toHaveBeenCalledWith(priorSignature, expect.anything(), expect.anything());
126+
expect(getEventSigningTransformStream).toHaveBeenCalledWith(
127+
priorSignature,
128+
expect.anything(),
129+
expect.anything(),
130+
expect.anything()
131+
);
122132
});
123133

124134
it("should start piping to request payload through event signer if downstream middleware returns", async () => {

Diff for: packages/middleware-websocket/src/EventStreamPayloadHandler.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface EventStreamPayloadHandlerOptions {
1919
messageSigner: Provider<MessageSigner>;
2020
utf8Encoder: Encoder;
2121
utf8Decoder: Decoder;
22+
systemClockOffset?: number;
2223
}
2324

2425
/**
@@ -31,10 +32,12 @@ export interface EventStreamPayloadHandlerOptions {
3132
export class EventStreamPayloadHandler implements IEventStreamPayloadHandler {
3233
private readonly messageSigner: Provider<MessageSigner>;
3334
private readonly eventStreamCodec: EventStreamCodec;
35+
private readonly systemClockOffsetProvider: Provider<number>;
3436

3537
constructor(options: EventStreamPayloadHandlerOptions) {
3638
this.messageSigner = options.messageSigner;
3739
this.eventStreamCodec = new EventStreamCodec(options.utf8Encoder, options.utf8Decoder);
40+
this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0;
3841
}
3942

4043
async handle<T extends MetadataBearer>(
@@ -69,7 +72,8 @@ export class EventStreamPayloadHandler implements IEventStreamPayloadHandler {
6972
const signingStream = getEventSigningTransformStream(
7073
priorSignature,
7174
await this.messageSigner(),
72-
this.eventStreamCodec
75+
this.eventStreamCodec,
76+
this.systemClockOffsetProvider
7377
);
7478

7579
const signedPayload = payload.pipeThrough(signingStream);

Diff for: packages/middleware-websocket/src/eventstream-payload-handler-provider.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export const eventStreamPayloadHandlerProvider: EventStreamPayloadHandlerProvide
77
utf8Encoder: Encoder;
88
utf8Decoder: Decoder;
99
messageSigner: Provider<MessageSigner>;
10+
systemClockOffset?: number;
1011
}) => new EventStreamPayloadHandler(options);

Diff for: packages/middleware-websocket/src/get-event-signing-stream.spec.ts

+9-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe(getEventSigningTransformStream.name, () => {
1616
window.TransformStream = TransformStream;
1717
});
1818
afterEach(() => {
19-
Date = originalDate;
19+
window.Date = originalDate;
2020
window.TransformStream = originalTransformStreamCtor;
2121
});
2222

@@ -63,24 +63,21 @@ describe(getEventSigningTransformStream.name, () => {
6363
.mockReturnValueOnce({ message: message1, signature: "7369676e617475726531" } as SignedMessage) //'signature1'
6464
.mockReturnValueOnce({ message: message2, signature: "7369676e617475726532" } as SignedMessage); //'signature2'
6565

66-
// mock 'new Date()'
6766
let mockDateCount = 0;
68-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
69-
const mockDate = jest
70-
.spyOn(window, "Date")
71-
//@ts-ignore: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor/60918716#60918716
72-
.mockImplementation((input) => {
73-
if (input) return new originalDate(input);
74-
mockDateCount += 1;
75-
return expected[mockDateCount - 1][":date"].value;
76-
});
67+
function MockDate(input?: any): Date {
68+
return input ? new originalDate(input) : (expected[mockDateCount++][":date"].value as Date);
69+
}
70+
MockDate.now = () => MockDate().getTime();
71+
window.Date = MockDate as any;
72+
7773
const signingStream = getEventSigningTransformStream(
7874
"initial",
7975
{
8076
sign: mockMessageSigner,
8177
signMessage: mockMessageSigner,
8278
},
83-
eventStreamCodec
79+
eventStreamCodec,
80+
async () => 0
8481
);
8582
const output: Array<MessageHeaders> = [];
8683

Diff for: packages/middleware-websocket/src/get-event-signing-stream.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
import { EventStreamCodec } from "@smithy/eventstream-codec";
2-
import { MessageHeaders, MessageSigner } from "@smithy/types";
2+
import { MessageHeaders, MessageSigner, Provider } from "@smithy/types";
33
import { fromHex } from "@smithy/util-hex-encoding";
44

55
/**
66
* Get a transform stream that signs the eventstream
77
* Implementation replicated from @aws-sdk/eventstream-handler-node::EventSigningStream
88
* but modified to be compatible with WHATWG stream interface
9+
*
10+
* @internal
911
*/
1012
export const getEventSigningTransformStream = (
1113
initialSignature: string,
1214
messageSigner: MessageSigner,
13-
eventStreamCodec: EventStreamCodec
15+
eventStreamCodec: EventStreamCodec,
16+
systemClockOffsetProvider: Provider<number>
1417
): TransformStream<Uint8Array, Uint8Array> => {
1518
let priorSignature = initialSignature;
1619
const transformer: Transformer<Uint8Array, Uint8Array> = {
1720
start() {},
1821
async transform(chunk, controller) {
1922
try {
20-
const now = new Date();
23+
const now = new Date(Date.now() + (await systemClockOffsetProvider()));
2124
const dateHeader: MessageHeaders = {
2225
":date": { type: "timestamp", value: now },
2326
};

0 commit comments

Comments
 (0)