From 04627618afe526dd776ee678b723ed6e697f440d Mon Sep 17 00:00:00 2001 From: AllanZhengYP Date: Tue, 26 Dec 2023 13:52:09 -0800 Subject: [PATCH] revert(auth): standalone enable oauth listener for MPAs (#12753) * Revert "fix(auth): oAuthStore is used before a valid OAuth config is confirmed (#12748)" This reverts commit ae6438686a06f31a1bb69e4e8cadb584baac4b2a. * Revert "feat(auth): standalone enable oauth listener for MPAs (#12731)" This reverts commit 94d1fb2c91a539d336e24c3fe86881422cce0474. --- packages/auth/__tests__/mockData.ts | 27 - .../providers/cognito/autoSignIn.test.ts | 7 +- .../cognito/confirmResetPassword.test.ts | 4 - .../providers/cognito/confirmSignUp.test.ts | 4 - .../cognito/confirmUserAttribute.test.ts | 4 - .../providers/cognito/deleteUser.test.ts | 4 - .../cognito/deleteUserAttributes.test.ts | 4 - .../providers/cognito/fetchDevices.test.ts | 4 - .../providers/cognito/forgetDevice.test.ts | 4 - .../providers/cognito/getCurrentUser.test.ts | 4 - .../providers/cognito/rememberDevice.test.ts | 4 - .../cognito/resendSignUpCode.test.ts | 4 - .../providers/cognito/resetPassword.test.ts | 4 - .../sendUserAttributeVerificationCode.test.ts | 4 - .../providers/cognito/setUpTOTP.test.ts | 4 - .../cognito/signInErrorCases.test.ts | 4 - .../cognito/signInWithCustomAuth.test.ts | 5 - .../cognito/signInWithCustomSRPAuth.test.ts | 5 - .../cognito/signInWithRedirect.test.ts | 490 ++++++++---------- .../providers/cognito/signInWithSRP.test.ts | 5 - .../cognito/signInWithUserPassword.test.ts | 5 - .../providers/cognito/signUp.test.ts | 4 - .../cognito/updateMFAPreference.test.ts | 4 - .../providers/cognito/updatePassword.test.ts | 4 - .../cognito/updateUserAttribute.test.ts | 4 - .../cognito/updateUserAttributes.test.ts | 4 - .../oauth/attemptCompleteOAuthFlow.test.ts | 148 ------ .../utils/oauth/completeOAuthFlow.test.ts | 285 ---------- .../cognito/utils/oauth/validateState.test.ts | 62 --- .../providers/cognito/verifyTOTPSetup.test.ts | 4 - .../auth/enable-oauth-listener/package.json | 7 - packages/auth/jest.config.js | 8 +- packages/auth/package.json | 12 +- .../cognito/apis/signInWithRedirect.ts | 462 +++++++++++++++-- .../utils/oauth/attemptCompleteOAuthFlow.ts | 64 --- .../cognito/utils/oauth/completeOAuthFlow.ts | 261 ---------- .../cognito/utils/oauth/createOAuthError.ts | 17 - .../utils/oauth/enableOAuthListener.native.ts | 4 - .../utils/oauth/enableOAuthListener.ts | 23 - .../cognito/utils/oauth/handleFailure.ts | 22 - .../providers/cognito/utils/oauth/index.ts | 4 - .../cognito/utils/oauth/inflightPromise.ts | 14 - .../cognito/utils/oauth/oAuthStore.ts | 7 - .../cognito/utils/oauth/validateState.ts | 28 - .../auth/enable-oauth-listener/package.json | 8 - packages/aws-amplify/package.json | 42 +- .../src/auth/enableOAuthListener.ts | 4 - packages/core/__tests__/HubClass.test.ts | 4 +- packages/core/src/libraryUtils.ts | 1 - packages/core/src/singleton/Amplify.ts | 37 +- .../core/src/singleton/Auth/utils/index.ts | 1 + packages/core/src/singleton/constants.ts | 4 - 52 files changed, 683 insertions(+), 1470 deletions(-) delete mode 100644 packages/auth/__tests__/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.test.ts delete mode 100644 packages/auth/__tests__/providers/cognito/utils/oauth/completeOAuthFlow.test.ts delete mode 100644 packages/auth/__tests__/providers/cognito/utils/oauth/validateState.test.ts delete mode 100644 packages/auth/enable-oauth-listener/package.json delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/completeOAuthFlow.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/createOAuthError.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.native.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/handleFailure.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/inflightPromise.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/oAuthStore.ts delete mode 100644 packages/auth/src/providers/cognito/utils/oauth/validateState.ts delete mode 100644 packages/aws-amplify/auth/enable-oauth-listener/package.json delete mode 100644 packages/aws-amplify/src/auth/enableOAuthListener.ts delete mode 100644 packages/core/src/singleton/constants.ts diff --git a/packages/auth/__tests__/mockData.ts b/packages/auth/__tests__/mockData.ts index 1b308cffd02..3ca3fc7ae3f 100644 --- a/packages/auth/__tests__/mockData.ts +++ b/packages/auth/__tests__/mockData.ts @@ -156,30 +156,3 @@ export const transformedMockData = [ name: 'react-native', }, ]; - -export const mockAuthConfigWithOAuth = { - Auth: { - Cognito: { - identityPoolId: 'identityPoolId', - userPoolClientId: 'userPoolClientId', - userPoolId: 'userPoolId', - loginWith: { - username: true, - oauth: { - domain: 'oauth.domain.com', - scopes: [ - 'phone', - 'email', - 'openid', - 'profile', - 'aws.cognito.signin.user.admin', - ], - redirectSignIn: ['http://localhost:3000/'], - redirectSignOut: ['http://localhost:3000/'], - responseType: 'code' as const, - providers: ['Google' as const], - }, - }, - }, - }, -}; diff --git a/packages/auth/__tests__/providers/cognito/autoSignIn.test.ts b/packages/auth/__tests__/providers/cognito/autoSignIn.test.ts index 330a930edbb..db8ac93c9eb 100644 --- a/packages/auth/__tests__/providers/cognito/autoSignIn.test.ts +++ b/packages/auth/__tests__/providers/cognito/autoSignIn.test.ts @@ -16,11 +16,6 @@ import { Amplify } from 'aws-amplify'; import * as initiateAuthHelpers from '../../../src/providers/cognito/utils/signInHelpers'; import { AuthError } from '../../../src/errors/AuthError'; -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); - const authConfig = { Cognito: { userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398', @@ -39,7 +34,7 @@ describe('Auto sign-in API Happy Path Cases:', () => { signUpSpy = jest .spyOn(signUpClient, 'signUp') .mockImplementationOnce( - async () => ({ UserConfirmed: true }) as SignUpCommandOutput + async () => ({ UserConfirmed: true } as SignUpCommandOutput) ); handleUserSRPAuthflowSpy = jest diff --git a/packages/auth/__tests__/providers/cognito/confirmResetPassword.test.ts b/packages/auth/__tests__/providers/cognito/confirmResetPassword.test.ts index 19fb9d6c7c1..62220174c56 100644 --- a/packages/auth/__tests__/providers/cognito/confirmResetPassword.test.ts +++ b/packages/auth/__tests__/providers/cognito/confirmResetPassword.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/confirmSignUp.test.ts b/packages/auth/__tests__/providers/cognito/confirmSignUp.test.ts index 56f8af67338..256c5d23e5e 100644 --- a/packages/auth/__tests__/providers/cognito/confirmSignUp.test.ts +++ b/packages/auth/__tests__/providers/cognito/confirmSignUp.test.ts @@ -16,10 +16,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/confirmUserAttribute.test.ts b/packages/auth/__tests__/providers/cognito/confirmUserAttribute.test.ts index 6c23fb93023..1e2e9d1bdb9 100644 --- a/packages/auth/__tests__/providers/cognito/confirmUserAttribute.test.ts +++ b/packages/auth/__tests__/providers/cognito/confirmUserAttribute.test.ts @@ -16,10 +16,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/deleteUser.test.ts b/packages/auth/__tests__/providers/cognito/deleteUser.test.ts index 59ac25e856e..156aa5cd00a 100644 --- a/packages/auth/__tests__/providers/cognito/deleteUser.test.ts +++ b/packages/auth/__tests__/providers/cognito/deleteUser.test.ts @@ -16,10 +16,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock('../../../src/providers/cognito/apis/signOut'); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' diff --git a/packages/auth/__tests__/providers/cognito/deleteUserAttributes.test.ts b/packages/auth/__tests__/providers/cognito/deleteUserAttributes.test.ts index c3e855204ea..89409f318d5 100644 --- a/packages/auth/__tests__/providers/cognito/deleteUserAttributes.test.ts +++ b/packages/auth/__tests__/providers/cognito/deleteUserAttributes.test.ts @@ -14,10 +14,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/fetchDevices.test.ts b/packages/auth/__tests__/providers/cognito/fetchDevices.test.ts index ccaacae5d79..c7fd8e89ef7 100644 --- a/packages/auth/__tests__/providers/cognito/fetchDevices.test.ts +++ b/packages/auth/__tests__/providers/cognito/fetchDevices.test.ts @@ -14,10 +14,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/forgetDevice.test.ts b/packages/auth/__tests__/providers/cognito/forgetDevice.test.ts index 5ff7709276d..78244f1c577 100644 --- a/packages/auth/__tests__/providers/cognito/forgetDevice.test.ts +++ b/packages/auth/__tests__/providers/cognito/forgetDevice.test.ts @@ -16,10 +16,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/getCurrentUser.test.ts b/packages/auth/__tests__/providers/cognito/getCurrentUser.test.ts index d7ee5c9d31e..3df362f3baa 100644 --- a/packages/auth/__tests__/providers/cognito/getCurrentUser.test.ts +++ b/packages/auth/__tests__/providers/cognito/getCurrentUser.test.ts @@ -13,10 +13,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { Auth: { getTokens: jest.fn() }, getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); describe('getCurrentUser', () => { const mockedSub = 'mockedSub'; diff --git a/packages/auth/__tests__/providers/cognito/rememberDevice.test.ts b/packages/auth/__tests__/providers/cognito/rememberDevice.test.ts index 8979cc652a9..930b729a93f 100644 --- a/packages/auth/__tests__/providers/cognito/rememberDevice.test.ts +++ b/packages/auth/__tests__/providers/cognito/rememberDevice.test.ts @@ -16,10 +16,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/resendSignUpCode.test.ts b/packages/auth/__tests__/providers/cognito/resendSignUpCode.test.ts index a1809ec4c80..a4698fbee89 100644 --- a/packages/auth/__tests__/providers/cognito/resendSignUpCode.test.ts +++ b/packages/auth/__tests__/providers/cognito/resendSignUpCode.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/resetPassword.test.ts b/packages/auth/__tests__/providers/cognito/resetPassword.test.ts index ca11c9c9339..31a75be379a 100644 --- a/packages/auth/__tests__/providers/cognito/resetPassword.test.ts +++ b/packages/auth/__tests__/providers/cognito/resetPassword.test.ts @@ -14,10 +14,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/sendUserAttributeVerificationCode.test.ts b/packages/auth/__tests__/providers/cognito/sendUserAttributeVerificationCode.test.ts index 835b90b78c2..92399e59a33 100644 --- a/packages/auth/__tests__/providers/cognito/sendUserAttributeVerificationCode.test.ts +++ b/packages/auth/__tests__/providers/cognito/sendUserAttributeVerificationCode.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/setUpTOTP.test.ts b/packages/auth/__tests__/providers/cognito/setUpTOTP.test.ts index d3c76b99d32..4b83ec24769 100644 --- a/packages/auth/__tests__/providers/cognito/setUpTOTP.test.ts +++ b/packages/auth/__tests__/providers/cognito/setUpTOTP.test.ts @@ -14,10 +14,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/signInErrorCases.test.ts b/packages/auth/__tests__/providers/cognito/signInErrorCases.test.ts index 76d62551433..46d192f872f 100644 --- a/packages/auth/__tests__/providers/cognito/signInErrorCases.test.ts +++ b/packages/auth/__tests__/providers/cognito/signInErrorCases.test.ts @@ -16,10 +16,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock('../../../src/providers/cognito/apis/getCurrentUser'); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' diff --git a/packages/auth/__tests__/providers/cognito/signInWithCustomAuth.test.ts b/packages/auth/__tests__/providers/cognito/signInWithCustomAuth.test.ts index d6a13b60801..13674d158cb 100644 --- a/packages/auth/__tests__/providers/cognito/signInWithCustomAuth.test.ts +++ b/packages/auth/__tests__/providers/cognito/signInWithCustomAuth.test.ts @@ -13,11 +13,6 @@ import { } from '../../../src/providers/cognito/tokenProvider'; import * as clients from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider'; -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); - const authConfig = { Cognito: { userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398', diff --git a/packages/auth/__tests__/providers/cognito/signInWithCustomSRPAuth.test.ts b/packages/auth/__tests__/providers/cognito/signInWithCustomSRPAuth.test.ts index 869ae829bec..72e5ab315f5 100644 --- a/packages/auth/__tests__/providers/cognito/signInWithCustomSRPAuth.test.ts +++ b/packages/auth/__tests__/providers/cognito/signInWithCustomSRPAuth.test.ts @@ -13,11 +13,6 @@ import { } from '../../../src/providers/cognito/tokenProvider'; import * as clients from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider'; -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); - const authConfig = { Cognito: { userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398', diff --git a/packages/auth/__tests__/providers/cognito/signInWithRedirect.test.ts b/packages/auth/__tests__/providers/cognito/signInWithRedirect.test.ts index 7d11bb49129..da55fbc15f8 100644 --- a/packages/auth/__tests__/providers/cognito/signInWithRedirect.test.ts +++ b/packages/auth/__tests__/providers/cognito/signInWithRedirect.test.ts @@ -1,292 +1,258 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Amplify } from '@aws-amplify/core'; +import { AuthError } from '../../../src/errors/AuthError'; +import { Hub } from '@aws-amplify/core'; import { - assertOAuthConfig, - assertTokenProviderConfig, - urlSafeEncode, - isBrowser, - ADD_OAUTH_LISTENER, -} from '@aws-amplify/core/internals/utils'; -import { assertUserNotAuthenticated } from '../../../src/providers/cognito/utils/signInHelpers'; + INVALID_ORIGIN_EXCEPTION, + INVALID_REDIRECT_EXCEPTION, +} from '../../../src/errors/constants'; +import { getRedirectUrl } from '../../../src/providers/cognito/utils/oauth/getRedirectUrl'; +import { getRedirectUrl as getRedirectUrlRN } from '../../../src/providers/cognito/utils/oauth/getRedirectUrl.native'; import { - generateCodeVerifier, - generateState, -} from '../../../src/providers/cognito/utils/oauth'; -import { getAuthUserAgentValue, openAuthSession } from '../../../src/utils'; -import { - handleFailure, - oAuthStore, - completeOAuthFlow, -} from '../../../src/providers/cognito/utils/oauth'; -import { attemptCompleteOAuthFlow } from '../../../src/providers/cognito/utils/oauth/attemptCompleteOAuthFlow'; -import { createOAuthError } from '../../../src/providers/cognito/utils/oauth/createOAuthError'; - -import { signInWithRedirect } from '../../../src/providers/cognito/apis/signInWithRedirect'; - -import type { OAuthStore } from '../../../src/providers/cognito/utils/types'; -import { mockAuthConfigWithOAuth } from '../../mockData'; - -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - assertOAuthConfig: jest.fn(), - assertTokenProviderConfig: jest.fn(), - urlSafeEncode: jest.fn(), - isBrowser: jest.fn(() => true), + parseRedirectURL, + signInWithRedirect, +} from '../../../src/providers/cognito/apis/signInWithRedirect'; +import { openAuthSession } from '../../../src/utils'; +import { AMPLIFY_SYMBOL } from '@aws-amplify/core/internals/utils'; +jest.mock('../../../src/utils/openAuthSession'); +jest.mock('@aws-amplify/core', () => ({ + ...(jest.createMockFromModule('@aws-amplify/core') as object), + Amplify: { + getConfig: jest.fn(() => ({ + Auth: { + Cognito: { + userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398', + userPoolId: 'us-west-2_zzzzz', + identityPoolId: 'us-west-2:xxxxxx', + loginWith: { + oauth: { + domain: 'my_cognito_domain', + redirectSignIn: ['http://localhost:3000/'], + redirectSignOut: ['http://localhost:3000/'], + responseType: 'code', + scopes: [ + 'email', + 'openid', + 'profile', + 'aws.cognito.signin.user.admin', + ], + }, + }, + }, + }, + })), + }, + Hub: { dispatch: jest.fn(), listen: jest.fn() }, })); -jest.mock('@aws-amplify/core', () => { - const { ADD_OAUTH_LISTENER } = jest.requireActual( - '@aws-amplify/core/internals/utils' - ); - return { - Amplify: { - getConfig: jest.fn(() => mockAuthConfigWithOAuth), - [ADD_OAUTH_LISTENER]: jest.fn(), - }, - ConsoleLogger: jest.fn(), - }; -}); -jest.mock('../../../src/providers/cognito/utils/signInHelpers'); -jest.mock('../../../src/providers/cognito/utils/oauth', () => ({ - ...jest.requireActual('../../../src/providers/cognito/utils/oauth'), - completeOAuthFlow: jest.fn(), - handleFailure: jest.fn(), - generateCodeVerifier: jest.fn(), - generateState: jest.fn(), -})); -jest.mock('../../../src/providers/cognito/utils/oauth/oAuthStore', () => ({ - oAuthStore: { - setAuthConfig: jest.fn(), - storeOAuthInFlight: jest.fn(), - storeOAuthState: jest.fn(), - storePKCE: jest.fn(), - loadOAuthInFlight: jest.fn(), - loadOAuthSignIn: jest.fn(), - storeOAuthSignIn: jest.fn(), - loadOAuthState: jest.fn(), - loadPKCE: jest.fn(), - clearOAuthData: jest.fn(), - clearOAuthInflightData: jest.fn(), - } as OAuthStore, -})); -jest.mock('../../../src/providers/cognito/utils/oauth/createOAuthError'); -jest.mock('../../../src/utils'); - -const mockAssertOAuthConfig = assertOAuthConfig as jest.Mock; -const mockAssertTokenProviderConfig = assertTokenProviderConfig as jest.Mock; -const mockUrlSafeEncode = urlSafeEncode as jest.Mock; -const mockAssertUserNotAuthenticated = assertUserNotAuthenticated as jest.Mock; -const mockOpenAuthSession = openAuthSession as jest.Mock; -const mockGenerateCodeVerifier = generateCodeVerifier as jest.Mock; -const mockGenerateState = generateState as jest.Mock; -const mockIsBrowser = isBrowser as jest.Mock; - -const mockCompleteOAuthFlow = completeOAuthFlow as jest.Mock; -const mockGetAuthUserAgentValue = getAuthUserAgentValue as jest.Mock; -const mockHandleFailure = handleFailure as jest.Mock; -const mockCreateOAuthError = createOAuthError as jest.Mock; - -describe('signInWithRedirect', () => { - const mockState = 'oauth_state'; - const mockCodeVerifierValue = 'code_verifier_value'; - const mockCodeVerifierMethod = 'S256'; - const mockCodeChallenge = 'code_challenge'; - const mockToCodeChallenge = jest.fn(() => mockCodeChallenge); - - beforeAll(() => { - mockGenerateState.mockReturnValue(mockState); - mockGenerateCodeVerifier.mockReturnValue({ - value: mockCodeVerifierValue, - method: mockCodeVerifierMethod, - toCodeChallenge: mockToCodeChallenge, - }); - mockUrlSafeEncode.mockImplementation(customState => customState); - mockIsBrowser.mockReturnValue(true); - }); - - afterEach(() => { - mockAssertTokenProviderConfig.mockClear(); - mockAssertOAuthConfig.mockClear(); - mockAssertUserNotAuthenticated.mockClear(); - mockUrlSafeEncode.mockClear(); - mockGenerateState.mockClear(); - mockGenerateCodeVerifier.mockClear(); - mockOpenAuthSession.mockClear(); - mockToCodeChallenge.mockClear(); - mockHandleFailure.mockClear(); - mockCompleteOAuthFlow.mockClear(); - - (oAuthStore.setAuthConfig as jest.Mock).mockClear(); - (oAuthStore.storeOAuthInFlight as jest.Mock).mockClear(); - (oAuthStore.storeOAuthState as jest.Mock).mockClear(); - (oAuthStore.storePKCE as jest.Mock).mockClear(); - }); - - it('invokes dependent functions with expected parameters', async () => { - await signInWithRedirect({ provider: 'Google' }); - expect(mockAssertTokenProviderConfig).toHaveBeenCalledTimes(1); - expect(mockAssertOAuthConfig).toHaveBeenCalledTimes(1); - expect(oAuthStore.setAuthConfig).toHaveBeenCalledWith( - mockAuthConfigWithOAuth.Auth.Cognito - ); - expect(mockAssertUserNotAuthenticated).toHaveBeenCalledTimes(1); - - expect(mockGenerateState).toHaveBeenCalledTimes(1); - expect(mockGenerateCodeVerifier).toHaveBeenCalledWith(128); - expect(oAuthStore.storeOAuthInFlight).toHaveBeenCalledWith(true); - expect(oAuthStore.storeOAuthState).toHaveBeenCalledWith(mockState); - expect(oAuthStore.storePKCE).toHaveBeenCalledWith(mockCodeVerifierValue); - expect(mockToCodeChallenge).toHaveBeenCalledTimes(1); - - expect(mockOpenAuthSession).toHaveBeenCalledTimes(1); - const [oauthUrl, redirectSignIn, preferPrivateSession] = - mockOpenAuthSession.mock.calls[0]; - expect(oauthUrl).toStrictEqual( - 'https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=Google&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256' - ); - expect(redirectSignIn).toEqual( - mockAuthConfigWithOAuth.Auth.Cognito.loginWith.oauth.redirectSignIn - ); - expect(preferPrivateSession).toBeUndefined(); +describe('signInWithRedirect API', () => { + it('should pass correct arguments to oauth', () => { + // TODO ADD tests }); - it('uses "Cognito" as the default provider if not specified', async () => { - const expectedDefaultProvider = 'COGNITO'; - await signInWithRedirect(); - const [oauthUrl] = mockOpenAuthSession.mock.calls[0]; - expect(oauthUrl).toStrictEqual( - `https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedDefaultProvider}&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256` - ); - }); - - it('uses custom provider when specified', async () => { - const expectedCustomProvider = 'PieAuth'; - await signInWithRedirect({ provider: { custom: expectedCustomProvider } }); - const [oauthUrl] = mockOpenAuthSession.mock.calls[0]; - expect(oauthUrl).toStrictEqual( - `https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedCustomProvider}&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256` - ); + it('should try to clear oauth data before starting an oauth flow.', async () => { + // TODO: ADD Test: previous test was invalid }); - it('uses custom state if specified', async () => { - const expectedCustomState = 'verify_me'; - await signInWithRedirect({ customState: expectedCustomState }); - expect(mockUrlSafeEncode).toHaveBeenCalledWith(expectedCustomState); - }); - - describe('specifications on Web', () => { - describe('side effect', () => { - it('attaches oauth listener to the Amplify singleton', async () => { - (oAuthStore.loadOAuthInFlight as jest.Mock).mockResolvedValueOnce( - false - ); - - expect(Amplify[ADD_OAUTH_LISTENER]).toHaveBeenCalledWith( - attemptCompleteOAuthFlow - ); + describe('signInWithRedirect API error cases', () => { + const oauthErrorMessage = 'an oauth error has occurred'; + const oauthError = new AuthError({ + name: 'OAuthSignInException', + message: oauthErrorMessage, + }); + const invalidStateOauthError = new AuthError({ + name: 'OAuthSignInException', + message: "An error occurred while validating the state", + }); + const mockOpenAuthSession = openAuthSession as jest.Mock; + const mockHubDispatch = Hub.dispatch as jest.Mock; + + afterEach(() => { + mockOpenAuthSession.mockReset(); + }); + + it('should throw and dispatch when an error is returned in the URL in RN', async () => { + mockOpenAuthSession.mockResolvedValueOnce({ + type: 'error', + error: oauthErrorMessage, }); + + await expect(signInWithRedirect()).rejects.toThrow(oauthError); + expect(Hub.dispatch).toHaveBeenCalledWith( + 'auth', + { + event: 'signInWithRedirect_failure', + data: { error: oauthError }, + }, + 'Auth', + AMPLIFY_SYMBOL + ); }); - }); - - describe('specifications on react-native', () => { - it('invokes `completeOAuthFlow` when `openAuthSession`completes', async () => { - const mockOpenAuthSessionResult = { + + it('should throw when state is not valid after calling signInWithRedirect', async () => { + mockOpenAuthSession.mockResolvedValueOnce({ type: 'success', - url: 'http://redrect-in-react-native.com', - }; - mockOpenAuthSession.mockResolvedValueOnce(mockOpenAuthSessionResult); - - await signInWithRedirect({ - provider: 'Google', - options: { preferPrivateSession: true }, + url: 'http:localhost:3000/oauth2/redirect?state=invalid_state&code=mock_code&scope=openid%20email%20profile&session_state=mock_session_state', }); - - expect(mockCompleteOAuthFlow).toHaveBeenCalledWith( - expect.objectContaining({ - currentUrl: mockOpenAuthSessionResult.url, - preferPrivateSession: true, - }) + + await expect(signInWithRedirect()).rejects.toThrow(invalidStateOauthError); + expect(mockHubDispatch).toHaveBeenCalledWith( + 'auth', + { + event: 'signInWithRedirect_failure', + data: { error: invalidStateOauthError }, + }, + 'Auth', + AMPLIFY_SYMBOL ); - expect(mockGetAuthUserAgentValue).toHaveBeenCalledTimes(1); + }); - - it('invokes `handleFailure` with the error created by `createOAuthError` when `openAuthSession` completes with error', async () => { - const mockOpenAuthSessionResult = { - type: 'error', - error: new Error('some error'), - }; - mockCreateOAuthError.mockReturnValueOnce(mockOpenAuthSessionResult.error); - - mockOpenAuthSession.mockResolvedValueOnce(mockOpenAuthSessionResult); - - await expect( - signInWithRedirect({ - provider: 'Google', - options: { preferPrivateSession: true }, - }) - ).rejects.toThrow(mockOpenAuthSessionResult.error); - - expect(mockCreateOAuthError).toHaveBeenCalledWith( - String(mockOpenAuthSessionResult.error) - ); - expect(mockHandleFailure).toHaveBeenCalledWith( - mockOpenAuthSessionResult.error + + it('should dispatch the signInWithRedirect_failure event when an error is returned in the URL', async () => { + Object.defineProperty(window, 'location', { + value: { + href: 'http:localhost:3000/oauth2/redirect?error=OAuthSignInException&error_description=an+oauth+error+has+occurred', + }, + writable: true, + }); + await expect(parseRedirectURL).not.toThrow(); + expect(mockHubDispatch).toHaveBeenCalledWith( + 'auth', + { + event: 'signInWithRedirect_failure', + data: { error: oauthError }, + }, + 'Auth', + AMPLIFY_SYMBOL ); }); - - it('invokes `handleFailure` with the error thrown from `completeOAuthFlow`', async () => { - const expectedError = new Error('some error'); - const mockOpenAuthSessionResult = { - type: 'success', - url: 'https://url.com', - }; - mockOpenAuthSession.mockResolvedValueOnce(mockOpenAuthSessionResult); - mockCompleteOAuthFlow.mockRejectedValueOnce(expectedError); - - await expect( - signInWithRedirect({ - provider: 'Google', - options: { preferPrivateSession: true }, - }) - ).rejects.toThrow(expectedError); - - expect(mockCompleteOAuthFlow).toHaveBeenCalledWith( - expect.objectContaining({ - currentUrl: mockOpenAuthSessionResult.url, - }) + + it('should dispatch the signInWithRedirect_failure event when state is not valid', async () => { + Object.defineProperty(window, 'location', { + value: { + href: `http:localhost:3000/oauth2/redirect?state='invalid_state'&code=mock_code&scope=openid%20email%20profile&session_state=mock_session_state`, + }, + writable: true, + }); + await expect(parseRedirectURL).not.toThrow(); + expect(mockHubDispatch).toHaveBeenCalledWith( + 'auth', + { + event: 'signInWithRedirect_failure', + data: { error: oauthError }, + }, + 'Auth', + AMPLIFY_SYMBOL ); - expect(mockHandleFailure).toHaveBeenCalledWith(expectedError); }); }); - - describe('errors', () => { - it('rethrows error thrown from `assertTokenProviderConfig`', async () => { - const mockError = new Error('mock error'); - mockAssertTokenProviderConfig.mockImplementationOnce(() => { - throw mockError; + + describe('getRedirectUrl on web', () => { + const originalWindowLocation = window.location; + + const currentWindownLocationParamsList: { + origin: string; + pathname: string; + }[] = [ + { origin: 'https://example.com', pathname: '/' }, + { origin: 'https://example.com', pathname: '/app' }, + { origin: 'https://example.com', pathname: '/app/page' }, + { origin: 'http://localhost:3000', pathname: '/' }, + { origin: 'http://localhost:3000', pathname: '/app' }, + ]; + afterEach(() => { + Object.defineProperty(globalThis, 'window', { + value: originalWindowLocation, }); - - await expect(signInWithRedirect()).rejects.toThrow(mockError); }); - - it('rethrows error thrown from `assertOAuthConfig`', async () => { - const mockError = new Error('mock error'); - mockAssertOAuthConfig.mockImplementationOnce(() => { - throw mockError; + it.each(currentWindownLocationParamsList)( + 'should pick the url that matches the current window', + async windowParams => { + const { origin, pathname } = windowParams; + Object.defineProperty(globalThis, 'window', { + value: { location: { origin, pathname } }, + writable: true, + }); + const redirectsFromConfig = [ + 'http://localhost:3000/', + 'https://example.com/', + 'https://example.com/app', + 'https://example.com/app/page', + 'http://localhost:3000/app', + ]; + const redirect = getRedirectUrl(redirectsFromConfig); + expect(redirect).toBe( + redirectsFromConfig.find(redir => redir === redirect) + ); + } + ); + it('should pick the first url that is comming from a different pathname but same domain', async () => { + Object.defineProperty(globalThis, 'window', { + value: { + location: { + origin: 'https://example.com', + pathname: '/app', + hostname: 'example.com', + }, + }, + writable: true, }); - - await expect(signInWithRedirect()).rejects.toThrow(mockError); + const redirect = getRedirectUrl(['https://example.com/another-app']); + expect(redirect).toBe('https://example.com/another-app'); }); - - it('rethrow error thrown from `assertUserNotAuthenticated`', async () => { - const mockError = new Error('mock error'); - mockAssertUserNotAuthenticated.mockImplementationOnce(() => { - throw mockError; + + it('should throw if the url is not comming from the same origin', async () => { + Object.defineProperty(globalThis, 'window', { + value: { + location: { origin: 'https://differentorigin.com', pathname: '/app' }, + }, + writable: true, }); - - await expect(signInWithRedirect()).rejects.toThrow(mockError); + + try { + return getRedirectUrl(['http://localhost:3000/', 'https://example.com/']); + } catch (error: any) { + expect(error).toBeInstanceOf(AuthError); + expect(error.name).toBe(INVALID_ORIGIN_EXCEPTION); + } + }); + + it('should throw if the url is not found or invalid', async () => { + Object.defineProperty(globalThis, 'window', { + value: { + location: { origin: 'http://localhost:3000', pathname: '/' }, + }, + writable: true, + }); + + try { + return getRedirectUrl(['novalid']); + } catch (error: any) { + expect(error).toBeInstanceOf(AuthError); + expect(error.name).toBe(INVALID_REDIRECT_EXCEPTION); + } + }); + }); + + describe('getRedirectUrl on React Native', () => { + it('should pick the first non http or https redirect', async () => { + const redirect = getRedirectUrlRN([ + 'app:custom', + 'https://example.com/', + 'http://localhost:3000/', + ]); + expect(redirect).toBe('app:custom'); + }); + it('should throw if the redirect is invalid or not found', async () => { + try { + return getRedirectUrlRN(['invalid']); + } catch (error: any) { + expect(error).toBeInstanceOf(AuthError); + expect(error.name).toBe(INVALID_REDIRECT_EXCEPTION); + } }); }); }); + + diff --git a/packages/auth/__tests__/providers/cognito/signInWithSRP.test.ts b/packages/auth/__tests__/providers/cognito/signInWithSRP.test.ts index 38936c7222e..6845b63b1e7 100644 --- a/packages/auth/__tests__/providers/cognito/signInWithSRP.test.ts +++ b/packages/auth/__tests__/providers/cognito/signInWithSRP.test.ts @@ -15,11 +15,6 @@ import { AuthError } from '../../../src'; import { createKeysForAuthStorage } from '../../../src/providers/cognito/tokenProvider/TokenStore'; import * as clients from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider'; -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); - const authConfig = { Cognito: { userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398', diff --git a/packages/auth/__tests__/providers/cognito/signInWithUserPassword.test.ts b/packages/auth/__tests__/providers/cognito/signInWithUserPassword.test.ts index bc49ea94e8c..87de0c29eb2 100644 --- a/packages/auth/__tests__/providers/cognito/signInWithUserPassword.test.ts +++ b/packages/auth/__tests__/providers/cognito/signInWithUserPassword.test.ts @@ -13,11 +13,6 @@ import { } from '../../../src/providers/cognito/tokenProvider'; import * as clients from '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider'; -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); - const authConfig = { Cognito: { userPoolClientId: '111111-aaaaa-42d8-891d-ee81a1549398', diff --git a/packages/auth/__tests__/providers/cognito/signUp.test.ts b/packages/auth/__tests__/providers/cognito/signUp.test.ts index bc87d00dae0..5ff8729e00c 100644 --- a/packages/auth/__tests__/providers/cognito/signUp.test.ts +++ b/packages/auth/__tests__/providers/cognito/signUp.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/updateMFAPreference.test.ts b/packages/auth/__tests__/providers/cognito/updateMFAPreference.test.ts index 0135a2f9710..8246bb27392 100644 --- a/packages/auth/__tests__/providers/cognito/updateMFAPreference.test.ts +++ b/packages/auth/__tests__/providers/cognito/updateMFAPreference.test.ts @@ -19,10 +19,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/updatePassword.test.ts b/packages/auth/__tests__/providers/cognito/updatePassword.test.ts index 21902fd76cf..a7435967921 100644 --- a/packages/auth/__tests__/providers/cognito/updatePassword.test.ts +++ b/packages/auth/__tests__/providers/cognito/updatePassword.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/updateUserAttribute.test.ts b/packages/auth/__tests__/providers/cognito/updateUserAttribute.test.ts index 8826b4c7c55..1b35c20daef 100644 --- a/packages/auth/__tests__/providers/cognito/updateUserAttribute.test.ts +++ b/packages/auth/__tests__/providers/cognito/updateUserAttribute.test.ts @@ -12,10 +12,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock('../../../src/providers/cognito/apis/updateUserAttributes'); describe('updateUserAttribute API happy path cases', () => { diff --git a/packages/auth/__tests__/providers/cognito/updateUserAttributes.test.ts b/packages/auth/__tests__/providers/cognito/updateUserAttributes.test.ts index b30d123e1cf..9fa9170e069 100644 --- a/packages/auth/__tests__/providers/cognito/updateUserAttributes.test.ts +++ b/packages/auth/__tests__/providers/cognito/updateUserAttributes.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/__tests__/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.test.ts b/packages/auth/__tests__/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.test.ts deleted file mode 100644 index 3fd523eff96..00000000000 --- a/packages/auth/__tests__/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - assertOAuthConfig, - assertTokenProviderConfig, -} from '@aws-amplify/core/internals/utils'; - -import { attemptCompleteOAuthFlow } from '../../../../../src/providers/cognito/utils/oauth/attemptCompleteOAuthFlow'; -import { completeOAuthFlow } from '../../../../../src/providers/cognito/utils/oauth/completeOAuthFlow'; -import { getRedirectUrl } from '../../../../../src/providers/cognito/utils/oauth/getRedirectUrl'; -import { oAuthStore } from '../../../../../src/providers/cognito/utils/oauth/oAuthStore'; -import { addInflightPromise } from '../../../../../src/providers/cognito/utils/oauth/inflightPromise'; -import { cognitoUserPoolsTokenProvider } from '../../../../../src/providers/cognito/tokenProvider/tokenProvider'; -import { mockAuthConfigWithOAuth } from '../../../../mockData'; - -import type { OAuthStore } from '../../../../../src/providers/cognito/utils/types'; - -jest.mock('@aws-amplify/core/internals/utils'); -jest.mock('../../../../../src/providers/cognito/utils/oauth/completeOAuthFlow'); -jest.mock('../../../../../src/providers/cognito/utils/oauth/getRedirectUrl'); -jest.mock( - '../../../../../src/providers/cognito/utils/oauth/oAuthStore', - () => ({ - oAuthStore: { - setAuthConfig: jest.fn(), - storeOAuthInFlight: jest.fn(), - storeOAuthState: jest.fn(), - storePKCE: jest.fn(), - loadOAuthInFlight: jest.fn(), - loadOAuthSignIn: jest.fn(), - storeOAuthSignIn: jest.fn(), - loadOAuthState: jest.fn(), - loadPKCE: jest.fn(), - clearOAuthData: jest.fn(), - clearOAuthInflightData: jest.fn(), - } as OAuthStore, - }) -); -jest.mock( - '../../../../../src/providers/cognito/tokenProvider/tokenProvider', - () => ({ - cognitoUserPoolsTokenProvider: { - setWaitForInflightOAuth: jest.fn(), - }, - }) -); -jest.mock( - '../../../../../src/providers/cognito/utils/oauth/inflightPromise', - () => ({ - addInflightPromise: jest.fn(resolver => { - resolver(); - }), - }) -); - -const mockAssertOAuthConfig = assertOAuthConfig as jest.Mock; -const mockAssertTokenProviderConfig = assertTokenProviderConfig as jest.Mock; -const mockCompleteOAuthFlow = completeOAuthFlow as jest.Mock; -const mockGetRedirectUrl = getRedirectUrl as jest.Mock; -const mockAddInflightPromise = addInflightPromise as jest.Mock; - -describe('attemptCompleteOAuthFlow', () => { - let windowSpy = jest.spyOn(window, 'window', 'get'); - const mockRedirectUrl = 'http://localhost:3000/'; - - beforeAll(() => { - (oAuthStore.loadOAuthInFlight as jest.Mock).mockResolvedValue(false); - mockGetRedirectUrl.mockReturnValue(mockRedirectUrl); - - windowSpy.mockImplementation( - () => - ({ - location: { - href: 'http://localhost:3000/', - origin: 'http://localhost:3000', - pathname: undefined, - }, - }) as any - ); - }); - - afterAll(() => { - windowSpy.mockRestore(); - }); - - afterEach(() => { - mockAssertTokenProviderConfig.mockClear(); - mockAssertOAuthConfig.mockClear(); - mockCompleteOAuthFlow.mockClear(); - - (oAuthStore.setAuthConfig as jest.Mock).mockClear(); - (oAuthStore.loadOAuthInFlight as jest.Mock).mockClear(); - }); - - it('invokes config asserters', async () => { - const cognitoConfig = mockAuthConfigWithOAuth.Auth.Cognito; - await attemptCompleteOAuthFlow(cognitoConfig); - - expect(mockAssertTokenProviderConfig).toHaveBeenCalledWith(cognitoConfig); - expect(mockAssertOAuthConfig).toHaveBeenCalledWith(cognitoConfig); - expect(oAuthStore.setAuthConfig).toHaveBeenCalledWith(cognitoConfig); - }); - - it('does nothing when `await oAuthStore.loadOAuthInFlight()` resolves `false` (there is no inflight oauth process)', async () => { - await attemptCompleteOAuthFlow(mockAuthConfigWithOAuth.Auth.Cognito); - - expect(oAuthStore.loadOAuthInFlight).toHaveBeenCalledTimes(1); - expect(mockCompleteOAuthFlow).not.toHaveBeenCalled(); - }); - - it('sets inflight oauth promise and invokes `completeOAuthFlow` to complete an inflight oauth process', async () => { - (oAuthStore.loadOAuthInFlight as jest.Mock).mockResolvedValueOnce(true); - - await attemptCompleteOAuthFlow(mockAuthConfigWithOAuth.Auth.Cognito); - - expect(mockCompleteOAuthFlow).toHaveBeenCalledWith( - expect.objectContaining({ - currentUrl: 'http://localhost:3000/', - redirectUri: 'http://localhost:3000/', - }) - ); - - expect( - cognitoUserPoolsTokenProvider.setWaitForInflightOAuth - ).toHaveBeenCalledTimes(1); - - const callback = ( - cognitoUserPoolsTokenProvider.setWaitForInflightOAuth as jest.Mock - ).mock.calls[0][0]; - - // the blocking promise should resolve - expect(callback()).resolves.toBeUndefined(); - }); - - test.each([ - ['assertTokenProviderConfig', mockAssertTokenProviderConfig], - ['assertOAuthConfig', mockAssertOAuthConfig], - ['getRedirectUrl', mockGetRedirectUrl], - ])('when `%s` throws it does nothing', async (_, mockFunc) => { - mockFunc.mockImplementationOnce(() => { - throw new Error('some error'); - }); - expect( - attemptCompleteOAuthFlow(mockAuthConfigWithOAuth.Auth.Cognito) - ).resolves.toBeUndefined(); - }); -}); diff --git a/packages/auth/__tests__/providers/cognito/utils/oauth/completeOAuthFlow.test.ts b/packages/auth/__tests__/providers/cognito/utils/oauth/completeOAuthFlow.test.ts deleted file mode 100644 index 1bbe4b3509e..00000000000 --- a/packages/auth/__tests__/providers/cognito/utils/oauth/completeOAuthFlow.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Hub, decodeJWT } from '@aws-amplify/core'; -import { handleFailure } from '../../../../../src/providers/cognito/utils/oauth/handleFailure'; -import { validateState } from '../../../../../src/providers/cognito/utils/oauth/validateState'; -// import { createOAuthError } from '../../../../../src/providers/cognito/utils/oauth/createOAuthError'; -import { resolveAndClearInflightPromises } from '../../../../../src/providers/cognito/utils/oauth/inflightPromise'; -import { oAuthStore } from '../../../../../src/providers/cognito/utils/oauth/oAuthStore'; -import { cacheCognitoTokens } from '../../../../../src/providers/cognito/tokenProvider/cacheTokens'; -import { AuthError } from '../../../../../src/errors/AuthError'; -import { AuthErrorTypes } from '../../../../../src/types/Auth'; -import { OAuthStore } from '../../../../../src/providers/cognito/utils/types'; -import { getCurrentUser } from '../../../../../src/providers/cognito/apis/getCurrentUser'; - -import { completeOAuthFlow } from '../../../../../src/providers/cognito/utils/oauth/completeOAuthFlow'; - -jest.mock('@aws-amplify/core', () => ({ - Hub: { - dispatch: jest.fn(), - }, - decodeJWT: jest.fn(), - ConsoleLogger: jest.fn(), -})); -jest.mock('../../../../../src/providers/cognito/utils/oauth//handleFailure'); -jest.mock('../../../../../src/providers/cognito/utils/oauth/validateState'); -jest.mock('../../../../../src/providers/cognito/utils/oauth/inflightPromise'); -// jest.mock('../../../../../src/providers/cognito/utils/oauth/createOAuthError'); -jest.mock('../../../../../src/providers/cognito/apis/getCurrentUser'); -jest.mock('../../../../../src/providers/cognito/tokenProvider/cacheTokens'); -jest.mock( - '../../../../../src/providers/cognito/utils/oauth/oAuthStore', - () => ({ - oAuthStore: { - setAuthConfig: jest.fn(), - storeOAuthInFlight: jest.fn(), - storeOAuthState: jest.fn(), - storePKCE: jest.fn(), - loadOAuthInFlight: jest.fn(), - loadOAuthSignIn: jest.fn(), - storeOAuthSignIn: jest.fn(), - loadOAuthState: jest.fn(), - loadPKCE: jest.fn(), - clearOAuthData: jest.fn(), - clearOAuthInflightData: jest.fn(), - } as OAuthStore, - }) -); - -const mockHandleFailure = handleFailure as jest.Mock; -const mockValidateState = validateState as jest.Mock; -const mockResolveAndClearInflightPromises = - resolveAndClearInflightPromises as jest.Mock; -const mockCacheCognitoTokens = cacheCognitoTokens as jest.Mock; -const mockHubDispatch = Hub.dispatch as jest.Mock; -const mockDecodeJWT = decodeJWT as jest.Mock; -// const mockCreateOAuthError = createOAuthError as jest.Mock; - -describe('completeOAuthFlow', () => { - let windowSpy = jest.spyOn(window, 'window', 'get'); - const mockFetch = jest.fn(); - const mockReplaceState = jest.fn(); - - beforeAll(() => { - (global as any).fetch = mockFetch; - windowSpy.mockImplementation( - () => - ({ - history: { - replaceState: mockReplaceState, - }, - }) as any - ); - }); - - afterEach(() => { - mockHandleFailure.mockClear(); - mockResolveAndClearInflightPromises.mockClear(); - mockFetch.mockClear(); - mockReplaceState.mockClear(); - mockHubDispatch.mockClear(); - - (oAuthStore.clearOAuthData as jest.Mock).mockClear(); - (oAuthStore.clearOAuthInflightData as jest.Mock).mockClear(); - (oAuthStore.clearOAuthData as jest.Mock).mockClear(); - (oAuthStore.storeOAuthSignIn as jest.Mock).mockClear(); - }); - - it('handles error presented in the redirect url', async () => { - const expectedErrorMessage = 'some error message'; - - expect( - completeOAuthFlow({ - currentUrl: `http://localhost:3000?error=true&error_description=${expectedErrorMessage}`, - userAgentValue: 'UserAgent', - clientId: 'clientId', - redirectUri: 'http://localhost:3000/', - responseType: 'code', - domain: 'localhost:3000', - }) - ).rejects.toThrow(expectedErrorMessage); - }); - - describe('handleCodeFlow', () => { - const expectedState = 'someState123'; - const testInput = { - currentUrl: `http://localhost:3000?code=12345&state=${expectedState}`, - userAgentValue: 'UserAgent', - clientId: 'clientId', - redirectUri: 'http://localhost:3000/', - responseType: 'code', - domain: 'oauth.domain.com', - }; - - it('throws when `code` is not presented in the redirect url', () => { - expect( - completeOAuthFlow({ - ...testInput, - currentUrl: `http://localhost:3000?state=someState123`, - }) - ).rejects.toThrow('The inflight OAuth flow has been cancelled.'); - }); - - it('throws when `state` is not presented in the redirect url', async () => { - expect( - completeOAuthFlow({ - ...testInput, - currentUrl: `http://localhost:3000?code=123`, - }) - ).rejects.toThrow('The inflight OAuth flow has been cancelled.'); - }); - - it('handles error when `validateState` fails', async () => { - const expectedErrorMessage = 'some error'; - mockValidateState.mockImplementationOnce(() => { - throw new AuthError({ - name: AuthErrorTypes.OAuthSignInError, - message: expectedErrorMessage, - }); - }); - - await expect(completeOAuthFlow(testInput)).rejects.toThrow( - expectedErrorMessage - ); - expect(mockValidateState).toHaveBeenCalledWith(expectedState); - }); - - it('exchanges auth token and completes the oauth process', async () => { - const expectedTokens = { - access_token: 'access_token', - id_token: 'id_token', - refresh_token: 'refresh_token', - token_type: 'token_type', - expires_in: 'expires_in', - }; - mockValidateState.mockReturnValueOnce('myState-valid_state'); - (oAuthStore.loadPKCE as jest.Mock).mockResolvedValueOnce('pkce23234a'); - const mockJsonMethod = jest.fn(() => Promise.resolve(expectedTokens)); - mockDecodeJWT.mockReturnValueOnce({ - payload: { - username: 'testuser', - }, - }); - mockFetch.mockResolvedValueOnce({ - json: mockJsonMethod, - }); - - await completeOAuthFlow(testInput); - - expect(mockFetch).toHaveBeenCalledWith( - 'https://oauth.domain.com/oauth2/token', - expect.objectContaining({ - method: 'POST', - body: 'grant_type=authorization_code&code=12345&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&code_verifier=pkce23234a', - }) - ); - expect(mockCacheCognitoTokens).toHaveBeenLastCalledWith({ - username: 'testuser', - AccessToken: expectedTokens.access_token, - IdToken: expectedTokens.id_token, - RefreshToken: expectedTokens.refresh_token, - TokenType: expectedTokens.token_type, - ExpiresIn: expectedTokens.expires_in, - }); - expect(mockReplaceState).toHaveBeenCalledWith( - {}, - '', - testInput.redirectUri - ); - - expect(oAuthStore.clearOAuthData).toHaveBeenCalledTimes(1); - expect(oAuthStore.storeOAuthSignIn).toHaveBeenCalledWith(true, undefined); - - expect(mockHubDispatch).toHaveBeenCalledTimes(3); - expect(mockResolveAndClearInflightPromises).toHaveBeenCalledTimes(1); - }); - - it('throws when `fetch` call resolves error', async () => { - const mockError = { - error: true, - error_message: 'some error', - }; - const mockJsonMethod = jest.fn(() => Promise.resolve(mockError)); - mockFetch.mockResolvedValueOnce({ - json: mockJsonMethod, - }); - - expect(completeOAuthFlow(testInput)).rejects.toThrow( - mockError.error_message - ); - }); - }); - - describe('handleImplicitFlow', () => { - const testInput = { - currentUrl: `http://localhost:3000#access_token=accessToken123`, - userAgentValue: 'UserAgent', - clientId: 'clientId', - redirectUri: 'http://localhost:3000/', - responseType: 'non-code', - domain: 'oauth.domain.com', - }; - - it('throws when access_token is not presented in the redirect url', () => { - expect( - completeOAuthFlow({ - ...testInput, - currentUrl: `http://localhost:3000#`, - }) - ).rejects.toThrow('No access token returned from OAuth flow.'); - }); - - it('handles error when `validateState` fails', async () => { - const expectedErrorMessage = 'some error'; - mockValidateState.mockImplementationOnce(() => { - throw new AuthError({ - name: AuthErrorTypes.OAuthSignInError, - message: expectedErrorMessage, - }); - }); - - await expect(completeOAuthFlow(testInput)).rejects.toThrow( - expectedErrorMessage - ); - }); - - it('completes the inflight oauth flow', async () => { - const expectedAccessToken = 'access_token'; - const expectedIdToken = 'id_token'; - const expectedTokenType = 'token_type'; - const expectedExpiresIn = 'expires_in'; - - mockDecodeJWT.mockReturnValueOnce({ - payload: { - username: 'testuser', - }, - }); - - await completeOAuthFlow({ - ...testInput, - currentUrl: `http://localhost:3000#access_token=${expectedAccessToken}&id_token=${expectedIdToken}&token_type=${expectedTokenType}&expires_in=${expectedExpiresIn}`, - }); - - expect(mockCacheCognitoTokens).toHaveBeenCalledWith({ - username: 'testuser', - AccessToken: expectedAccessToken, - IdToken: expectedIdToken, - TokenType: expectedTokenType, - ExpiresIn: expectedExpiresIn, - }); - - expect(mockReplaceState).toHaveBeenCalledWith( - {}, - '', - testInput.redirectUri - ); - - expect(oAuthStore.clearOAuthData).toHaveBeenCalledTimes(1); - expect(oAuthStore.storeOAuthSignIn).toHaveBeenCalledWith(true, undefined); - - expect(mockHubDispatch).toHaveBeenCalledTimes(2); - expect(mockResolveAndClearInflightPromises).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/packages/auth/__tests__/providers/cognito/utils/oauth/validateState.test.ts b/packages/auth/__tests__/providers/cognito/utils/oauth/validateState.test.ts deleted file mode 100644 index ad0e0eeff4e..00000000000 --- a/packages/auth/__tests__/providers/cognito/utils/oauth/validateState.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - validateState, - flowCancelledMessage, - validationFailedMessage, - validationRecoverySuggestion, -} from '../../../../../src/providers/cognito/utils/oauth/validateState'; -import { oAuthStore } from '../../../../../src/providers/cognito/utils/oauth/oAuthStore'; -import { OAuthStore } from '../../../../../src/providers/cognito/utils/types'; -import { AuthError } from '../../../../../src/errors/AuthError'; -import { AuthErrorTypes } from '../../../../../src/types/Auth'; - -jest.mock( - '../../../../../src/providers/cognito/utils/oauth/oAuthStore', - () => ({ - oAuthStore: { - setAuthConfig: jest.fn(), - storeOAuthInFlight: jest.fn(), - storeOAuthState: jest.fn(), - storePKCE: jest.fn(), - loadOAuthInFlight: jest.fn(), - loadOAuthSignIn: jest.fn(), - storeOAuthSignIn: jest.fn(), - loadOAuthState: jest.fn(), - loadPKCE: jest.fn(), - clearOAuthData: jest.fn(), - clearOAuthInflightData: jest.fn(), - } as OAuthStore, - }) -); - -describe('validateState', () => { - const mockLoadOAuthState = oAuthStore.loadOAuthState as jest.Mock; - const expectedState = 'some_state_123'; - - it('returns validated state', () => { - mockLoadOAuthState.mockResolvedValueOnce(expectedState); - - expect(validateState(expectedState)).resolves.toEqual(expectedState); - }); - - it('throws exception when input state is null but oAuthStore has previous store state', () => { - mockLoadOAuthState.mockResolvedValueOnce(expectedState); - const expectError = new AuthError({ - name: AuthErrorTypes.OAuthSignInError, - message: flowCancelledMessage, - }); - expect(validateState(null)).rejects.toThrow(expectError); - }); - - it('throws exception when the input state is not the same as the state in oAuthStore', () => { - mockLoadOAuthState.mockResolvedValueOnce(expectedState); - const expectError = new AuthError({ - name: AuthErrorTypes.OAuthSignInError, - message: validationFailedMessage, - recoverySuggestion: validationRecoverySuggestion, - }); - expect(validateState('different_state')).rejects.toThrow(expectError); - }); -}); diff --git a/packages/auth/__tests__/providers/cognito/verifyTOTPSetup.test.ts b/packages/auth/__tests__/providers/cognito/verifyTOTPSetup.test.ts index 248fc6c4a14..025d2f63871 100644 --- a/packages/auth/__tests__/providers/cognito/verifyTOTPSetup.test.ts +++ b/packages/auth/__tests__/providers/cognito/verifyTOTPSetup.test.ts @@ -15,10 +15,6 @@ jest.mock('@aws-amplify/core', () => ({ ...(jest.createMockFromModule('@aws-amplify/core') as object), Amplify: { getConfig: jest.fn(() => ({})) }, })); -jest.mock('@aws-amplify/core/internals/utils', () => ({ - ...jest.requireActual('@aws-amplify/core/internals/utils'), - isBrowser: jest.fn(() => false), -})); jest.mock( '../../../src/providers/cognito/utils/clients/CognitoIdentityProvider' ); diff --git a/packages/auth/enable-oauth-listener/package.json b/packages/auth/enable-oauth-listener/package.json deleted file mode 100644 index 302b634a671..00000000000 --- a/packages/auth/enable-oauth-listener/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@aws-amplify/auth/enable-oauth-listener", - "types": "../dist/esm/providers/cognito/utils/oauth/enableOAuthListener.d.ts", - "main": "../dist/cjs/providers/cognito/utils/oauth/enableOAuthListener.js", - "module": "../dist/esm/providers/cognito/utils/oauth/enableOAuthListener.mjs", - "sideEffects": true -} diff --git a/packages/auth/jest.config.js b/packages/auth/jest.config.js index bb70650f9d4..ce6816e489f 100644 --- a/packages/auth/jest.config.js +++ b/packages/auth/jest.config.js @@ -2,10 +2,10 @@ module.exports = { ...require('../../jest.config'), coverageThreshold: { global: { - branches: 69, - functions: 78, - lines: 87, - statements: 86, + branches: 64, + functions: 74, + lines: 83, + statements: 83, }, }, }; diff --git a/packages/auth/package.json b/packages/auth/package.json index 4dfece56f34..399f8947828 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -8,9 +8,7 @@ "react-native": "./src/index.ts", "sideEffects": [ "./dist/cjs/providers/cognito/apis/signInWithRedirect.js", - "./dist/esm/providers/cognito/apis/signInWithRedirect.mjs", - "./dist/cjs/providers/cognito/utils/oauth/enableOAuthListener.js", - "./dist/esm/providers/cognito/utils/oauth/enableOAuthListener.mjs" + "./dist/esm/providers/cognito/apis/signInWithRedirect.mjs" ], "publishConfig": { "access": "public" @@ -65,11 +63,6 @@ "import": "./dist/esm/server.mjs", "require": "./dist/cjs/server.js" }, - "./enable-oauth-listener": { - "types": "./dist/esm/providers/cognito/utils/oauth/enableOAuthListener.d.ts", - "import": "./dist/esm/providers/cognito/utils/oauth/enableOAuthListener.mjs", - "require": "./dist/cjs/providers/cognito/utils/oauth/enableOAuthListener.js" - }, "./package.json": "./package.json" }, "repository": { @@ -87,8 +80,7 @@ "dist/esm", "src", "cognito", - "server", - "enable-oauth-listener" + "server" ], "dependencies": { "tslib": "^2.5.0" diff --git a/packages/auth/src/providers/cognito/apis/signInWithRedirect.ts b/packages/auth/src/providers/cognito/apis/signInWithRedirect.ts index d37bf10d872..13756284ec6 100644 --- a/packages/auth/src/providers/cognito/apis/signInWithRedirect.ts +++ b/packages/auth/src/providers/cognito/apis/signInWithRedirect.ts @@ -1,35 +1,40 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Amplify, OAuthConfig } from '@aws-amplify/core'; +import { Amplify, Hub, defaultStorage, OAuthConfig } from '@aws-amplify/core'; import { AuthAction, + AMPLIFY_SYMBOL, assertOAuthConfig, assertTokenProviderConfig, + isBrowser, urlSafeEncode, + USER_AGENT_HEADER, + urlSafeDecode, + decodeJWT, + AmplifyUrl, } from '@aws-amplify/core/internals/utils'; -import '../utils/oauth/enableOAuthListener'; +import { cacheCognitoTokens } from '../tokenProvider/cacheTokens'; +import { cognitoUserPoolsTokenProvider } from '../tokenProvider'; import { cognitoHostedUIIdentityProviderMap } from '../types/models'; +import { DefaultOAuthStore } from '../utils/signInWithRedirectStore'; +import { AuthError } from '../../../errors/AuthError'; +import { AuthErrorTypes } from '../../../types/Auth'; +import { AuthErrorCodes } from '../../../common/AuthErrorStrings'; +import { authErrorMessages } from '../../../Errors'; import { getAuthUserAgentValue, openAuthSession } from '../../../utils'; import { assertUserNotAuthenticated } from '../utils/signInHelpers'; import { SignInWithRedirectInput } from '../types'; -import { - generateCodeVerifier, - generateState, - getRedirectUrl, - handleFailure, - completeOAuthFlow, - oAuthStore, -} from '../utils/oauth'; -import { AuthError } from '../../../errors/AuthError'; -import { createOAuthError } from '../utils/oauth/createOAuthError'; +import { generateCodeVerifier, generateState } from '../utils/oauth'; +import { getCurrentUser } from './getCurrentUser'; +import { getRedirectUrl } from '../utils/oauth/getRedirectUrl'; /** * Signs in a user with OAuth. Redirects the application to an Identity Provider. * * @param input - The SignInWithRedirectInput object, if empty it will redirect to Cognito HostedUI * - * @throws AuthTokenConfigException - Thrown when the user pool config is invalid. + * @throws AuthTokenConfigException - Thrown when the userpool config is invalid. * @throws OAuthNotConfigureException - Thrown when the oauth config is invalid. */ export async function signInWithRedirect( @@ -38,7 +43,7 @@ export async function signInWithRedirect( const authConfig = Amplify.getConfig().Auth?.Cognito; assertTokenProviderConfig(authConfig); assertOAuthConfig(authConfig); - oAuthStore.setAuthConfig(authConfig); + store.setAuthConfig(authConfig); await assertUserNotAuthenticated(); let provider = 'COGNITO'; // Default @@ -58,7 +63,9 @@ export async function signInWithRedirect( }); } -const oauthSignIn = async ({ +export const store = new DefaultOAuthStore(defaultStorage); + +export async function oauthSignIn({ oauthConfig, provider, clientId, @@ -70,7 +77,7 @@ const oauthSignIn = async ({ clientId: string; customState?: string; preferPrivateSession?: boolean; -}) => { +}) { const { domain, redirectSignIn, responseType, scopes } = oauthConfig; const randomState = generateState(); @@ -83,16 +90,16 @@ const oauthSignIn = async ({ const state = customState ? `${randomState}-${urlSafeEncode(customState)}` : randomState; - const { value, method, toCodeChallenge } = generateCodeVerifier(128); - const redirectUri = getRedirectUrl(oauthConfig.redirectSignIn); - oAuthStore.storeOAuthInFlight(true); - oAuthStore.storeOAuthState(state); - oAuthStore.storePKCE(value); + const redirectUri = getRedirectUrl(oauthConfig.redirectSignIn); + + store.storeOAuthInFlight(true); + store.storeOAuthState(state); + store.storePKCE(value); const queryString = Object.entries({ - redirect_uri: redirectUri, + redirect_uri: redirectUri , response_type: responseType, client_id: clientId, identity_provider: provider, @@ -108,30 +115,415 @@ const oauthSignIn = async ({ // TODO(v6): use URL object instead const oAuthUrl = `https://${domain}/oauth2/authorize?${queryString}`; - - // the following is effective only in react-native as openAuthSession resolves only in react-native const { type, error, url } = (await openAuthSession(oAuthUrl, redirectSignIn, preferPrivateSession)) ?? {}; + // This code will run in RN applications only as calling signInWithRedirect will + // resolve the promise. + if (type === 'success' && url) { + // ensure the code exchange completion resolves the signInWithRedirect + // returned promise in react-native + await handleAuthResponse({ + currentUrl: url, + clientId, + domain, + redirectUri: redirectSignIn[0], + responseType, + userAgentValue: getAuthUserAgentValue(AuthAction.SignInWithRedirect), + preferPrivateSession, + }); + } + // This code will run in RN applications only as calling signInWithRedirect will + // resolve the promise. + if (type === 'error') { + await handleFailure(String(error)); + } +} + +async function handleCodeFlow({ + currentUrl, + userAgentValue, + clientId, + redirectUri, + domain, + preferPrivateSession, +}: { + currentUrl: string; + userAgentValue: string; + clientId: string; + redirectUri: string; + domain: string; + preferPrivateSession?: boolean; +}) { + /* Convert URL into an object with parameters as keys +{ redirect_uri: 'http://localhost:3000/', response_type: 'code', ...} */ + const url = new AmplifyUrl(currentUrl); + let validatedState: string; + try { + validatedState = await validateState(getStateFromURL(url)); + } catch (err) { + invokeAndClearPromise(); + // validateState method will always throw an AuthError when the state is not valid. The if statement is making TS happy. + if (err instanceof AuthError) { + await handleFailure(err.message); + } + return; + } + const code = url.searchParams.get('code'); + + if (!code) { + await store.clearOAuthData(); + invokeAndClearPromise(); + return; + } + + const oAuthTokenEndpoint = 'https://' + domain + '/oauth2/token'; + + // TODO(v6): check hub events + // dispatchAuthEvent( + // 'codeFlow', + // {}, + // `Retrieving tokens from ${oAuthTokenEndpoint}` + // ); + + const codeVerifier = await store.loadPKCE(); + + const oAuthTokenBody = { + grant_type: 'authorization_code', + code, + client_id: clientId, + redirect_uri: redirectUri, + ...(codeVerifier ? { code_verifier: codeVerifier } : {}), + }; + + const body = Object.entries(oAuthTokenBody) + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); + + const { + access_token, + refresh_token, + id_token, + error, + error_message, + token_type, + expires_in, + } = await ( + await fetch(oAuthTokenEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + [USER_AGENT_HEADER]: userAgentValue, + }, + body, + }) + ).json(); + + if (error) { + invokeAndClearPromise(); + await handleFailure(error_message ?? error); + } + + await store.clearOAuthInflightData(); + + const username = + (access_token && decodeJWT(access_token).payload.username) ?? 'username'; + + await cacheCognitoTokens({ + username, + AccessToken: access_token, + IdToken: id_token, + RefreshToken: refresh_token, + TokenType: token_type, + ExpiresIn: expires_in, + }); + + return completeFlow({ + redirectUri, + state: validatedState, + preferPrivateSession, + }); +} + +async function handleImplicitFlow({ + currentUrl, + redirectUri, + preferPrivateSession, +}: { + currentUrl: string; + redirectUri: string; + preferPrivateSession?: boolean; +}) { + // hash is `null` if `#` doesn't exist on URL + + const url = new AmplifyUrl(currentUrl); + + const { + id_token, + access_token, + state, + token_type, + expires_in, + error_description, + error, + } = (url.hash ?? '#') + .substring(1) // Remove # from returned code + .split('&') + .map(pairings => pairings.split('=')) + .reduce((accum, [k, v]) => ({ ...accum, [k]: v }), { + id_token: undefined, + access_token: undefined, + state: undefined, + token_type: undefined, + expires_in: undefined, + error_description: undefined, + error: undefined, + }); + + if (error) { + invokeAndClearPromise(); + await handleFailure(error_description ?? error); + } + + if (!access_token) { + await store.clearOAuthData(); + invokeAndClearPromise(); + return; + } + let validatedState; + try { + validatedState = await validateState(state); + } catch (error) { + invokeAndClearPromise(); + // validateState method will always throw an AuthError when the state is not valid. The if statement is making TS happy. + if (error instanceof AuthError) { + await handleFailure(error.message); + } + return; + } + + const username = + (access_token && decodeJWT(access_token).payload.username) ?? 'username'; + + await cacheCognitoTokens({ + username, + AccessToken: access_token, + IdToken: id_token, + TokenType: token_type, + ExpiresIn: expires_in, + }); + return completeFlow({ + redirectUri, + state: validatedState, + preferPrivateSession, + }); +} + +async function completeFlow({ + redirectUri, + state, + preferPrivateSession, +}: { + preferPrivateSession?: boolean; + redirectUri: string; + state: string; +}) { + await store.clearOAuthData(); + await store.storeOAuthSignIn(true, preferPrivateSession); + if (isCustomState(state)) { + Hub.dispatch( + 'auth', + { + event: 'customOAuthState', + data: urlSafeDecode(getCustomState(state)), + }, + 'Auth', + AMPLIFY_SYMBOL + ); + } + Hub.dispatch('auth', { event: 'signInWithRedirect' }, 'Auth', AMPLIFY_SYMBOL); + Hub.dispatch( + 'auth', + { event: 'signedIn', data: await getCurrentUser() }, + 'Auth', + AMPLIFY_SYMBOL + ); + clearHistory(redirectUri); + invokeAndClearPromise(); +} + +async function handleAuthResponse({ + currentUrl, + userAgentValue, + clientId, + redirectUri, + responseType, + domain, + preferPrivateSession, +}: { + currentUrl: string; + userAgentValue: string; + clientId: string; + redirectUri: string; + responseType: string; + domain: string; + preferPrivateSession?: boolean; +}) { try { - if (type === 'error') { - throw createOAuthError(String(error)); + const urlParams = new AmplifyUrl(currentUrl); + const error = urlParams.searchParams.get('error'); + const errorMessage = urlParams.searchParams.get('error_description'); + + if (error) { + await handleFailure(errorMessage); } - if (type === 'success' && url) { - await completeOAuthFlow({ - currentUrl: url, + + if (responseType === 'code') { + return await handleCodeFlow({ + currentUrl, + userAgentValue, clientId, + redirectUri, domain, + preferPrivateSession, + }); + } else { + return await handleImplicitFlow({ + currentUrl, redirectUri, - responseType, - userAgentValue: getAuthUserAgentValue(AuthAction.SignInWithRedirect), preferPrivateSession, }); } - } catch (error) { - await handleFailure(error); - // rethrow the error so it can be caught by `await signInWithRedirect()` in react-native - throw error; + } catch (e) { + throw e; + } +} + +function getStateFromURL(urlParams: URL): string | null { + return urlParams.searchParams.get('state'); +} + +async function validateState(state?: string | null): Promise { + const savedState = await store.loadOAuthState(); + + // This is because savedState only exists if the flow was initiated by Amplify + const validatedState = state === savedState ? savedState : undefined; + if (!validatedState) { + throw new AuthError({ + name: AuthErrorTypes.OAuthSignInError, + message: 'An error occurred while validating the state', + recoverySuggestion: 'Try to initiate an OAuth flow from Amplify', + }); + } + return validatedState; +} + +async function handleFailure(errorMessage: string | null) { + const error = new AuthError({ + message: errorMessage ?? 'An error has occurred during the oauth proccess', + name: AuthErrorCodes.OAuthSignInError, + recoverySuggestion: authErrorMessages.oauthSignInError.log, + }); + await store.clearOAuthInflightData(); + Hub.dispatch( + 'auth', + { event: 'signInWithRedirect_failure', data: { error } }, + 'Auth', + AMPLIFY_SYMBOL + ); + throw new AuthError({ + message: errorMessage ?? '', + name: AuthErrorCodes.OAuthSignInError, + recoverySuggestion: authErrorMessages.oauthSignInError.log, + }); +} + +export async function parseRedirectURL() { + const authConfig = Amplify.getConfig().Auth?.Cognito; + try { + assertTokenProviderConfig(authConfig); + store.setAuthConfig(authConfig); + } catch (_err) { + // Token provider not configure nothing to do + return; + } + + // No OAuth inflight doesnt need to parse the url + if (!(await store.loadOAuthInFlight())) { + return; + } + try { + assertOAuthConfig(authConfig); + } catch (err) { + // TODO(v6): this should warn you have signInWithRedirect but is not configured + return; + } + + try { + const currentUrl = window.location.href; + const { loginWith, userPoolClientId } = authConfig; + const { domain, redirectSignIn, responseType } = loginWith.oauth; + + await handleAuthResponse({ + currentUrl, + clientId: userPoolClientId, + domain, + redirectUri: redirectSignIn[0], + responseType, + userAgentValue: getAuthUserAgentValue(AuthAction.SignInWithRedirect), + }); + } catch (err) { + // is ok if there is not OAuthConfig + } +} + +function urlListener() { + // Listen configure to parse url + parseRedirectURL(); + Hub.listen('core', capsule => { + if (capsule.payload.event === 'configure') { + parseRedirectURL(); + } + }); +} + +isBrowser() && urlListener(); + +// This has a reference for listeners that requires to be notified, TokenOrchestrator use this for load tokens +let inflightPromiseResolvers: ((value: void | PromiseLike) => void)[] = + []; + +const invokeAndClearPromise = () => { + for (const promiseResolver of inflightPromiseResolvers) { + promiseResolver(); } + inflightPromiseResolvers = []; }; + +isBrowser() && + cognitoUserPoolsTokenProvider.setWaitForInflightOAuth( + () => + new Promise(async (res, _rej) => { + if (!(await store.loadOAuthInFlight())) { + res(); + } else { + inflightPromiseResolvers.push(res); + } + return; + }) + ); + +function clearHistory(redirectUri: string) { + if (typeof window !== 'undefined' && typeof window.history !== 'undefined') { + window.history.replaceState({}, '', redirectUri); + } +} + +function isCustomState(state: string): Boolean { + return /-/.test(state); +} + +function getCustomState(state: string): string { + return state.split('-').splice(1).join('-'); +} diff --git a/packages/auth/src/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.ts b/packages/auth/src/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.ts deleted file mode 100644 index bd73cffc302..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/attemptCompleteOAuthFlow.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { AuthConfig } from '@aws-amplify/core'; -import { - AuthAction, - assertOAuthConfig, - assertTokenProviderConfig, -} from '@aws-amplify/core/internals/utils'; -import { oAuthStore } from './oAuthStore'; -import { completeOAuthFlow } from './completeOAuthFlow'; -import { getAuthUserAgentValue } from '../../../../utils'; -import { getRedirectUrl } from './getRedirectUrl'; -import { handleFailure } from './handleFailure'; -import { cognitoUserPoolsTokenProvider } from '../../tokenProvider'; -import { addInflightPromise } from './inflightPromise'; - -export const attemptCompleteOAuthFlow = async ( - authConfig: AuthConfig['Cognito'] -): Promise => { - try { - assertTokenProviderConfig(authConfig); - assertOAuthConfig(authConfig); - oAuthStore.setAuthConfig(authConfig); - } catch (_) { - // no-op - // This should not happen as Amplify singleton checks the oauth config key - // unless the oauth config object doesn't contain required properties - return; - } - - // No inflight OAuth - if (!(await oAuthStore.loadOAuthInFlight())) { - return; - } - - // when there is valid oauth config and there is an inflight oauth flow, try - // to block async calls that require fetching tokens before the oauth flow completes - // e.g. getCurrentUser, fetchAuthSession etc. - cognitoUserPoolsTokenProvider.setWaitForInflightOAuth( - () => - new Promise(async (res, _rej) => { - addInflightPromise(res); - }) - ); - - try { - const currentUrl = window.location.href; - const { loginWith, userPoolClientId } = authConfig; - const { domain, redirectSignIn, responseType } = loginWith.oauth; - const redirectUri = getRedirectUrl(redirectSignIn); - - await completeOAuthFlow({ - currentUrl, - clientId: userPoolClientId, - domain, - redirectUri, - responseType, - userAgentValue: getAuthUserAgentValue(AuthAction.SignInWithRedirect), - }); - } catch (err) { - await handleFailure(err); - } -}; diff --git a/packages/auth/src/providers/cognito/utils/oauth/completeOAuthFlow.ts b/packages/auth/src/providers/cognito/utils/oauth/completeOAuthFlow.ts deleted file mode 100644 index 4002ffbec5f..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/completeOAuthFlow.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - AMPLIFY_SYMBOL, - AmplifyUrl, - USER_AGENT_HEADER, - urlSafeDecode, -} from '@aws-amplify/core/internals/utils'; -import { oAuthStore } from './oAuthStore'; -import { Hub, decodeJWT } from '@aws-amplify/core'; -import { validateState } from './validateState'; -import { resolveAndClearInflightPromises } from './inflightPromise'; -import { cacheCognitoTokens } from '../../tokenProvider/cacheTokens'; -import { getCurrentUser } from '../../apis/getCurrentUser'; -import { createOAuthError } from './createOAuthError'; - -export const completeOAuthFlow = async ({ - currentUrl, - userAgentValue, - clientId, - redirectUri, - responseType, - domain, - preferPrivateSession, -}: { - currentUrl: string; - userAgentValue: string; - clientId: string; - redirectUri: string; - responseType: string; - domain: string; - preferPrivateSession?: boolean; -}): Promise => { - const urlParams = new AmplifyUrl(currentUrl); - const error = urlParams.searchParams.get('error'); - const errorMessage = urlParams.searchParams.get('error_description'); - - if (error) { - throw createOAuthError(errorMessage ?? error); - } - - if (responseType === 'code') { - return handleCodeFlow({ - currentUrl, - userAgentValue, - clientId, - redirectUri, - domain, - preferPrivateSession, - }); - } - - return handleImplicitFlow({ - currentUrl, - redirectUri, - preferPrivateSession, - }); -}; - -const handleCodeFlow = async ({ - currentUrl, - userAgentValue, - clientId, - redirectUri, - domain, - preferPrivateSession, -}: { - currentUrl: string; - userAgentValue: string; - clientId: string; - redirectUri: string; - domain: string; - preferPrivateSession?: boolean; -}) => { - /* Convert URL into an object with parameters as keys -{ redirect_uri: 'http://localhost:3000/', response_type: 'code', ...} */ - const url = new AmplifyUrl(currentUrl); - const code = url.searchParams.get('code'); - const state = url.searchParams.get('state'); - - // if `code` or `state` is not presented in the redirect url, most likely - // that the end user cancelled the inflight oauth flow by: - // 1. clicking the back button of browser - // 2. closing the provider hosted UI page and coming back to the app - if (!code || !state) { - throw createOAuthError('The inflight OAuth flow has been cancelled.'); - } - - // may throw error is being caught in attemptCompleteOAuthFlow.ts - const validatedState = await validateState(state); - - const oAuthTokenEndpoint = 'https://' + domain + '/oauth2/token'; - - // TODO(v6): check hub events - // dispatchAuthEvent( - // 'codeFlow', - // {}, - // `Retrieving tokens from ${oAuthTokenEndpoint}` - // ); - - const codeVerifier = await oAuthStore.loadPKCE(); - const oAuthTokenBody = { - grant_type: 'authorization_code', - code, - client_id: clientId, - redirect_uri: redirectUri, - ...(codeVerifier ? { code_verifier: codeVerifier } : {}), - }; - - const body = Object.entries(oAuthTokenBody) - .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) - .join('&'); - const { - access_token, - refresh_token, - id_token, - error, - error_message, - token_type, - expires_in, - } = await ( - await fetch(oAuthTokenEndpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - [USER_AGENT_HEADER]: userAgentValue, - }, - body, - }) - ).json(); - - if (error) { - // error is being caught in attemptCompleteOAuthFlow.ts - throw createOAuthError(error_message ?? error); - } - - const username = - (access_token && decodeJWT(access_token).payload.username) ?? 'username'; - - await cacheCognitoTokens({ - username, - AccessToken: access_token, - IdToken: id_token, - RefreshToken: refresh_token, - TokenType: token_type, - ExpiresIn: expires_in, - }); - - return completeFlow({ - redirectUri, - state: validatedState, - preferPrivateSession, - }); -}; - -const handleImplicitFlow = async ({ - currentUrl, - redirectUri, - preferPrivateSession, -}: { - currentUrl: string; - redirectUri: string; - preferPrivateSession?: boolean; -}) => { - // hash is `null` if `#` doesn't exist on URL - const url = new AmplifyUrl(currentUrl); - - const { - id_token, - access_token, - state, - token_type, - expires_in, - // the following have been handled at line 40 - // error_description, - // error, - } = (url.hash ?? '#') - .substring(1) // Remove # from returned code - .split('&') - .map(pairings => pairings.split('=')) - .reduce((accum, [k, v]) => ({ ...accum, [k]: v }), { - id_token: undefined, - access_token: undefined, - state: undefined, - token_type: undefined, - expires_in: undefined, - error_description: undefined, - error: undefined, - }); - - if (!access_token) { - // error is being caught in attemptCompleteOAuthFlow.ts - throw createOAuthError('No access token returned from OAuth flow.'); - } - - const validatedState = await validateState(state); - const username = - (access_token && decodeJWT(access_token).payload.username) ?? 'username'; - - await cacheCognitoTokens({ - username, - AccessToken: access_token, - IdToken: id_token, - TokenType: token_type, - ExpiresIn: expires_in, - }); - - return completeFlow({ - redirectUri, - state: validatedState, - preferPrivateSession, - }); -}; - -const completeFlow = async ({ - redirectUri, - state, - preferPrivateSession, -}: { - preferPrivateSession?: boolean; - redirectUri: string; - state: string; -}) => { - await oAuthStore.clearOAuthData(); - await oAuthStore.storeOAuthSignIn(true, preferPrivateSession); - if (isCustomState(state)) { - Hub.dispatch( - 'auth', - { - event: 'customOAuthState', - data: urlSafeDecode(getCustomState(state)), - }, - 'Auth', - AMPLIFY_SYMBOL - ); - } - Hub.dispatch('auth', { event: 'signInWithRedirect' }, 'Auth', AMPLIFY_SYMBOL); - Hub.dispatch( - 'auth', - { event: 'signedIn', data: await getCurrentUser() }, - 'Auth', - AMPLIFY_SYMBOL - ); - clearHistory(redirectUri); - resolveAndClearInflightPromises(); -}; - -const isCustomState = (state: string): Boolean => { - return /-/.test(state); -}; - -const getCustomState = (state: string): string => { - return state.split('-').splice(1).join('-'); -}; - -const clearHistory = (redirectUri: string) => { - if (typeof window !== 'undefined' && typeof window.history !== 'undefined') { - window.history.replaceState({}, '', redirectUri); - } -}; diff --git a/packages/auth/src/providers/cognito/utils/oauth/createOAuthError.ts b/packages/auth/src/providers/cognito/utils/oauth/createOAuthError.ts deleted file mode 100644 index bc0105e9db9..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/createOAuthError.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { authErrorMessages } from '../../../../Errors'; -import { AuthErrorCodes } from '../../../../common/AuthErrorStrings'; -import { AuthError } from '../../../../errors/AuthError'; - -export const createOAuthError = ( - message: string, - recoverySuggestion?: string -) => - new AuthError({ - message: message ?? 'An error has occurred during the oauth process.', - name: AuthErrorCodes.OAuthSignInError, - recoverySuggestion: - recoverySuggestion ?? authErrorMessages.oauthSignInError.log, - }); diff --git a/packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.native.ts b/packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.native.ts deleted file mode 100644 index 80b2d9ccc25..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.native.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// no-op for react-native diff --git a/packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.ts b/packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.ts deleted file mode 100644 index 0938781b451..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/enableOAuthListener.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Amplify } from '@aws-amplify/core'; -import { - ADD_OAUTH_LISTENER, - isBrowser, -} from '@aws-amplify/core/internals/utils'; -import { cognitoUserPoolsTokenProvider } from '../../tokenProvider'; -import { oAuthStore } from './oAuthStore'; -import { addInflightPromise } from './inflightPromise'; -import { attemptCompleteOAuthFlow } from './attemptCompleteOAuthFlow'; - -// attach the side effect for handling the completion of an inflight oauth flow -// this side effect works only on Web -isBrowser() && - (() => { - // add the listener to the singleton for triggering - Amplify[ADD_OAUTH_LISTENER](attemptCompleteOAuthFlow); - })(); - -// required to present for module loaders -export {}; diff --git a/packages/auth/src/providers/cognito/utils/oauth/handleFailure.ts b/packages/auth/src/providers/cognito/utils/oauth/handleFailure.ts deleted file mode 100644 index e7914594b23..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/handleFailure.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Hub } from '@aws-amplify/core'; -import { AMPLIFY_SYMBOL } from '@aws-amplify/core/internals/utils'; - -import { AuthError } from '../../../../errors/AuthError'; -import { oAuthStore } from './oAuthStore'; -import { resolveAndClearInflightPromises } from './inflightPromise'; - -export const handleFailure = async ( - error: AuthError | unknown -): Promise => { - resolveAndClearInflightPromises(); - await oAuthStore.clearOAuthInflightData(); - Hub.dispatch( - 'auth', - { event: 'signInWithRedirect_failure', data: { error } }, - 'Auth', - AMPLIFY_SYMBOL - ); -}; diff --git a/packages/auth/src/providers/cognito/utils/oauth/index.ts b/packages/auth/src/providers/cognito/utils/oauth/index.ts index 93488e44c3a..519122a5d3e 100644 --- a/packages/auth/src/providers/cognito/utils/oauth/index.ts +++ b/packages/auth/src/providers/cognito/utils/oauth/index.ts @@ -4,7 +4,3 @@ export { generateCodeVerifier } from './generateCodeVerifier'; export { generateState } from './generateState'; export { handleOAuthSignOut } from './handleOAuthSignOut'; -export { getRedirectUrl } from './getRedirectUrl'; -export { handleFailure } from './handleFailure'; -export { completeOAuthFlow } from './completeOAuthFlow'; -export { oAuthStore } from './oAuthStore'; diff --git a/packages/auth/src/providers/cognito/utils/oauth/inflightPromise.ts b/packages/auth/src/providers/cognito/utils/oauth/inflightPromise.ts deleted file mode 100644 index 3a8c5e89ebf..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/inflightPromise.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -const inflightPromises: ((value: void | PromiseLike) => void)[] = []; - -export const addInflightPromise = (resolver: () => void) => { - inflightPromises.push(resolver); -}; - -export const resolveAndClearInflightPromises = () => { - while (inflightPromises.length) { - inflightPromises.pop()?.(); - } -}; diff --git a/packages/auth/src/providers/cognito/utils/oauth/oAuthStore.ts b/packages/auth/src/providers/cognito/utils/oauth/oAuthStore.ts deleted file mode 100644 index 33e992a5de9..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/oAuthStore.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { defaultStorage } from '@aws-amplify/core'; -import { DefaultOAuthStore } from '../signInWithRedirectStore'; - -export const oAuthStore = new DefaultOAuthStore(defaultStorage); diff --git a/packages/auth/src/providers/cognito/utils/oauth/validateState.ts b/packages/auth/src/providers/cognito/utils/oauth/validateState.ts deleted file mode 100644 index 8f19ca2db80..00000000000 --- a/packages/auth/src/providers/cognito/utils/oauth/validateState.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { AuthError } from '../../../../errors/AuthError'; -import { AuthErrorTypes } from '../../../../types/Auth'; -import { oAuthStore } from './oAuthStore'; - -export const flowCancelledMessage = '`signInWithRedirect` has been canceled.'; -export const validationFailedMessage = - 'An error occurred while validating the state.'; -export const validationRecoverySuggestion = - 'Try to initiate an OAuth flow from Amplify'; - -export const validateState = async (state?: string | null): Promise => { - const savedState = await oAuthStore.loadOAuthState(); - - // This is because savedState only exists if the flow was initiated by Amplify - const validatedState = state === savedState ? savedState : undefined; - if (!validatedState) { - throw new AuthError({ - name: AuthErrorTypes.OAuthSignInError, - message: state === null ? flowCancelledMessage : validationFailedMessage, - recoverySuggestion: - state === null ? undefined : validationRecoverySuggestion, - }); - } - return validatedState; -}; diff --git a/packages/aws-amplify/auth/enable-oauth-listener/package.json b/packages/aws-amplify/auth/enable-oauth-listener/package.json deleted file mode 100644 index a2aef03629e..00000000000 --- a/packages/aws-amplify/auth/enable-oauth-listener/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "aws-amplify/auth/enable-oauth-listener", - "main": "../../dist/cjs/auth/enableOAuthListener.js", - "browser": "../../dist/esm/auth/enableOAuthListener.mjs", - "module": "../../dist/esm/auth/enableOAuthListener.mjs", - "typings": "../../dist/esm/auth/enableOAuthListener.d.ts", - "sideEffects": true -} diff --git a/packages/aws-amplify/package.json b/packages/aws-amplify/package.json index 8ba9c196002..2c54495e542 100644 --- a/packages/aws-amplify/package.json +++ b/packages/aws-amplify/package.json @@ -69,11 +69,6 @@ "import": "./dist/esm/auth/server.mjs", "require": "./dist/cjs/auth/server.js" }, - "./auth/enable-oauth-listener": { - "types": "./dist/esm/auth/enableOAuthListener.d.ts", - "import": "./dist/esm/auth/enableOAuthListener.mjs", - "require": "./dist/cjs/auth/enableOAuthListener.js" - }, "./analytics": { "types": "./dist/esm/analytics/index.d.ts", "import": "./dist/esm/analytics/index.mjs", @@ -183,9 +178,6 @@ "auth/server": [ "./dist/esm/auth/server.d.ts" ], - "auth/enable-oauth-listener": [ - "./dist/esm/auth/enableOAuthListener.ts.d.ts" - ], "analytics": [ "./dist/esm/analytics/index.d.ts" ], @@ -230,7 +222,7 @@ ] } }, - "sideEffects": ["./dist/esm/auth/enableOAuthListener.mjs", "./dist/cjs/auth/enableOAuthListener.js"], + "sideEffects": false, "scripts": { "test": "npm run lint && jest -w 1 --coverage --logHeapUsage", "test:size": "size-limit", @@ -303,13 +295,13 @@ "name": "[Analytics] record (Kinesis Firehose)", "path": "./dist/esm/analytics/kinesis-firehose/index.mjs", "import": "{ record }", - "limit": "41.50 kB" + "limit": "41.40 kB" }, { "name": "[Analytics] record (Personalize)", "path": "./dist/esm/analytics/personalize/index.mjs", "import": "{ record }", - "limit": "45.50 kB" + "limit": "45.12 kB" }, { "name": "[Analytics] identifyUser (Pinpoint)", @@ -333,7 +325,7 @@ "name": "[API] generateClient (AppSync)", "path": "./dist/esm/api/index.mjs", "import": "{ generateClient }", - "limit": "36.10 kB" + "limit": "36 kB" }, { "name": "[API] REST API handlers", @@ -351,13 +343,13 @@ "name": "[Auth] resetPassword (Cognito)", "path": "./dist/esm/auth/index.mjs", "import": "{ resetPassword }", - "limit": "9.00 kB" + "limit": "8.91 kB" }, { "name": "[Auth] confirmResetPassword (Cognito)", "path": "./dist/esm/auth/index.mjs", "import": "{ confirmResetPassword }", - "limit": "9.00 kB" + "limit": "8.86 kB" }, { "name": "[Auth] signIn (Cognito)", @@ -369,7 +361,7 @@ "name": "[Auth] resendSignUpCode (Cognito)", "path": "./dist/esm/auth/index.mjs", "import": "{ resendSignUpCode }", - "limit": "9.00 kB" + "limit": "8.9 kB" }, { "name": "[Auth] confirmSignUp (Cognito)", @@ -423,13 +415,13 @@ "name": "[Auth] getCurrentUser (Cognito)", "path": "./dist/esm/auth/index.mjs", "import": "{ getCurrentUser }", - "limit": "4.20 kB" + "limit": "4.09 kB" }, { "name": "[Auth] confirmUserAttribute (Cognito)", "path": "./dist/esm/auth/index.mjs", "import": "{ confirmUserAttribute }", - "limit": "9.00 kB" + "limit": "8.99 kB" }, { "name": "[Auth] signInWithRedirect (Cognito)", @@ -447,7 +439,7 @@ "name": "[Auth] Basic Auth Flow (Cognito)", "path": "./dist/esm/auth/index.mjs", "import": "{ signIn, signOut, fetchAuthSession, confirmSignIn }", - "limit": "28.00 kB" + "limit": "27.70 kB" }, { "name": "[Auth] OAuth Auth Flow (Cognito)", @@ -459,43 +451,43 @@ "name": "[Storage] copy (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ copy }", - "limit": "13.50 kB" + "limit": "13.20 kB" }, { "name": "[Storage] downloadData (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ downloadData }", - "limit": "14.00 kB" + "limit": "13.90 kB" }, { "name": "[Storage] getProperties (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ getProperties }", - "limit": "13.50 kB" + "limit": "13.20 kB" }, { "name": "[Storage] getUrl (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ getUrl }", - "limit": "14.50 kB" + "limit": "14.30 kB" }, { "name": "[Storage] list (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ list }", - "limit": "14.00 kB" + "limit": "13.60 kB" }, { "name": "[Storage] remove (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ remove }", - "limit": "13.50 kB" + "limit": "13.00 kB" }, { "name": "[Storage] uploadData (S3)", "path": "./dist/esm/storage/index.mjs", "import": "{ uploadData }", - "limit": "18.50 kB" + "limit": "18.30 kB" } ] } diff --git a/packages/aws-amplify/src/auth/enableOAuthListener.ts b/packages/aws-amplify/src/auth/enableOAuthListener.ts deleted file mode 100644 index 911b3fc5092..00000000000 --- a/packages/aws-amplify/src/auth/enableOAuthListener.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import '@aws-amplify/auth/enable-oauth-listener'; diff --git a/packages/core/__tests__/HubClass.test.ts b/packages/core/__tests__/HubClass.test.ts index 1479dcb53b1..a227fb832c2 100644 --- a/packages/core/__tests__/HubClass.test.ts +++ b/packages/core/__tests__/HubClass.test.ts @@ -1,6 +1,6 @@ Symbol = undefined as any; // this should be undefined before loading Hub -import { Hub } from '../src/Hub'; -import { ConsoleLogger } from '../src/Logger'; +import { Hub } from '../src'; +import { ConsoleLogger } from '../src'; describe('Symbol undefined before load Hub', () => { test('Symbol not supported', () => { diff --git a/packages/core/src/libraryUtils.ts b/packages/core/src/libraryUtils.ts index 58b6e879146..1654bd7ced0 100644 --- a/packages/core/src/libraryUtils.ts +++ b/packages/core/src/libraryUtils.ts @@ -20,7 +20,6 @@ export { } from './utils'; export { parseAWSExports } from './parseAWSExports'; export { LegacyConfig } from './singleton/types'; -export { ADD_OAUTH_LISTENER } from './singleton/constants'; export { amplifyUuid } from './utils/amplifyUuid'; export { AmplifyUrl, AmplifyUrlSearchParams } from './utils/amplifyUrl'; diff --git a/packages/core/src/singleton/Amplify.ts b/packages/core/src/singleton/Amplify.ts index 1eaaae8cdd5..3040c95c8b6 100644 --- a/packages/core/src/singleton/Amplify.ts +++ b/packages/core/src/singleton/Amplify.ts @@ -2,20 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { AuthClass } from './Auth'; import { Hub, AMPLIFY_SYMBOL } from '../Hub'; -import { - AuthConfig, - LegacyConfig, - LibraryOptions, - ResourcesConfig, -} from './types'; +import { LegacyConfig, LibraryOptions, ResourcesConfig } from './types'; import { parseAWSExports } from '../parseAWSExports'; import { deepFreeze } from '../utils'; -import { ADD_OAUTH_LISTENER } from './constants'; export class AmplifyClass { - private oAuthListener: - | ((authConfig: AuthConfig['Cognito']) => void) - | undefined = undefined; resourcesConfig: ResourcesConfig; libraryOptions: LibraryOptions; @@ -77,8 +68,6 @@ export class AmplifyClass { 'Configure', AMPLIFY_SYMBOL ); - - this.notifyOAuthListener(); } /** @@ -89,30 +78,6 @@ export class AmplifyClass { getConfig(): Readonly { return this.resourcesConfig; } - - /** @internal */ - [ADD_OAUTH_LISTENER](listener: (authConfig: AuthConfig['Cognito']) => void) { - if (this.resourcesConfig.Auth?.Cognito.loginWith?.oauth) { - // when Amplify has been configured with a valid OAuth config while adding the listener, run it directly - listener(this.resourcesConfig.Auth?.Cognito); - } else { - // otherwise register the listener and run it later when Amplify gets configured with a valid oauth config - this.oAuthListener = listener; - } - } - - private notifyOAuthListener() { - if ( - !this.resourcesConfig.Auth?.Cognito.loginWith?.oauth || - !this.oAuthListener - ) { - return; - } - - this.oAuthListener(this.resourcesConfig.Auth?.Cognito); - // the listener should only be notified once with a valid oauth config - this.oAuthListener = undefined; - } } /** diff --git a/packages/core/src/singleton/Auth/utils/index.ts b/packages/core/src/singleton/Auth/utils/index.ts index 4fb3880e9d1..43301206b44 100644 --- a/packages/core/src/singleton/Auth/utils/index.ts +++ b/packages/core/src/singleton/Auth/utils/index.ts @@ -6,6 +6,7 @@ import { AuthConfig, JWT, AuthUserPoolAndIdentityPoolConfig, + CognitoUserPoolWithOAuthConfig, CognitoUserPoolConfig, CognitoUserPoolAndIdentityPoolConfig, CognitoIdentityPoolConfig, diff --git a/packages/core/src/singleton/constants.ts b/packages/core/src/singleton/constants.ts deleted file mode 100644 index 714a7971edb..00000000000 --- a/packages/core/src/singleton/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -export const ADD_OAUTH_LISTENER = Symbol('oauth-listener');