From 8156cc9c84713541c236e41a8928f03368052bef Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Thu, 6 Feb 2020 11:05:52 -0800 Subject: [PATCH] fix(@aws-amplify/core): Support clock skew (#4844) * fix(@aws-amplify/core): AWS.config.systemClockOffset for signing requests * Add Util/Date to support new Date() with clock offset * Add tests for Utils.Date * Signer users Util.Date * Remove unused "date" for mocking * Rename Date to DateUtils to allow "import { DateUtils }" * Reference DateUtils directly vs. Utils.DateUtils * Move DateUtils.test.ts so that core can build * Add annotation to clockOffset for documentation & intellisense * Fix test path * Improve JSDoc Co-authored-by: Thiago Hirata Co-authored-by: Sam Martinez --- packages/core/__tests__/DateUtils-test.ts | 37 +++++++++++++++++++++++ packages/core/__tests__/Signer-test.ts | 6 +++- packages/core/src/Signer.ts | 7 +++-- packages/core/src/Util/DateUtils.ts | 35 +++++++++++++++++++++ packages/core/src/Util/index.ts | 1 + 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 packages/core/__tests__/DateUtils-test.ts create mode 100644 packages/core/src/Util/DateUtils.ts diff --git a/packages/core/__tests__/DateUtils-test.ts b/packages/core/__tests__/DateUtils-test.ts new file mode 100644 index 00000000000..ff892529d1f --- /dev/null +++ b/packages/core/__tests__/DateUtils-test.ts @@ -0,0 +1,37 @@ +import { DateUtils } from '../src/Util/DateUtils'; + +// Mock Date (https://github.com/facebook/jest/issues/2234#issuecomment-308121037) +const staticDate = new Date('2020-01-01'); +// @ts-ignore Type 'typeof Date' is not assignable to type 'DateConstructor'. +Date = class extends Date { + // @ts-ignore Constructors for derived classes must contain a 'super' call.ts(2377) + constructor() { + return staticDate; + } +}; + +describe('Date', () => { + describe('getDateWithClockOffset()', () => { + it('should return a new Date()', () => { + expect(DateUtils.getDateWithClockOffset()).toEqual(new Date()); + }); + }); + + describe('getClockOffset()', () => { + it('should default to 0', () => { + expect(DateUtils.getClockOffset()).toEqual(0); + }); + }); + + describe('with setClockOffset()', () => { + beforeAll(() => { + DateUtils.setClockOffset(1000); + }); + + describe('getDate()', () => { + expect(DateUtils.getDateWithClockOffset()).toEqual( + new Date(new Date().getTime() + 1000) + ); + }); + }); +}); diff --git a/packages/core/__tests__/Signer-test.ts b/packages/core/__tests__/Signer-test.ts index b3991353fa4..0eb43393aa4 100644 --- a/packages/core/__tests__/Signer-test.ts +++ b/packages/core/__tests__/Signer-test.ts @@ -20,13 +20,14 @@ jest.mock('../src/Facet', () => { }; return { update }; }; + return { AWS: ret, }; }); import Signer from '../src/Signer'; -import AWS from '../src'; +import { DateUtils } from '../src'; describe('Signer test', () => { describe('sign test', () => { @@ -45,6 +46,8 @@ describe('Signer test', () => { .spyOn(Date.prototype, 'toISOString') .mockReturnValueOnce('0'); + const getDateSpy = jest.spyOn(DateUtils, 'getDateWithClockOffset'); + const res = { headers: { Authorization: @@ -56,6 +59,7 @@ describe('Signer test', () => { url: url, }; expect(Signer.sign(request, access_info)).toEqual(res); + expect(getDateSpy).toHaveBeenCalledTimes(1); spyon.mockClear(); }); diff --git a/packages/core/src/Signer.ts b/packages/core/src/Signer.ts index 751dc4e15a4..66616f66b04 100644 --- a/packages/core/src/Signer.ts +++ b/packages/core/src/Signer.ts @@ -12,6 +12,7 @@ */ import { ConsoleLogger as Logger } from './Logger'; +import { DateUtils } from './Util'; import { AWS } from './Facet'; const logger = new Logger('Signer'), @@ -299,7 +300,7 @@ export default class Signer { request.headers = request.headers || {}; // datetime string and date string - const dt = new Date(), + const dt = DateUtils.getDateWithClockOffset(), dt_str = dt.toISOString().replace(/[:\-]|\.\d{3}/g, ''), d_str = dt_str.substr(0, 8); @@ -370,7 +371,9 @@ export default class Signer { const body: any = typeof urlOrRequest === 'object' ? urlOrRequest.body : undefined; - const now = new Date().toISOString().replace(/[:\-]|\.\d{3}/g, ''); + const now = DateUtils.getDateWithClockOffset() + .toISOString() + .replace(/[:\-]|\.\d{3}/g, ''); const today = now.substr(0, 8); // Intentionally discarding search const { search, ...parsedUrl } = url.parse(urlToSign, true, true); diff --git a/packages/core/src/Util/DateUtils.ts b/packages/core/src/Util/DateUtils.ts new file mode 100644 index 00000000000..88dc2c0e1c1 --- /dev/null +++ b/packages/core/src/Util/DateUtils.ts @@ -0,0 +1,35 @@ +/** + * Date & time utility functions to abstract the `aws-sdk` away from users. + * (v2 => v3 modularization is a breaking change) + * + * @see https://github.com/aws/aws-sdk-js/blob/6edf586dcc1de7fe8fbfbbd9a0d2b1847921e6e1/lib/util.js#L262 + */ + +export const DateUtils = { + /** + * Milliseconds to offset the date to compensate for clock skew between device & services + */ + clockOffset: 0, + + getDateWithClockOffset() { + if (DateUtils.clockOffset) { + return new Date(new Date().getTime() + DateUtils.clockOffset); + } else { + return new Date(); + } + }, + + /** + * @returns {number} Clock offset in milliseconds + */ + getClockOffset() { + return DateUtils.clockOffset; + }, + + /** + * @param {number} offset Clock offset in milliseconds + */ + setClockOffset(offset: number) { + DateUtils.clockOffset = offset; + }, +}; diff --git a/packages/core/src/Util/index.ts b/packages/core/src/Util/index.ts index 0e032f729f4..e22e2984509 100644 --- a/packages/core/src/Util/index.ts +++ b/packages/core/src/Util/index.ts @@ -1,3 +1,4 @@ export * from './Retry'; export { default as Mutex } from './Mutex'; export { default as Reachability } from './Reachability'; +export * from './DateUtils';