From 33640c488b75b76ddd3c5b13889e9a1f736c842a Mon Sep 17 00:00:00 2001 From: Hui Zhao <10602282+HuiSF@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:03:02 -0700 Subject: [PATCH] fix(auth): request initiateAuth with retry (#13854) --- .../__mocks__/mocks.js | 22 +++++++++ .../__tests__/CognitoUser.test.js | 5 +- .../__tests__/cryptoSecureRandomInt.test.js | 47 +++++++++++++------ .../src/CognitoUser.js | 4 +- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/packages/amazon-cognito-identity-js/__mocks__/mocks.js b/packages/amazon-cognito-identity-js/__mocks__/mocks.js index 569c1b5a5dd..7ca44587597 100644 --- a/packages/amazon-cognito-identity-js/__mocks__/mocks.js +++ b/packages/amazon-cognito-identity-js/__mocks__/mocks.js @@ -29,6 +29,28 @@ export function netRequestMockSuccess(success, data = {}) { } } +/** + * Mock a single network request to be either successful or fail with an optional data object + * @param {boolean} success defines if a network request is successful + * @param {object?} optional data to return onSuccess, some tests requires specific object values + * @param {string?} optional operationName to provide clarity inside the testing function + */ +export function netRequestWithRetryMockSuccess(success, data = {}) { + if (success) { + jest + .spyOn(Client.prototype, 'requestWithRetry') + .mockImplementationOnce((...[, , callback]) => { + callback(null, data); + }); + } else { + jest + .spyOn(Client.prototype, 'requestWithRetry') + .mockImplementationOnce((...[, , callback]) => { + callback(networkError, null); + }); + } +} + /** * * @param {string} fnName function name within the AuthenticationHelper class diff --git a/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js b/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js index 23a015b5c4c..2033fe52e43 100644 --- a/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js +++ b/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js @@ -12,6 +12,7 @@ import { callback, authHelperMock, netRequestMockSuccess, + netRequestWithRetryMockSuccess, } from '../__mocks__/mocks'; import { @@ -1609,7 +1610,7 @@ describe('refreshSession()', () => { }); test('happy path for refresh session ', () => { - netRequestMockSuccess(true, { + netRequestWithRetryMockSuccess(true, { AuthenticationResult: { RefreshToken: null }, }); cognitoUser.refreshSession(...refreshSessionDefaults); @@ -1618,7 +1619,7 @@ describe('refreshSession()', () => { ); }); test('client throws an error ', () => { - netRequestMockSuccess(false); + netRequestWithRetryMockSuccess(false); cognitoUser.refreshSession(...refreshSessionDefaults); expect(callback.mock.calls[0][0]).toMatchObject(new Error('Network Error')); }); diff --git a/packages/amazon-cognito-identity-js/__tests__/cryptoSecureRandomInt.test.js b/packages/amazon-cognito-identity-js/__tests__/cryptoSecureRandomInt.test.js index c70e47777d4..abeb6bf8be1 100644 --- a/packages/amazon-cognito-identity-js/__tests__/cryptoSecureRandomInt.test.js +++ b/packages/amazon-cognito-identity-js/__tests__/cryptoSecureRandomInt.test.js @@ -1,5 +1,14 @@ const crypto = require('crypto'); +jest.mock('crypto', () => ({ + getRandomValues: jest.fn(), + randomBytes: jest.fn(() => ({ + readInt32LE: jest.fn().mockReturnValueOnce(54321), + })), +})); + +const mockCrypto = crypto; + describe('cryptoSecureRandomInt test', () => { let windowSpy; @@ -19,8 +28,8 @@ describe('cryptoSecureRandomInt test', () => { }, })); - const cryptoSecureRandomInt = require('../src/utils/cryptoSecureRandomInt') - .default; + const cryptoSecureRandomInt = + require('../src/utils/cryptoSecureRandomInt').default; expect(window.crypto).toBeTruthy(); expect(cryptoSecureRandomInt()).toBe(12345); }); @@ -33,27 +42,37 @@ describe('cryptoSecureRandomInt test', () => { }, })); - const cryptoSecureRandomInt = require('../src/utils/cryptoSecureRandomInt') - .default; + const cryptoSecureRandomInt = + require('../src/utils/cryptoSecureRandomInt').default; expect(window.msCrypto).toBeTruthy(); expect(cryptoSecureRandomInt()).toBe(67890); }); - test('crypto is set for Node', () => { - windowSpy.mockImplementation(() => ({ + test('crypto is set for Node (< 18)', () => { + windowSpy.mockImplementationOnce(() => ({ crypto: null, })); - const randomBytesMock = jest - .spyOn(crypto, 'randomBytes') - .mockImplementationOnce(() => ({ - readInt32LE: jest.fn().mockReturnValueOnce(54321), - })); + const originalGetRandomValues = mockCrypto.getRandomValues; - const cryptoSecureRandomInt = require('../src/utils/cryptoSecureRandomInt') - .default; + mockCrypto.getRandomValues = undefined; + + const cryptoSecureRandomInt = + require('../src/utils/cryptoSecureRandomInt').default; expect(cryptoSecureRandomInt()).toBe(54321); - randomBytesMock.mockRestore(); + mockCrypto.getRandomValues = originalGetRandomValues; + }); + + test('crypto is set for Node (>= 18)', () => { + windowSpy.mockImplementationOnce(() => ({ + crypto: null, + })); + + mockCrypto.getRandomValues.mockReturnValueOnce([54321]); + + const cryptoSecureRandomInt = + require('../src/utils/cryptoSecureRandomInt').default; + expect(cryptoSecureRandomInt()).toBe(54321); }); }); diff --git a/packages/amazon-cognito-identity-js/src/CognitoUser.js b/packages/amazon-cognito-identity-js/src/CognitoUser.js index 9dc61d9e228..2ccf7359442 100644 --- a/packages/amazon-cognito-identity-js/src/CognitoUser.js +++ b/packages/amazon-cognito-identity-js/src/CognitoUser.js @@ -1130,7 +1130,7 @@ export default class CognitoUser { UserAttributes: attributes, ClientMetadata: clientMetadata, }, - (err,result) => { + (err, result) => { if (err) { return callback(err, null); } @@ -1478,7 +1478,7 @@ export default class CognitoUser { if (this.getUserContextData()) { jsonReq.UserContextData = this.getUserContextData(); } - this.client.request('InitiateAuth', jsonReq, (err, authResult) => { + this.client.requestWithRetry('InitiateAuth', jsonReq, (err, authResult) => { if (err) { if (err.code === 'NotAuthorizedException') { this.clearCachedUser();