diff --git a/packages/eventstream-handler-node/src/EventSigningStream.spec.ts b/packages/eventstream-handler-node/src/EventSigningStream.spec.ts index 964d062c6367..5d6da99e0279 100644 --- a/packages/eventstream-handler-node/src/EventSigningStream.spec.ts +++ b/packages/eventstream-handler-node/src/EventSigningStream.spec.ts @@ -11,7 +11,7 @@ describe("EventSigningStream", () => { Date = originalDate; }); - it("should sign a eventstream payload properly", (done) => { + it("should sign an eventstream payload properly", (done) => { const eventStreamCodec = new EventStreamCodec(toUtf8, fromUtf8); const message1: Message = { headers: {}, @@ -44,17 +44,14 @@ describe("EventSigningStream", () => { .fn() .mockReturnValueOnce({ message: message1, signature: "7369676e617475726531" } as SignedMessage) //'signature1' .mockReturnValueOnce({ message: message2, signature: "7369676e617475726532" } as SignedMessage); //'signature2' - // mock 'new Date()' + let mockDateCount = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const mockDate = jest - .spyOn(global, "Date") - //@ts-ignore: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor/60918716#60918716 - .mockImplementation((input) => { - if (input) return new originalDate(input); - mockDateCount += 1; - return expected[mockDateCount - 1][":date"].value; - }); + function MockDate(input?: any): Date { + return input ? new originalDate(input) : (expected[mockDateCount++][":date"].value as Date); + } + MockDate.now = () => MockDate().getTime(); + global.Date = MockDate as any; + const signingStream = new EventSigningStream({ priorSignature: "initial", messageSigner: { @@ -62,6 +59,7 @@ describe("EventSigningStream", () => { signMessage: mockMessageSigner, }, eventStreamCodec, + systemClockOffsetProvider: async () => 0, }); const output: Array = []; signingStream.on("data", (chunk) => { diff --git a/packages/eventstream-handler-node/src/EventSigningStream.ts b/packages/eventstream-handler-node/src/EventSigningStream.ts index e45ef1a30c3a..3ab45cccd885 100644 --- a/packages/eventstream-handler-node/src/EventSigningStream.ts +++ b/packages/eventstream-handler-node/src/EventSigningStream.ts @@ -1,5 +1,5 @@ import { EventStreamCodec } from "@smithy/eventstream-codec"; -import { MessageHeaders, MessageSigner } from "@smithy/types"; +import { MessageHeaders, MessageSigner, Provider } from "@smithy/types"; import { Transform, TransformCallback, TransformOptions } from "stream"; /** @@ -9,6 +9,7 @@ export interface EventSigningStreamOptions extends TransformOptions { priorSignature: string; messageSigner: MessageSigner; eventStreamCodec: EventStreamCodec; + systemClockOffsetProvider: Provider; } /** @@ -20,6 +21,7 @@ export class EventSigningStream extends Transform { private priorSignature: string; private messageSigner: MessageSigner; private eventStreamCodec: EventStreamCodec; + private readonly systemClockOffsetProvider: Provider; constructor(options: EventSigningStreamOptions) { super({ @@ -32,11 +34,12 @@ export class EventSigningStream extends Transform { this.priorSignature = options.priorSignature; this.eventStreamCodec = options.eventStreamCodec; this.messageSigner = options.messageSigner; + this.systemClockOffsetProvider = options.systemClockOffsetProvider; } async _transform(chunk: Uint8Array, encoding: string, callback: TransformCallback): Promise { try { - const now = new Date(); + const now = new Date(Date.now() + (await this.systemClockOffsetProvider())); const dateHeader: MessageHeaders = { ":date": { type: "timestamp", value: now }, }; diff --git a/packages/eventstream-handler-node/src/EventStreamPayloadHandler.spec.ts b/packages/eventstream-handler-node/src/EventStreamPayloadHandler.spec.ts index a37ef5e160ca..d2a774126c6a 100644 --- a/packages/eventstream-handler-node/src/EventStreamPayloadHandler.spec.ts +++ b/packages/eventstream-handler-node/src/EventStreamPayloadHandler.spec.ts @@ -90,6 +90,7 @@ describe(EventStreamPayloadHandler.name, () => { priorSignature, eventStreamCodec: expect.anything(), messageSigner: expect.anything(), + systemClockOffsetProvider: expect.any(Function), }); }); @@ -121,6 +122,7 @@ describe(EventStreamPayloadHandler.name, () => { priorSignature, eventStreamCodec: expect.anything(), messageSigner: expect.anything(), + systemClockOffsetProvider: expect.any(Function), }); }); diff --git a/packages/eventstream-handler-node/src/EventStreamPayloadHandler.ts b/packages/eventstream-handler-node/src/EventStreamPayloadHandler.ts index 60fb215b4f2e..c750f9158fa6 100644 --- a/packages/eventstream-handler-node/src/EventStreamPayloadHandler.ts +++ b/packages/eventstream-handler-node/src/EventStreamPayloadHandler.ts @@ -23,6 +23,7 @@ export interface EventStreamPayloadHandlerOptions { messageSigner: Provider; utf8Encoder: Encoder; utf8Decoder: Decoder; + systemClockOffset?: number; } /** @@ -37,10 +38,12 @@ export interface EventStreamPayloadHandlerOptions { export class EventStreamPayloadHandler implements IEventStreamPayloadHandler { private readonly messageSigner: Provider; private readonly eventStreamCodec: EventStreamCodec; + private readonly systemClockOffsetProvider: Provider; constructor(options: EventStreamPayloadHandlerOptions) { this.messageSigner = options.messageSigner; this.eventStreamCodec = new EventStreamCodec(options.utf8Encoder, options.utf8Decoder); + this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0; } async handle( @@ -79,6 +82,7 @@ export class EventStreamPayloadHandler implements IEventStreamPayloadHandler { priorSignature, eventStreamCodec: this.eventStreamCodec, messageSigner: await this.messageSigner(), + systemClockOffsetProvider: this.systemClockOffsetProvider, }); pipeline(payloadStream, signingStream, request.body, (err: NodeJS.ErrnoException | null) => { diff --git a/packages/eventstream-handler-node/src/provider.ts b/packages/eventstream-handler-node/src/provider.ts index bd0706281ea8..63a4cddc3a8e 100644 --- a/packages/eventstream-handler-node/src/provider.ts +++ b/packages/eventstream-handler-node/src/provider.ts @@ -14,4 +14,5 @@ export const eventStreamPayloadHandlerProvider: EventStreamPayloadHandlerProvide utf8Encoder: Encoder; utf8Decoder: Decoder; messageSigner: Provider; + systemClockOffset?: number; }) => new EventStreamPayloadHandler(options); diff --git a/packages/middleware-websocket/src/EventStreamPayloadHandler.spec.ts b/packages/middleware-websocket/src/EventStreamPayloadHandler.spec.ts index 117ab62ab33c..f4975d7583d0 100644 --- a/packages/middleware-websocket/src/EventStreamPayloadHandler.spec.ts +++ b/packages/middleware-websocket/src/EventStreamPayloadHandler.spec.ts @@ -91,7 +91,12 @@ describe(EventStreamPayloadHandler.name, () => { }); expect(getEventSigningTransformStream).toHaveBeenCalledTimes(1); - expect(getEventSigningTransformStream).toHaveBeenCalledWith(priorSignature, expect.anything(), expect.anything()); + expect(getEventSigningTransformStream).toHaveBeenCalledWith( + priorSignature, + expect.anything(), + expect.anything(), + expect.anything() + ); }); 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, () => { }); expect(getEventSigningTransformStream).toHaveBeenCalledTimes(1); - expect(getEventSigningTransformStream).toHaveBeenCalledWith(priorSignature, expect.anything(), expect.anything()); + expect(getEventSigningTransformStream).toHaveBeenCalledWith( + priorSignature, + expect.anything(), + expect.anything(), + expect.anything() + ); }); it("should start piping to request payload through event signer if downstream middleware returns", async () => { diff --git a/packages/middleware-websocket/src/EventStreamPayloadHandler.ts b/packages/middleware-websocket/src/EventStreamPayloadHandler.ts index 5551a59fc4fb..f33dbcbc7dea 100644 --- a/packages/middleware-websocket/src/EventStreamPayloadHandler.ts +++ b/packages/middleware-websocket/src/EventStreamPayloadHandler.ts @@ -19,6 +19,7 @@ export interface EventStreamPayloadHandlerOptions { messageSigner: Provider; utf8Encoder: Encoder; utf8Decoder: Decoder; + systemClockOffset?: number; } /** @@ -31,10 +32,12 @@ export interface EventStreamPayloadHandlerOptions { export class EventStreamPayloadHandler implements IEventStreamPayloadHandler { private readonly messageSigner: Provider; private readonly eventStreamCodec: EventStreamCodec; + private readonly systemClockOffsetProvider: Provider; constructor(options: EventStreamPayloadHandlerOptions) { this.messageSigner = options.messageSigner; this.eventStreamCodec = new EventStreamCodec(options.utf8Encoder, options.utf8Decoder); + this.systemClockOffsetProvider = async () => options.systemClockOffset ?? 0; } async handle( @@ -69,7 +72,8 @@ export class EventStreamPayloadHandler implements IEventStreamPayloadHandler { const signingStream = getEventSigningTransformStream( priorSignature, await this.messageSigner(), - this.eventStreamCodec + this.eventStreamCodec, + this.systemClockOffsetProvider ); const signedPayload = payload.pipeThrough(signingStream); diff --git a/packages/middleware-websocket/src/eventstream-payload-handler-provider.ts b/packages/middleware-websocket/src/eventstream-payload-handler-provider.ts index e3d1b6ae8736..b5bb7f3fca60 100644 --- a/packages/middleware-websocket/src/eventstream-payload-handler-provider.ts +++ b/packages/middleware-websocket/src/eventstream-payload-handler-provider.ts @@ -7,4 +7,5 @@ export const eventStreamPayloadHandlerProvider: EventStreamPayloadHandlerProvide utf8Encoder: Encoder; utf8Decoder: Decoder; messageSigner: Provider; + systemClockOffset?: number; }) => new EventStreamPayloadHandler(options); diff --git a/packages/middleware-websocket/src/get-event-signing-stream.spec.ts b/packages/middleware-websocket/src/get-event-signing-stream.spec.ts index d88f98d47c92..2936ab3d3ee7 100644 --- a/packages/middleware-websocket/src/get-event-signing-stream.spec.ts +++ b/packages/middleware-websocket/src/get-event-signing-stream.spec.ts @@ -16,7 +16,7 @@ describe(getEventSigningTransformStream.name, () => { window.TransformStream = TransformStream; }); afterEach(() => { - Date = originalDate; + window.Date = originalDate; window.TransformStream = originalTransformStreamCtor; }); @@ -63,24 +63,21 @@ describe(getEventSigningTransformStream.name, () => { .mockReturnValueOnce({ message: message1, signature: "7369676e617475726531" } as SignedMessage) //'signature1' .mockReturnValueOnce({ message: message2, signature: "7369676e617475726532" } as SignedMessage); //'signature2' - // mock 'new Date()' let mockDateCount = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const mockDate = jest - .spyOn(window, "Date") - //@ts-ignore: https://stackoverflow.com/questions/60912023/jest-typescript-mock-date-constructor/60918716#60918716 - .mockImplementation((input) => { - if (input) return new originalDate(input); - mockDateCount += 1; - return expected[mockDateCount - 1][":date"].value; - }); + function MockDate(input?: any): Date { + return input ? new originalDate(input) : (expected[mockDateCount++][":date"].value as Date); + } + MockDate.now = () => MockDate().getTime(); + window.Date = MockDate as any; + const signingStream = getEventSigningTransformStream( "initial", { sign: mockMessageSigner, signMessage: mockMessageSigner, }, - eventStreamCodec + eventStreamCodec, + async () => 0 ); const output: Array = []; diff --git a/packages/middleware-websocket/src/get-event-signing-stream.ts b/packages/middleware-websocket/src/get-event-signing-stream.ts index 9da5909797d0..795dc2867980 100644 --- a/packages/middleware-websocket/src/get-event-signing-stream.ts +++ b/packages/middleware-websocket/src/get-event-signing-stream.ts @@ -1,23 +1,26 @@ import { EventStreamCodec } from "@smithy/eventstream-codec"; -import { MessageHeaders, MessageSigner } from "@smithy/types"; +import { MessageHeaders, MessageSigner, Provider } from "@smithy/types"; import { fromHex } from "@smithy/util-hex-encoding"; /** * Get a transform stream that signs the eventstream * Implementation replicated from @aws-sdk/eventstream-handler-node::EventSigningStream * but modified to be compatible with WHATWG stream interface + * + * @internal */ export const getEventSigningTransformStream = ( initialSignature: string, messageSigner: MessageSigner, - eventStreamCodec: EventStreamCodec + eventStreamCodec: EventStreamCodec, + systemClockOffsetProvider: Provider ): TransformStream => { let priorSignature = initialSignature; const transformer: Transformer = { start() {}, async transform(chunk, controller) { try { - const now = new Date(); + const now = new Date(Date.now() + (await systemClockOffsetProvider())); const dateHeader: MessageHeaders = { ":date": { type: "timestamp", value: now }, };