From 9701928736c6e6773a251d34ff5a2f3e1cbab5ce Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:29:59 -0500 Subject: [PATCH 01/16] fix: setup integ test run for user agent rollback --- .github/workflows/push-integ-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-integ-test.yml b/.github/workflows/push-integ-test.yml index a7a09f7a539..337e08e69de 100644 --- a/.github/workflows/push-integ-test.yml +++ b/.github/workflows/push-integ-test.yml @@ -8,7 +8,7 @@ concurrency: on: push: branches: - - feat/example-integ-test-branch/main + - feat/rollback-user-agent/main jobs: e2e: From bb02130627ad10f17b3ae3ed5af190b8ea014e95 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:31:37 -0500 Subject: [PATCH 02/16] Revert "fix: avoid federated signin redirect loop (#11847)" This reverts commit 47d38f44470aa0d25973282335c49edb920d7cab. --- packages/auth/src/internals/InternalAuth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth/src/internals/InternalAuth.ts b/packages/auth/src/internals/InternalAuth.ts index 7cde958446e..a54c3e1f13f 100644 --- a/packages/auth/src/internals/InternalAuth.ts +++ b/packages/auth/src/internals/InternalAuth.ts @@ -261,7 +261,7 @@ export class InternalAuthClass { // See https://github.com/aws-amplify/amplify-js/issues/4388 const usedResponseUrls = {}; // Only register urlListener once - if (this.getModuleName() === 'Auth') { + if (this.getModuleName() === 'InternalAuth') { urlListener(({ url }) => { if (usedResponseUrls[url]) { return; From d08ce50fe423275af6f8bf4f1e6a98bc18708b5b Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:32:00 -0500 Subject: [PATCH 03/16] Revert "fix: adds InternalAuth to requiredModules for SSR (#11837)" This reverts commit 471cf2a8a066c24d638dc3c0d4e5782f462c587d. --- packages/aws-amplify/src/withSSRContext.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/aws-amplify/src/withSSRContext.ts b/packages/aws-amplify/src/withSSRContext.ts index 40ff4c8df47..d9d52f4cd09 100644 --- a/packages/aws-amplify/src/withSSRContext.ts +++ b/packages/aws-amplify/src/withSSRContext.ts @@ -11,10 +11,8 @@ import { DataStore } from '@aws-amplify/datastore'; import { Amplify } from './index'; const requiredModules = [ - // Credentials cannot function without Auth + // API cannot function without Auth Auth, - // API cannot function without InternalAuth - InternalAuth, // Auth cannot function without Credentials Credentials, ]; @@ -31,8 +29,8 @@ export function withSSRContext(context: Context = {}) { const { modules = defaultModules, req } = context; if (modules.includes(DataStore)) { modules.push(InternalAPI); + modules.push(InternalAuth); } - const previousConfig = Amplify.configure(); const amplify = new AmplifyClass(); const storage = new UniversalStorage({ req }); From 166c6e142702751cb1462a06cccbebe6286fc7b4 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:32:13 -0500 Subject: [PATCH 04/16] Revert "feat: capture DataStore and API metrics on cross-category auth calls (#11824)" This reverts commit b6987fc64ce7617a3a4cba78ae23a8c6936808d1. --- .../api-graphql/__tests__/GraphQLAPI-test.ts | 155 +----------------- .../src/internals/InternalGraphQLAPI.ts | 28 +--- packages/api/src/internals/InternalAPI.ts | 6 +- .../__tests__/withSSRContext-test.ts | 8 +- packages/aws-amplify/src/withSSRContext.ts | 2 - .../__tests__/authStrategies.test.ts | 39 ++--- .../authModeStrategies/multiAuthStrategy.ts | 12 +- packages/datastore/src/datastore/datastore.ts | 8 +- packages/datastore/src/sync/constants.ts | 17 -- .../datastore/src/sync/processors/mutation.ts | 10 +- .../src/sync/processors/subscription.ts | 28 ++-- .../datastore/src/sync/processors/sync.ts | 10 +- packages/datastore/src/sync/utils.ts | 5 +- packages/datastore/src/types.ts | 6 +- 14 files changed, 70 insertions(+), 264 deletions(-) delete mode 100644 packages/datastore/src/sync/constants.ts diff --git a/packages/api-graphql/__tests__/GraphQLAPI-test.ts b/packages/api-graphql/__tests__/GraphQLAPI-test.ts index 3824feed192..1eb5a88f997 100644 --- a/packages/api-graphql/__tests__/GraphQLAPI-test.ts +++ b/packages/api-graphql/__tests__/GraphQLAPI-test.ts @@ -1,4 +1,4 @@ -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { GraphQLAPIClass as API } from '../src'; import { InternalGraphQLAPIClass as InternalAPI } from '../src/internals'; import { graphqlOperation } from '../src/GraphQLAPI'; @@ -363,7 +363,7 @@ describe('API test', () => { }); const spyonAuth = jest - .spyOn(InternalAuth, 'currentAuthenticatedUser') + .spyOn(Auth, 'currentAuthenticatedUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res({ @@ -884,7 +884,7 @@ describe('API test', () => { .spyOn(RestClient.prototype, 'post') .mockReturnValue(Promise.resolve({})); - jest.spyOn(InternalAuth, 'currentSession').mockReturnValue({ + jest.spyOn(Auth, 'currentSession').mockReturnValue({ getAccessToken: () => ({ getJwtToken: () => 'Secret-Token', }), @@ -1394,7 +1394,7 @@ describe('Internal API customUserAgent test', () => { }); }); describe('graphql test', () => { - test('happy case mutation - API_KEY', async () => { + test('happy case mutation', async () => { const spyonAuth = jest .spyOn(Credentials, 'get') .mockImplementationOnce(() => { @@ -1410,7 +1410,6 @@ describe('Internal API customUserAgent test', () => { res({}); }); }); - const internalApi = new InternalAPI(config); const url = 'https://appsync.amazonaws.com', region = 'us-east-2', @@ -1465,152 +1464,6 @@ describe('Internal API customUserAgent test', () => { ); expect(spyon).toBeCalledWith(url, init); - - spyonAuth.mockClear(); - spyon.mockClear(); - }); - - test('happy case mutation - OPENID_CONNECT', async () => { - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - const spyonCache = jest - .spyOn(Cache, 'getItem') - .mockImplementationOnce(() => { - return null; - }); - - const spyonAuth = jest - .spyOn(InternalAuth, 'currentAuthenticatedUser') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res({ - name: 'federated user', - token: 'federated_token_from_storage', - }); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const internalApi = new InternalAPI(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - internalApi.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'OPENID_CONNECT', - }); - - const headers = { - Authorization: 'federated_token_from_storage', - 'x-amz-user-agent': expectedUserAgentAPI, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await internalApi.graphql( - graphqlOperation(GetEvent, variables), - undefined, - customUserAgentDetailsAPI - ); - - expect(spyon).toBeCalledWith(url, init); - expect(spyonAuth).toBeCalledWith(undefined, customUserAgentDetailsAPI); - - spyonCache.mockClear(); - spyonAuth.mockClear(); - spyon.mockClear(); - }); - - test('happy case mutation - AMAZON_COGNITO_USER_POOLS', async () => { - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const spyonAuth = jest - .spyOn(InternalAuth, 'currentSession') - .mockReturnValue({ - getAccessToken: () => ({ - getJwtToken: () => 'Secret-Token', - }), - } as any); - - const internalApi = new InternalAPI(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - internalApi.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: 'Secret-Token', - 'x-amz-user-agent': expectedUserAgentAPI, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await internalApi.graphql( - { - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, - }, - undefined, - customUserAgentDetailsAPI - ); - - expect(spyon).toBeCalledWith(url, init); - expect(spyonAuth).toBeCalledWith(customUserAgentDetailsAPI); - - spyon.mockClear(); - spyonAuth.mockClear(); }); test('happy case subscription', async done => { diff --git a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts index e7c071d41e2..40ecf3fb8af 100644 --- a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts +++ b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts @@ -18,7 +18,7 @@ import { INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER, } from '@aws-amplify/core'; import { InternalPubSub } from '@aws-amplify/pubsub/internals'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { Cache } from '@aws-amplify/cache'; import { GraphQLAuthError, @@ -51,7 +51,7 @@ export class InternalGraphQLAPIClass { private _options; private _api = null; - InternalAuth = InternalAuth; + Auth = Auth; Cache = Cache; Credentials = Credentials; @@ -119,8 +119,7 @@ export class InternalGraphQLAPIClass { private async _headerBasedAuth( defaultAuthenticationType?, - additionalHeaders: { [key: string]: string } = {}, - customUserAgentDetails?: CustomUserAgentDetails + additionalHeaders: { [key: string]: string } = {} ) { const { aws_appsync_authenticationType, aws_appsync_apiKey: apiKey } = this._options; @@ -152,10 +151,7 @@ export class InternalGraphQLAPIClass { if (federatedInfo) { token = federatedInfo.token; } else { - const currentUser = await InternalAuth.currentAuthenticatedUser( - undefined, - customUserAgentDetails - ); + const currentUser = await Auth.currentAuthenticatedUser(); if (currentUser) { token = currentUser.token; } @@ -172,9 +168,7 @@ export class InternalGraphQLAPIClass { break; case 'AMAZON_COGNITO_USER_POOLS': try { - const session = await this.InternalAuth.currentSession( - customUserAgentDetails - ); + const session = await this.Auth.currentSession(); headers = { Authorization: session.getAccessToken().getJwtToken(), }; @@ -291,18 +285,10 @@ export class InternalGraphQLAPIClass { const headers = { ...(!customGraphqlEndpoint && - (await this._headerBasedAuth( - authMode, - additionalHeaders, - customUserAgentDetails - ))), + (await this._headerBasedAuth(authMode, additionalHeaders))), ...(customGraphqlEndpoint && (customEndpointRegion - ? await this._headerBasedAuth( - authMode, - additionalHeaders, - customUserAgentDetails - ) + ? await this._headerBasedAuth(authMode, additionalHeaders) : { Authorization: null })), ...(await graphql_headers({ query, variables })), ...additionalHeaders, diff --git a/packages/api/src/internals/InternalAPI.ts b/packages/api/src/internals/InternalAPI.ts index 9c3231bc749..4b555f338b6 100644 --- a/packages/api/src/internals/InternalAPI.ts +++ b/packages/api/src/internals/InternalAPI.ts @@ -8,7 +8,7 @@ import { } from '@aws-amplify/api-graphql'; import { InternalGraphQLAPIClass } from '@aws-amplify/api-graphql/internals'; import { RestAPIClass } from '@aws-amplify/api-rest'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { Cache } from '@aws-amplify/cache'; import { Amplify, @@ -37,7 +37,7 @@ export class InternalAPIClass { private _restApi: RestAPIClass; private _graphqlApi: InternalGraphQLAPIClass; - InternalAuth = InternalAuth; + Auth = Auth; Cache = Cache; Credentials = Credentials; @@ -67,7 +67,7 @@ export class InternalAPIClass { // Share Amplify instance with client for SSR this._restApi.Credentials = this.Credentials; - this._graphqlApi.InternalAuth = this.InternalAuth; + this._graphqlApi.Auth = this.Auth; this._graphqlApi.Cache = this.Cache; this._graphqlApi.Credentials = this.Credentials; diff --git a/packages/aws-amplify/__tests__/withSSRContext-test.ts b/packages/aws-amplify/__tests__/withSSRContext-test.ts index 1557d65b0c7..0e109b5c396 100644 --- a/packages/aws-amplify/__tests__/withSSRContext-test.ts +++ b/packages/aws-amplify/__tests__/withSSRContext-test.ts @@ -71,12 +71,12 @@ describe('withSSRContext', () => { }); it('should use Amplify components from the ssr context', () => { - const { Auth, DataStore, InternalAPI, InternalAuth } = withSSRContext(); + const { Auth, DataStore, InternalAPI } = withSSRContext(); + + expect(DataStore.Auth).toBe(Auth); + expect(DataStore.Auth).not.toBe(Amplify.Auth); expect(DataStore.InternalAPI).toBe(InternalAPI); - expect(DataStore.InternalAPI).not.toBe(Amplify.InternalAPI); - expect(DataStore.InternalAuth).toBe(InternalAuth); - expect(DataStore.InternalAuth).not.toBe(Amplify.InternalAuth); }); }); diff --git a/packages/aws-amplify/src/withSSRContext.ts b/packages/aws-amplify/src/withSSRContext.ts index d9d52f4cd09..34cfe76e795 100644 --- a/packages/aws-amplify/src/withSSRContext.ts +++ b/packages/aws-amplify/src/withSSRContext.ts @@ -3,7 +3,6 @@ import { API } from '@aws-amplify/api'; import { InternalAPI } from '@aws-amplify/api/internals'; import { Auth } from '@aws-amplify/auth'; -import { InternalAuth } from '@aws-amplify/auth/internals'; import { AmplifyClass, Credentials, UniversalStorage } from '@aws-amplify/core'; import { DataStore } from '@aws-amplify/datastore'; @@ -29,7 +28,6 @@ export function withSSRContext(context: Context = {}) { const { modules = defaultModules, req } = context; if (modules.includes(DataStore)) { modules.push(InternalAPI); - modules.push(InternalAuth); } const previousConfig = Amplify.configure(); const amplify = new AmplifyClass(); diff --git a/packages/datastore/__tests__/authStrategies.test.ts b/packages/datastore/__tests__/authStrategies.test.ts index b10a573d945..d9b2411a3ce 100644 --- a/packages/datastore/__tests__/authStrategies.test.ts +++ b/packages/datastore/__tests__/authStrategies.test.ts @@ -1,8 +1,3 @@ -import { - Category, - CustomUserAgentDetails, - DataStoreAction, -} from '@aws-amplify/core'; import { InternalSchema, ModelAttributeAuthAllow, @@ -452,7 +447,7 @@ async function testMultiAuthStrategy({ hasAuthenticatedUser: boolean; result: any; }) { - const currentUserSpy = mockCurrentUser({ hasAuthenticatedUser }); + mockCurrentUser({ hasAuthenticatedUser }); const multiAuthStrategyWrapper = require('../src/authModeStrategies/multiAuthStrategy').multiAuthStrategy; @@ -460,10 +455,6 @@ async function testMultiAuthStrategy({ const multiAuthStrategy = multiAuthStrategyWrapper({}); const schema = getAuthSchema(authRules); - const customUserAgentDetails: CustomUserAgentDetails = { - category: Category.DataStore, - action: DataStoreAction.GraphQl, - }; const authModes = await multiAuthStrategy({ schema, @@ -472,11 +463,9 @@ async function testMultiAuthStrategy({ // but it still technically a required attribute in TS, since customers // won't actually be calling the function directly in their app. operation: ModelOperation.READ, - customUserAgentDetails, }); expect(authModes).toEqual(result); - expect(currentUserSpy).toBeCalledWith(undefined, customUserAgentDetails); jest.resetModules(); jest.resetAllMocks(); } @@ -551,22 +540,18 @@ function mockCurrentUser({ }: { hasAuthenticatedUser: boolean; }) { - const currentAuthenticatedUserSpy = jest.fn().mockImplementation(() => { - return new Promise((res, rej) => { - if (hasAuthenticatedUser) { - res(hasAuthenticatedUser); - } else { - rej(hasAuthenticatedUser); - } - }); - }); - - jest.mock('@aws-amplify/auth/internals', () => ({ - InternalAuth: { - currentAuthenticatedUser: currentAuthenticatedUserSpy, + jest.mock('@aws-amplify/auth', () => ({ + Auth: { + currentAuthenticatedUser: () => { + return new Promise((res, rej) => { + if (hasAuthenticatedUser) { + res(hasAuthenticatedUser); + } else { + rej(hasAuthenticatedUser); + } + }); + }, }, GRAPHQL_AUTH_MODE, })); - - return currentAuthenticatedUserSpy; } diff --git a/packages/datastore/src/authModeStrategies/multiAuthStrategy.ts b/packages/datastore/src/authModeStrategies/multiAuthStrategy.ts index 515368f6ea4..df1c84bfdff 100644 --- a/packages/datastore/src/authModeStrategies/multiAuthStrategy.ts +++ b/packages/datastore/src/authModeStrategies/multiAuthStrategy.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api-graphql'; import { AuthModeStrategy, @@ -9,7 +9,6 @@ import { ModelAttributeAuthAllow, AmplifyContext, } from '../types'; -import { Category, DataStoreAction } from '@aws-amplify/core'; function getProviderFromRule( rule: ModelAttributeAuthProperty @@ -140,14 +139,11 @@ export const multiAuthStrategy: ( amplifyContext: AmplifyContext ) => AuthModeStrategy = (amplifyContext: AmplifyContext) => - async ({ schema, modelName, customUserAgentDetails }) => { - amplifyContext.InternalAuth = amplifyContext.InternalAuth || InternalAuth; + async ({ schema, modelName }) => { + amplifyContext.Auth = amplifyContext.Auth || Auth; let currentUser; try { - currentUser = await amplifyContext.InternalAuth.currentAuthenticatedUser( - undefined, - customUserAgentDetails - ); + currentUser = await amplifyContext.Auth.currentAuthenticatedUser(); } catch (e) { // No current user } diff --git a/packages/datastore/src/datastore/datastore.ts b/packages/datastore/src/datastore/datastore.ts index 6df68ef12ed..98901289d6f 100644 --- a/packages/datastore/src/datastore/datastore.ts +++ b/packages/datastore/src/datastore/datastore.ts @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { InternalAPI } from '@aws-amplify/api/internals'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { Cache } from '@aws-amplify/cache'; import { Amplify, @@ -1383,7 +1383,7 @@ enum DataStoreState { // https://github.com/aws-amplify/amplify-js/pull/10477/files#r1007363485 class DataStore { // reference to configured category instances. Used for preserving SSR context - private InternalAuth = InternalAuth; + private Auth = Auth; private InternalAPI = InternalAPI; private Cache = Cache; @@ -1414,7 +1414,7 @@ class DataStore { private storageAdapter!: Adapter; // object that gets passed to descendent classes. Allows us to pass these down by reference private amplifyContext: AmplifyContext = { - InternalAuth: this.InternalAuth, + Auth: this.Auth, InternalAPI: this.InternalAPI, Cache: this.Cache, }; @@ -2440,7 +2440,7 @@ class DataStore { }; configure = (config: DataStoreConfig = {}) => { - this.amplifyContext.InternalAuth = this.InternalAuth; + this.amplifyContext.Auth = this.Auth; this.amplifyContext.InternalAPI = this.InternalAPI; this.amplifyContext.Cache = this.Cache; diff --git a/packages/datastore/src/sync/constants.ts b/packages/datastore/src/sync/constants.ts deleted file mode 100644 index 61a8e93eac7..00000000000 --- a/packages/datastore/src/sync/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { - Category, - CustomUserAgentDetails, - DataStoreAction, -} from '@aws-amplify/core'; - -export const userAgentDetailsGraphQL: CustomUserAgentDetails = { - category: Category.DataStore, - action: DataStoreAction.GraphQl, -}; - -export const userAgentDetailsSubscribe: CustomUserAgentDetails = { - category: Category.DataStore, - action: DataStoreAction.Subscribe, -}; diff --git a/packages/datastore/src/sync/processors/mutation.ts b/packages/datastore/src/sync/processors/mutation.ts index 17c34c2b888..cd073fc3c4d 100644 --- a/packages/datastore/src/sync/processors/mutation.ts +++ b/packages/datastore/src/sync/processors/mutation.ts @@ -3,7 +3,10 @@ import { GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; import { InternalAPI } from '@aws-amplify/api/internals'; import { + Category, ConsoleLogger as Logger, + CustomUserAgentDetails, + DataStoreAction, jitteredBackoff, NonRetryableError, retry, @@ -11,7 +14,6 @@ import { } from '@aws-amplify/core'; import Observable, { ZenObservable } from 'zen-observable-ts'; import { MutationEvent } from '../'; -import { userAgentDetailsGraphQL as customUserAgentDetails } from '../constants'; import { ModelInstanceCreator } from '../../datastore/datastore'; import { ExclusiveStorage as Storage } from '../../storage/storage'; import { @@ -195,7 +197,6 @@ class MutationProcessor { this.amplifyConfig.aws_appsync_authenticationType, modelName: model, schema: this.schema, - customUserAgentDetails, }); const operationAuthModes = modelAuthModes[operation.toUpperCase()]; @@ -350,6 +351,11 @@ class MutationProcessor { const opType = this.opTypeFromTransformerOperation(operation); + const customUserAgentDetails: CustomUserAgentDetails = { + category: Category.DataStore, + action: DataStoreAction.GraphQl, + }; + do { try { const result = >>( diff --git a/packages/datastore/src/sync/processors/subscription.ts b/packages/datastore/src/sync/processors/subscription.ts index 421e8beaeac..7cc65e12848 100644 --- a/packages/datastore/src/sync/processors/subscription.ts +++ b/packages/datastore/src/sync/processors/subscription.ts @@ -2,17 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 import { GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; import { InternalAPI } from '@aws-amplify/api/internals'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { Cache } from '@aws-amplify/cache'; import { + Category, ConsoleLogger as Logger, + CustomUserAgentDetails, + DataStoreAction, Hub, HubCapsule, BackgroundProcessManager, } from '@aws-amplify/core'; import { CONTROL_MSG as PUBSUB_CONTROL_MSG } from '@aws-amplify/pubsub'; import Observable, { ZenObservable } from 'zen-observable-ts'; -import { userAgentDetailsSubscribe as customUserAgentDetails } from '../constants'; import { InternalSchema, PersistentModel, @@ -84,7 +86,7 @@ class SubscriptionProcessor { private readonly authModeStrategy: AuthModeStrategy, private readonly errorHandler: ErrorHandler, private readonly amplifyContext: AmplifyContext = { - InternalAuth, + Auth, InternalAPI, Cache, } @@ -295,14 +297,11 @@ class SubscriptionProcessor { let cognitoTokenPayload: { [field: string]: any }, oidcTokenPayload: { [field: string]: any }; let userCredentials = USER_CREDENTIALS.none; - this.runningProcesses.add(async () => { try { // retrieving current AWS Credentials const credentials = - await this.amplifyContext.InternalAuth.currentCredentials( - customUserAgentDetails - ); + await this.amplifyContext.Auth.currentCredentials(); userCredentials = credentials.authenticated ? USER_CREDENTIALS.auth : USER_CREDENTIALS.unauth; @@ -312,9 +311,7 @@ class SubscriptionProcessor { try { // retrieving current token info from Cognito UserPools - const session = await this.amplifyContext.InternalAuth.currentSession( - customUserAgentDetails - ); + const session = await this.amplifyContext.Auth.currentSession(); cognitoTokenPayload = session.getIdToken().decodePayload(); } catch (err) { // best effort to get jwt from Cognito @@ -338,10 +335,7 @@ class SubscriptionProcessor { token = federatedInfo.token; } else { const currentUser = - await this.amplifyContext.InternalAuth.currentAuthenticatedUser( - undefined, - customUserAgentDetails - ); + await this.amplifyContext.Auth.currentAuthenticatedUser(); if (currentUser) { token = currentUser.token; } @@ -371,7 +365,6 @@ class SubscriptionProcessor { this.amplifyConfig.aws_appsync_authenticationType, modelName: modelDefinition.name, schema: this.schema, - customUserAgentDetails, }); // subscriptions are created only based on the READ auth mode(s) @@ -438,6 +431,11 @@ class SubscriptionProcessor { const variables = {}; + const customUserAgentDetails: CustomUserAgentDetails = { + category: Category.DataStore, + action: DataStoreAction.Subscribe, + }; + if (addFilter && predicatesGroup) { variables['filter'] = predicateToGraphQLFilter(predicatesGroup); diff --git a/packages/datastore/src/sync/processors/sync.ts b/packages/datastore/src/sync/processors/sync.ts index 24fddc0573f..d561be7ffe9 100644 --- a/packages/datastore/src/sync/processors/sync.ts +++ b/packages/datastore/src/sync/processors/sync.ts @@ -3,7 +3,6 @@ import { GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api'; import { InternalAPI } from '@aws-amplify/api/internals'; import Observable from 'zen-observable-ts'; -import { userAgentDetailsGraphQL as customUserAgentDetails } from '../constants'; import { InternalSchema, ModelInstanceMetadata, @@ -26,7 +25,10 @@ import { } from '../utils'; import { jitteredExponentialRetry, + Category, ConsoleLogger as Logger, + CustomUserAgentDetails, + DataStoreAction, Hub, NonRetryableError, BackgroundProcessManager, @@ -116,7 +118,6 @@ class SyncProcessor { defaultAuthMode: this.amplifyConfig.aws_appsync_authenticationType, modelName: modelDefinition.name, schema: this.schema, - customUserAgentDetails, }); // sync only needs the READ auth mode(s) @@ -218,6 +219,11 @@ class SyncProcessor { this.amplifyConfig ); + const customUserAgentDetails: CustomUserAgentDetails = { + category: Category.DataStore, + action: DataStoreAction.GraphQl, + }; + return await this.amplifyContext.InternalAPI.graphql( { query, diff --git a/packages/datastore/src/sync/utils.ts b/packages/datastore/src/sync/utils.ts index 9c14a93185a..0edce1a2b5d 100644 --- a/packages/datastore/src/sync/utils.ts +++ b/packages/datastore/src/sync/utils.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api-graphql'; import { GraphQLAuthError } from '@aws-amplify/api'; -import { CustomUserAgentDetails, Logger } from '@aws-amplify/core'; +import { Logger } from '@aws-amplify/core'; import { ModelInstanceCreator } from '../datastore/datastore'; import { AuthorizationRule, @@ -819,13 +819,11 @@ export async function getModelAuthModes({ defaultAuthMode, modelName, schema, - customUserAgentDetails, }: { authModeStrategy: AuthModeStrategy; defaultAuthMode: GRAPHQL_AUTH_MODE; modelName: string; schema: InternalSchema; - customUserAgentDetails?: CustomUserAgentDetails; }): Promise<{ [key in ModelOperation]: GRAPHQL_AUTH_MODE[]; }> { @@ -847,7 +845,6 @@ export async function getModelAuthModes({ schema, modelName, operation, - customUserAgentDetails, }); if (typeof authModes === 'string') { diff --git a/packages/datastore/src/types.ts b/packages/datastore/src/types.ts index d09b1ec5fa6..e5468981a85 100644 --- a/packages/datastore/src/types.ts +++ b/packages/datastore/src/types.ts @@ -16,11 +16,10 @@ import { } from './util'; import { PredicateAll } from './predicates'; import { GRAPHQL_AUTH_MODE } from '@aws-amplify/api-graphql'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth } from '@aws-amplify/auth'; import { InternalAPI } from '@aws-amplify/api/internals'; import { Cache } from '@aws-amplify/cache'; import { Adapter } from './storage/adapter'; -import { CustomUserAgentDetails } from '@aws-amplify/core'; export type Scalar = T extends Array ? InnerType : T; @@ -972,7 +971,6 @@ export type AuthModeStrategyParams = { schema: InternalSchema; modelName: string; operation: ModelOperation; - customUserAgentDetails?: CustomUserAgentDetails; }; export type AuthModeStrategy = ( @@ -1104,7 +1102,7 @@ export enum LimitTimerRaceResolvedValues { //#endregion export type AmplifyContext = { - InternalAuth: typeof InternalAuth; + Auth: typeof Auth; InternalAPI: typeof InternalAPI; Cache: typeof Cache; }; From 6e7d8387d6e283983481de470c986ab55b4afdd0 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:32:30 -0500 Subject: [PATCH 05/16] Revert "fix: only register auth urlListener from InternalAuth instance (#11810)" This reverts commit bee9c8e25f43b092b5ed8c256ebc1c37f2a4655b. --- packages/auth/src/internals/InternalAuth.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/auth/src/internals/InternalAuth.ts b/packages/auth/src/internals/InternalAuth.ts index a54c3e1f13f..3584e5b78a4 100644 --- a/packages/auth/src/internals/InternalAuth.ts +++ b/packages/auth/src/internals/InternalAuth.ts @@ -260,17 +260,14 @@ export class InternalAuthClass { // Prevents _handleAuthResponse from being called multiple times in Expo // See https://github.com/aws-amplify/amplify-js/issues/4388 const usedResponseUrls = {}; - // Only register urlListener once - if (this.getModuleName() === 'InternalAuth') { - urlListener(({ url }) => { - if (usedResponseUrls[url]) { - return; - } + urlListener(({ url }) => { + if (usedResponseUrls[url]) { + return; + } - usedResponseUrls[url] = true; - this._handleAuthResponse(url); - }); - } + usedResponseUrls[url] = true; + this._handleAuthResponse(url); + }); } dispatchAuthEvent( From 946d6fefe9563946147196898f94d76f1edc5dca Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:32:39 -0500 Subject: [PATCH 06/16] Revert "feat: capture PubSub metrics on cross-category auth calls (#11803)" This reverts commit 10c68684c242f8e3d056b3821a7a30fab56ea2a8. --- .../AWSAppSyncRealTimeProvider.test.ts | 94 ++++++------------- .../AWSAppSyncRealTimeProvider/index.ts | 20 +--- 2 files changed, 34 insertions(+), 80 deletions(-) diff --git a/packages/pubsub/__tests__/AWSAppSyncRealTimeProvider.test.ts b/packages/pubsub/__tests__/AWSAppSyncRealTimeProvider.test.ts index 7f9f6e6990f..d40e7061787 100644 --- a/packages/pubsub/__tests__/AWSAppSyncRealTimeProvider.test.ts +++ b/packages/pubsub/__tests__/AWSAppSyncRealTimeProvider.test.ts @@ -10,16 +10,8 @@ jest.mock('@aws-amplify/core', () => ({ })); import Observable from 'zen-observable-ts'; -import { - Reachability, - Credentials, - Logger, - Signer, - Category, - PubSubAction, - CustomUserAgentDetails, -} from '@aws-amplify/core'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Reachability, Credentials, Logger, Signer } from '@aws-amplify/core'; +import { Auth } from '@aws-amplify/auth'; import { Cache } from '@aws-amplify/cache'; import { MESSAGE_TYPES } from '../src/Providers/constants'; @@ -31,11 +23,6 @@ import { ConnectionState as CS } from '../src'; import { AWSAppSyncRealTimeProvider } from '../src/Providers/AWSAppSyncRealTimeProvider'; import { loggers } from 'winston'; -const userAgentDetails: CustomUserAgentDetails = { - category: Category.PubSub, - action: PubSubAction.Subscribe, -}; - describe('AWSAppSyncRealTimeProvider', () => { describe('isCustomDomain()', () => { test('Custom domain returns `true`', () => { @@ -1075,10 +1062,10 @@ describe('AWSAppSyncRealTimeProvider', () => { }); test('authenticating with OPENID_CONNECT', async () => { - expect.assertions(2); + expect.assertions(1); - const authSpy = jest - .spyOn(InternalAuth, 'currentAuthenticatedUser') + const userSpy = jest + .spyOn(Auth, 'currentAuthenticatedUser') .mockImplementation(() => { return Promise.resolve({ token: 'test', @@ -1086,14 +1073,10 @@ describe('AWSAppSyncRealTimeProvider', () => { }); provider - .subscribe( - 'test', - { - appSyncGraphqlEndpoint: 'ws://localhost:8080', - authenticationType: 'OPENID_CONNECT', - }, - userAgentDetails - ) + .subscribe('test', { + appSyncGraphqlEndpoint: 'ws://localhost:8080', + authenticationType: 'OPENID_CONNECT', + }) .subscribe({ error: () => {} }); await fakeWebSocketInterface?.readyForUse; @@ -1102,15 +1085,13 @@ describe('AWSAppSyncRealTimeProvider', () => { 'DEBUG', 'Authenticating with OPENID_CONNECT' ); - expect(authSpy).toBeCalledWith(undefined, userAgentDetails); - authSpy.mockClear(); }); test('authenticating with OPENID_CONNECT with empty token', async () => { - expect.assertions(2); + expect.assertions(1); - const authSpy = jest - .spyOn(InternalAuth, 'currentAuthenticatedUser') + jest + .spyOn(Auth, 'currentAuthenticatedUser') .mockImplementation(() => { return Promise.resolve({ token: undefined, @@ -1118,14 +1099,10 @@ describe('AWSAppSyncRealTimeProvider', () => { }); provider - .subscribe( - 'test', - { - appSyncGraphqlEndpoint: 'ws://localhost:8080', - authenticationType: 'OPENID_CONNECT', - }, - userAgentDetails - ) + .subscribe('test', { + appSyncGraphqlEndpoint: 'ws://localhost:8080', + authenticationType: 'OPENID_CONNECT', + }) .subscribe({ error: () => {} }); // TODO Find a better way to give the catch stack time to resolve @@ -1135,8 +1112,6 @@ describe('AWSAppSyncRealTimeProvider', () => { 'DEBUG', 'AppSync Realtime subscription init error: Error: No federated jwt' ); - expect(authSpy).toBeCalledWith(undefined, userAgentDetails); - authSpy.mockClear(); }); test('authenticating with OPENID_CONNECT from cached token', async () => { @@ -1163,29 +1138,23 @@ describe('AWSAppSyncRealTimeProvider', () => { }); test('authenticating with AMAZON_COGNITO_USER_POOLS', async () => { - expect.assertions(2); + expect.assertions(1); - const authSpy = jest - .spyOn(InternalAuth, 'currentSession') - .mockImplementation(() => { - return Promise.resolve({ - getAccessToken: () => { - return { - getJwtToken: () => {}, - }; - }, - } as any); - }); + jest.spyOn(Auth, 'currentSession').mockImplementation(() => { + return Promise.resolve({ + getAccessToken: () => { + return { + getJwtToken: () => {}, + }; + }, + } as any); + }); provider - .subscribe( - 'test', - { - appSyncGraphqlEndpoint: 'ws://localhost:8080', - authenticationType: 'AMAZON_COGNITO_USER_POOLS', - }, - userAgentDetails - ) + .subscribe('test', { + appSyncGraphqlEndpoint: 'ws://localhost:8080', + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + }) .subscribe({ error: () => {} }); await fakeWebSocketInterface?.readyForUse; @@ -1194,9 +1163,6 @@ describe('AWSAppSyncRealTimeProvider', () => { 'DEBUG', 'Authenticating with AMAZON_COGNITO_USER_POOLS' ); - expect(authSpy).toBeCalledWith(userAgentDetails); - - authSpy.mockClear(); }); test('authenticating with AWS_LAMBDA', async () => { diff --git a/packages/pubsub/src/Providers/AWSAppSyncRealTimeProvider/index.ts b/packages/pubsub/src/Providers/AWSAppSyncRealTimeProvider/index.ts index 27b0751b802..ffc8c47a812 100644 --- a/packages/pubsub/src/Providers/AWSAppSyncRealTimeProvider/index.ts +++ b/packages/pubsub/src/Providers/AWSAppSyncRealTimeProvider/index.ts @@ -21,8 +21,7 @@ import { getAmplifyUserAgent, } from '@aws-amplify/core'; import { Cache } from '@aws-amplify/cache'; -import { GRAPHQL_AUTH_MODE } from '@aws-amplify/auth'; -import { InternalAuth } from '@aws-amplify/auth/internals'; +import { Auth, GRAPHQL_AUTH_MODE } from '@aws-amplify/auth'; import { AbstractPubSubProvider } from '../PubSubProvider'; import { CONTROL_MSG, @@ -115,7 +114,6 @@ type AWSAppSyncRealTimeAuthInput = canonicalUri: string; payload: string; host?: string | undefined; - customUserAgentDetails?: CustomUserAgentDetails; }; export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider { @@ -351,7 +349,6 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider | undefined > { @@ -931,18 +927,14 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider Date: Tue, 22 Aug 2023 16:32:50 -0500 Subject: [PATCH 07/16] Revert "feat: pipe auth user agent details through to service call (#11755)" This reverts commit 9497c28c83754f5d2fea1e832ba1e03d2087e66c. --- .../internals/index.d.ts | 47 +- .../src/internals/InternalCognitoUser.js | 8 +- .../src/internals/InternalCognitoUserPool.js | 5 +- packages/analytics/package.json | 4 +- packages/api-graphql/package.json | 2 +- packages/api/package.json | 2 +- .../auth/__tests__/auth-attribute-test.ts | 14 +- packages/auth/__tests__/auth-creds-test.ts | 12 +- .../__tests__/auth-federation-unit-tests.ts | 87 +- .../auth/__tests__/auth-refresh-token-test.ts | 53 +- packages/auth/__tests__/auth-unit-test.ts | 1012 +++++++---------- packages/auth/__tests__/hosted-ui.test.ts | 38 +- packages/auth/__tests__/totp-unit-test.ts | 139 +-- packages/auth/package.json | 2 +- packages/auth/src/OAuth/OAuth.ts | 19 +- packages/auth/src/internals/InternalAuth.ts | 976 +++++----------- packages/auth/src/utils.ts | 27 - packages/core/package.json | 2 +- packages/core/src/Platform/types.ts | 70 +- packages/datastore/package.json | 2 +- packages/geo/package.json | 2 +- packages/interactions/package.json | 2 +- packages/notifications/package.json | 4 +- packages/predictions/package.json | 8 +- packages/pubsub/package.json | 4 +- packages/storage/package.json | 2 +- 26 files changed, 951 insertions(+), 1592 deletions(-) delete mode 100644 packages/auth/src/utils.ts diff --git a/packages/amazon-cognito-identity-js/internals/index.d.ts b/packages/amazon-cognito-identity-js/internals/index.d.ts index 713869332ae..5bb20d17a63 100644 --- a/packages/amazon-cognito-identity-js/internals/index.d.ts +++ b/packages/amazon-cognito-identity-js/internals/index.d.ts @@ -22,6 +22,29 @@ import { export const addAuthCategoryToCognitoUserAgent: () => void; export const addFrameworkToCognitoUserAgent: (content: string) => void; +export class InternalCognitoUserPool { + constructor( + data: ICognitoUserPoolData, + wrapRefreshSessionCallback?: (target: NodeCallback.Any) => NodeCallback.Any + ); + + public getUserPoolId(): string; + public getUserPoolName(): string; + public getClientId(): string; + + public signUp( + username: string, + password: string, + userAttributes: CognitoUserAttribute[], + validationData: CognitoUserAttribute[], + callback: NodeCallback, + clientMetadata?: ClientMetadata, + userAgentValue?: string + ): void; + + public getCurrentUser(): CognitoUser | null; +} + export class InternalCognitoUser { constructor(data: ICognitoUserData); @@ -211,7 +234,6 @@ export class InternalCognitoUser { ): void; public deleteUser( callback: NodeCallback, - clientMetaData?: ClientMetadata, userAgentValue?: string ): void; public enableMFA( @@ -270,26 +292,3 @@ export class InternalCognitoUser { userAgentValue?: string ): void; } - -export class InternalCognitoUserPool { - constructor( - data: ICognitoUserPoolData, - wrapRefreshSessionCallback?: (target: NodeCallback.Any) => NodeCallback.Any - ); - - public getUserPoolId(): string; - public getUserPoolName(): string; - public getClientId(): string; - - public signUp( - username: string, - password: string, - userAttributes: CognitoUserAttribute[], - validationData: CognitoUserAttribute[], - callback: NodeCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - - public getCurrentUser(): CognitoUser | null; -} diff --git a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js b/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js index f9cda9dd4e7..19f0e87b79e 100644 --- a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js +++ b/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js @@ -1378,7 +1378,7 @@ export class InternalCognitoUser { const userData = this.getUserDataFromCache(); if (!userData) { - this.fetchUserData(userAgentValue) + this.fetchUserData() .then(data => { callback(null, data); }) @@ -1387,7 +1387,7 @@ export class InternalCognitoUser { } if (this.isFetchUserDataAndTokenRequired(params)) { - this.fetchUserData(userAgentValue) + this.fetchUserData() .then(data => { return this.refreshSessionIfPossible(params, userAgentValue).then( () => data @@ -1436,10 +1436,10 @@ export class InternalCognitoUser { * @param {string} userAgentValue Optional string containing custom user agent value */ fetchUserData(userAgentValue) { - return this.createGetUserRequest(userAgentValue).then(data => { + return this.createGetUserRequest().then(data => { this.cacheUserData(data); return data; - }); + }, userAgentValue); } /** diff --git a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js b/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js index e83b8ce273e..af98946441e 100644 --- a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js +++ b/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js @@ -5,7 +5,6 @@ import Client from '../Client'; import CognitoUser from '../CognitoUser'; -import { InternalCognitoUser } from '../internals'; import StorageHelper from '../StorageHelper'; const USER_POOL_ID_MAX_LENGTH = 55; @@ -131,7 +130,7 @@ export class InternalCognitoUserPool { }; const returnData = { - user: new InternalCognitoUser(cognitoUser), + user: new CognitoUser(cognitoUser), userConfirmed: data.UserConfirmed, userSub: data.UserSub, codeDeliveryDetails: data.CodeDeliveryDetails, @@ -157,7 +156,7 @@ export class InternalCognitoUserPool { Storage: this.storage, }; - return new InternalCognitoUser(cognitoUser); + return new CognitoUser(cognitoUser); } return null; diff --git a/packages/analytics/package.json b/packages/analytics/package.json index bd7ce789fa2..714bcdd6cd8 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -66,13 +66,13 @@ "name": "Analytics (Pinpoint)", "path": "./lib-esm/index.js", "import": "{ Amplify, Analytics, AWSPinpointProvider }", - "limit": "31.57 kB" + "limit": "31.5 kB" }, { "name": "Analytics (Kinesis)", "path": "./lib-esm/index.js", "import": "{ Amplify, Analytics, AWSKinesisProvider }", - "limit": "60.82 kB" + "limit": "60.5 kB" } ], "jest": { diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json index 01e3882904d..66fb3a85f49 100644 --- a/packages/api-graphql/package.json +++ b/packages/api-graphql/package.json @@ -65,7 +65,7 @@ "name": "API (GraphQL client)", "path": "./lib-esm/index.js", "import": "{ Amplify, GraphQLAPI }", - "limit": "90.35 kB" + "limit": "89.52 kB" } ], "jest": { diff --git a/packages/api/package.json b/packages/api/package.json index c93324012c2..d9f0fa62357 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -67,7 +67,7 @@ "name": "API (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, API }", - "limit": "91.12 kB" + "limit": "90.28 kB" } ], "jest": { diff --git a/packages/auth/__tests__/auth-attribute-test.ts b/packages/auth/__tests__/auth-attribute-test.ts index e91dad66e7f..10963bcfd42 100644 --- a/packages/auth/__tests__/auth-attribute-test.ts +++ b/packages/auth/__tests__/auth-attribute-test.ts @@ -8,10 +8,6 @@ import { CognitoAccessToken, CognitoUserAttribute, } from 'amazon-cognito-identity-js'; -import { - InternalCognitoUserPool, - InternalCognitoUser, -} from 'amazon-cognito-identity-js/internals'; import { AuthOptions } from '../src/types'; import { InternalAuthClass } from '../src/internals/InternalAuth'; @@ -27,7 +23,7 @@ const authOptions: AuthOptions = { describe('User-Attribute-validation', () => { it('Check-non-verified-attributes', async () => { const spyonAuthUserAttributes = jest - .spyOn(InternalAuthClass.prototype as any, '_userAttributes') + .spyOn(InternalAuthClass.prototype, 'userAttributes') .mockImplementation((user: CognitoUser) => { const emailAttribute = new CognitoUserAttribute({ Name: 'email', @@ -79,10 +75,10 @@ describe('User-Attribute-validation', () => { const auth = new Auth(authOptions); const spyUserPoolCurrentUser = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ UserPoolId: authOptions.userPoolId, ClientId: authOptions.userPoolWebClientId, }), @@ -91,7 +87,7 @@ describe('User-Attribute-validation', () => { }); const spyUserGetSession = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { const session = new CognitoUserSession({ AccessToken: new CognitoAccessToken({ AccessToken: 'accesstoken' }), @@ -107,7 +103,7 @@ describe('User-Attribute-validation', () => { }); const spyGetUserData = jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementation(callback => { const emailAttribute = { Name: 'email', diff --git a/packages/auth/__tests__/auth-creds-test.ts b/packages/auth/__tests__/auth-creds-test.ts index 7de9e72a6bd..b6208d4f7f8 100644 --- a/packages/auth/__tests__/auth-creds-test.ts +++ b/packages/auth/__tests__/auth-creds-test.ts @@ -3,15 +3,11 @@ import { Credentials } from '@aws-amplify/core'; import { AuthOptions } from '../src/types'; import { CognitoUser, + CognitoUserPool, CognitoUserSession, CognitoAccessToken, CognitoIdToken, - CognitoUserPool, } from 'amazon-cognito-identity-js'; -import { - InternalCognitoUser, - InternalCognitoUserPool, -} from 'amazon-cognito-identity-js/internals'; const authOptions: AuthOptions = { userPoolId: 'us-west-2_0xxxxxxxx', userPoolWebClientId: 'awsUserPoolsWebClientId', @@ -25,7 +21,7 @@ describe('credentials syncing tests', () => { const auth = new Auth(authOptions); jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementation((authenticationDetails, callback) => { const session = new CognitoUserSession({ AccessToken: new CognitoAccessToken({ AccessToken: 'accesstoken' }), @@ -36,7 +32,7 @@ describe('credentials syncing tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ Pool: new CognitoUserPool({ @@ -53,7 +49,7 @@ describe('credentials syncing tests', () => { }); jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { callback(null, session); }); diff --git a/packages/auth/__tests__/auth-federation-unit-tests.ts b/packages/auth/__tests__/auth-federation-unit-tests.ts index 13b742a63fb..388a769cd3b 100644 --- a/packages/auth/__tests__/auth-federation-unit-tests.ts +++ b/packages/auth/__tests__/auth-federation-unit-tests.ts @@ -54,15 +54,15 @@ jest.mock('amazon-cognito-identity-js/lib/CognitoUserSession', () => { return CognitoUserSession; }); -jest.mock('amazon-cognito-identity-js/internals', () => { - const InternalCognitoUserPool = () => {}; +jest.mock('amazon-cognito-identity-js/lib/CognitoUserPool', () => { + const CognitoUserPool = () => {}; - InternalCognitoUserPool.prototype.InternalCognitoUserPool = options => { - InternalCognitoUserPool.prototype.options = options; - return InternalCognitoUserPool; + CognitoUserPool.prototype.CognitoUserPool = options => { + CognitoUserPool.prototype.options = options; + return CognitoUserPool; }; - InternalCognitoUserPool.prototype.getCurrentUser = () => { + CognitoUserPool.prototype.getCurrentUser = () => { return { username: 'username', getSession: callback => { @@ -78,7 +78,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { }; }; - InternalCognitoUserPool.prototype.signUp = ( + CognitoUserPool.prototype.signUp = ( username, password, signUpAttributeList, @@ -89,48 +89,49 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'signUpResult'); }; - const InternalCognitoUser = () => {}; + return CognitoUserPool; +}); + +jest.mock('amazon-cognito-identity-js/lib/CognitoUser', () => { + const CognitoUser = () => {}; - InternalCognitoUser.prototype.InternalCognitoUser = options => { - InternalCognitoUser.prototype.options = options; - return InternalCognitoUser; + CognitoUser.prototype.CognitoUser = options => { + CognitoUser.prototype.options = options; + return CognitoUser; }; - InternalCognitoUser.prototype.getSession = callback => { + CognitoUser.prototype.getSession = callback => { callback(null, 'session'); }; - InternalCognitoUser.prototype.getUserAttributes = callback => { + CognitoUser.prototype.getUserAttributes = callback => { callback(null, 'attributes'); }; - InternalCognitoUser.prototype.getAttributeVerificationCode = ( - attr, - callback - ) => { + CognitoUser.prototype.getAttributeVerificationCode = (attr, callback) => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.verifyAttribute = (attr, code, callback) => { + CognitoUser.prototype.verifyAttribute = (attr, code, callback) => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.authenticateUser = ( + CognitoUser.prototype.authenticateUser = ( authenticationDetails, callback ) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.sendMFACode = (code, callback) => { + CognitoUser.prototype.sendMFACode = (code, callback) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.resendConfirmationCode = callback => { + CognitoUser.prototype.resendConfirmationCode = callback => { callback(null, 'result'); }; - InternalCognitoUser.prototype.changePassword = ( + CognitoUser.prototype.changePassword = ( oldPassword, newPassword, callback @@ -138,25 +139,21 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.forgotPassword = callback => { + CognitoUser.prototype.forgotPassword = callback => { callback.onSuccess(); }; - InternalCognitoUser.prototype.confirmPassword = ( - code, - password, - callback - ) => { + CognitoUser.prototype.confirmPassword = (code, password, callback) => { callback.onSuccess(); }; - InternalCognitoUser.prototype.signOut = () => {}; + CognitoUser.prototype.signOut = () => {}; - InternalCognitoUser.prototype.globalSignOut = callback => { + CognitoUser.prototype.globalSignOut = callback => { callback.onSuccess(); }; - InternalCognitoUser.prototype.confirmRegistration = ( + CognitoUser.prototype.confirmRegistration = ( confirmationCode, forceAliasCreation, callback @@ -164,7 +161,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'Success'); }; - InternalCognitoUser.prototype.completeNewPasswordChallenge = ( + CognitoUser.prototype.completeNewPasswordChallenge = ( password, requiredAttributes, callback @@ -172,46 +169,36 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.updateAttributes = ( - attributeList, - callback - ) => { + CognitoUser.prototype.updateAttributes = (attributeList, callback) => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.setAuthenticationFlowType = type => {}; + CognitoUser.prototype.setAuthenticationFlowType = type => {}; - InternalCognitoUser.prototype.initiateAuth = ( - authenticationDetails, - callback - ) => { + CognitoUser.prototype.initiateAuth = (authenticationDetails, callback) => { callback.customChallenge('challengeParam'); }; - InternalCognitoUser.prototype.sendCustomChallengeAnswer = ( + CognitoUser.prototype.sendCustomChallengeAnswer = ( challengeAnswer, callback ) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.refreshSession = (refreshToken, callback) => { + CognitoUser.prototype.refreshSession = (refreshToken, callback) => { callback(null, 'session'); }; - InternalCognitoUser.prototype.getUsername = () => { + CognitoUser.prototype.getUsername = () => { return 'username'; }; - InternalCognitoUser.prototype.getUserData = callback => { + CognitoUser.prototype.getUserData = callback => { callback(null, 'data'); }; - return { - ...jest.requireActual('amazon-cognito-identity-js/internals'), - InternalCognitoUser, - InternalCognitoUserPool, - }; + return CognitoUser; }); function mockGAPI({ reloadAuthResponse }: { reloadAuthResponse: Function }) { diff --git a/packages/auth/__tests__/auth-refresh-token-test.ts b/packages/auth/__tests__/auth-refresh-token-test.ts index 8ecc5861f9c..3aeedd9a437 100644 --- a/packages/auth/__tests__/auth-refresh-token-test.ts +++ b/packages/auth/__tests__/auth-refresh-token-test.ts @@ -1,17 +1,14 @@ import { AuthClass as Auth } from '../src/Auth'; import { + CognitoUserPool, CognitoUser, CognitoUserSession, CognitoIdToken, CognitoAccessToken, + CognitoUserAttribute, } from 'amazon-cognito-identity-js'; -import { - InternalCognitoUser, - InternalCognitoUserPool, -} from 'amazon-cognito-identity-js/internals'; - import { AuthOptions } from '../src/types'; const clientMetadata = { @@ -35,7 +32,7 @@ describe('Refresh token', () => { expect.assertions(1); const getSessionSpy = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation( // @ts-ignore ( @@ -54,10 +51,10 @@ describe('Refresh token', () => { ); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ ClientId: authOptions.userPoolWebClientId, UserPoolId: authOptions.userPoolId, }), @@ -74,7 +71,7 @@ describe('Refresh token', () => { expect.assertions(2); const getSessionSpy = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation( // @ts-ignore ( @@ -93,10 +90,10 @@ describe('Refresh token', () => { ); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ ClientId: authOptions.userPoolWebClientId, UserPoolId: authOptions.userPoolId, }), @@ -114,7 +111,7 @@ describe('Refresh token', () => { expect.assertions(2); - jest.spyOn(InternalCognitoUser.prototype, 'getSession').mockImplementation( + jest.spyOn(CognitoUser.prototype, 'getSession').mockImplementation( // @ts-ignore ( callback: (error: Error, session: CognitoUserSession) => void, @@ -132,16 +129,16 @@ describe('Refresh token', () => { ); jest - .spyOn(InternalCognitoUser.prototype, 'globalSignOut') + .spyOn(CognitoUser.prototype, 'globalSignOut') .mockImplementation(({ onSuccess, onFailure }) => { onSuccess(''); }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ ClientId: authOptions.userPoolWebClientId, UserPoolId: authOptions.userPoolId, }), @@ -160,7 +157,7 @@ describe('Refresh token', () => { expect.assertions(1); - jest.spyOn(InternalCognitoUser.prototype, 'getSession').mockImplementation( + jest.spyOn(CognitoUser.prototype, 'getSession').mockImplementation( // @ts-ignore ( callback: (error: Error, session: CognitoUserSession) => void, @@ -178,7 +175,7 @@ describe('Refresh token', () => { ); jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementation((callback, params) => { expect(params.clientMetadata).toEqual(clientMetadata); @@ -192,10 +189,10 @@ describe('Refresh token', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ ClientId: authOptions.userPoolWebClientId, UserPoolId: authOptions.userPoolId, }), @@ -212,7 +209,7 @@ describe('Refresh token', () => { expect.assertions(2); - jest.spyOn(InternalCognitoUser.prototype, 'getSession').mockImplementation( + jest.spyOn(CognitoUser.prototype, 'getSession').mockImplementation( // @ts-ignore ( callback: (error: Error, session: CognitoUserSession) => void, @@ -230,7 +227,7 @@ describe('Refresh token', () => { ); jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementation((callback, params) => { expect(params.clientMetadata).toEqual(clientMetadata); @@ -244,10 +241,10 @@ describe('Refresh token', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ ClientId: authOptions.userPoolWebClientId, UserPoolId: authOptions.userPoolId, }), @@ -266,7 +263,7 @@ describe('Refresh token', () => { expect.assertions(3); - jest.spyOn(InternalCognitoUser.prototype, 'getSession').mockImplementation( + jest.spyOn(CognitoUser.prototype, 'getSession').mockImplementation( // @ts-ignore ( callback: (error: Error, session: CognitoUserSession) => void, @@ -284,7 +281,7 @@ describe('Refresh token', () => { ); jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementation((callback, params) => { expect(params.clientMetadata).toEqual(clientMetadata); @@ -298,10 +295,10 @@ describe('Refresh token', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return new CognitoUser({ - Pool: new InternalCognitoUserPool({ + Pool: new CognitoUserPool({ ClientId: authOptions.userPoolWebClientId, UserPoolId: authOptions.userPoolId, }), @@ -310,7 +307,7 @@ describe('Refresh token', () => { }); jest - .spyOn(InternalCognitoUser.prototype, 'setUserMfaPreference') + .spyOn(CognitoUser.prototype, 'setUserMfaPreference') .mockImplementation( (smsMfaSettings, softwareTokenMfaSettings, callback) => { callback(); diff --git a/packages/auth/__tests__/auth-unit-test.ts b/packages/auth/__tests__/auth-unit-test.ts index 26993310391..eaf6b022370 100644 --- a/packages/auth/__tests__/auth-unit-test.ts +++ b/packages/auth/__tests__/auth-unit-test.ts @@ -2,16 +2,14 @@ import OAuth from '../src/OAuth/OAuth'; import * as oauthStorage from '../src/OAuth/oauthStorage'; import { CookieStorage, + CognitoUserPool, + CognitoUser, CognitoUserSession, CognitoIdToken, CognitoAccessToken, NodeCallback, ISignUpResult, } from 'amazon-cognito-identity-js'; -import { - InternalCognitoUser, - InternalCognitoUserPool, -} from 'amazon-cognito-identity-js/internals'; const MAX_DEVICES: number = 60; @@ -71,50 +69,86 @@ jest.mock('amazon-cognito-identity-js/lib/CognitoUserSession', () => { return CognitoUserSession; }); -jest.mock('amazon-cognito-identity-js/internals', () => { - // prettier-ignore - const InternalCognitoUser = function() { +jest.mock('amazon-cognito-identity-js/lib/CognitoUserPool', () => { + const CognitoUserPool = () => {}; + + CognitoUserPool.prototype.CognitoUserPool = options => { + CognitoUserPool.prototype.options = options; + return CognitoUserPool; + }; + + CognitoUserPool.prototype.getCurrentUser = () => { + return { + username: 'username', + attributes: { email: 'test@test.com' }, + getSession: callback => { + // throw 3; + callback(null, { + getAccessToken: () => { + return { + decodePayload: () => 'payload', + getJwtToken: () => 'jwt', + }; + }, + }); + }, + }; + }; + + CognitoUserPool.prototype.signUp = ( + username, + password, + signUpAttributeList, + validationData, + callback, + clientMetadata + ) => { + callback(null, 'signUpResult'); + }; + + return CognitoUserPool; +}); + +jest.mock('amazon-cognito-identity-js/lib/CognitoUser', () => { + const CognitoUser = function() { // mock private member this.signInUserSession = null; }; - InternalCognitoUser.prototype.InternalCognitoUser = options => { - InternalCognitoUser.prototype.options = options; - return InternalCognitoUser; + CognitoUser.prototype.CognitoUser = options => { + CognitoUser.prototype.options = options; + return CognitoUser; }; - InternalCognitoUser.prototype.getSession = callback => { + CognitoUser.prototype.getSession = callback => { // throw 3; callback(null, 'session'); }; - InternalCognitoUser.prototype.getUserAttributes = callback => { + CognitoUser.prototype.getUserAttributes = callback => { callback(null, 'attributes'); }; - InternalCognitoUser.prototype.getAttributeVerificationCode = ( - attr, - callback - ) => { + CognitoUser.prototype.getAttributeVerificationCode = (attr, callback) => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.verifyAttribute = (attr, code, callback) => { + CognitoUser.prototype.verifyAttribute = (attr, code, callback) => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.authenticateUser = ( + CognitoUser.prototype.authenticateUser = ( authenticationDetails, callback ) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.sendMFACode = (code, callback) => { + CognitoUser.prototype.sendMFACode = (code, callback) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.resendConfirmationCode = callback => { + CognitoUser.prototype.resendConfirmationCode = callback => { callback(null, { CodeDeliveryDetails: { AttributeName: 'email', @@ -124,7 +158,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { }); }; - InternalCognitoUser.prototype.changePassword = ( + CognitoUser.prototype.changePassword = ( oldPassword, newPassword, callback @@ -132,29 +166,25 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.forgotPassword = callback => { + CognitoUser.prototype.forgotPassword = callback => { callback.onSuccess(); }; - InternalCognitoUser.prototype.confirmPassword = ( - code, - password, - callback - ) => { + CognitoUser.prototype.confirmPassword = (code, password, callback) => { callback.onSuccess(); }; - InternalCognitoUser.prototype.signOut = callback => { + CognitoUser.prototype.signOut = callback => { if (callback && typeof callback === 'function') { callback(); } }; - InternalCognitoUser.prototype.globalSignOut = callback => { + CognitoUser.prototype.globalSignOut = callback => { callback.onSuccess(); }; - InternalCognitoUser.prototype.confirmRegistration = ( + CognitoUser.prototype.confirmRegistration = ( confirmationCode, forceAliasCreation, callback @@ -162,7 +192,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'Success'); }; - InternalCognitoUser.prototype.completeNewPasswordChallenge = ( + CognitoUser.prototype.completeNewPasswordChallenge = ( password, requiredAttributes, callback @@ -170,51 +200,42 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.updateAttributes = ( - attributeList, - callback - ) => { + CognitoUser.prototype.updateAttributes = (attributeList, callback) => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.deleteAttributes = ( - attributeList, - callback - ) => { + CognitoUser.prototype.deleteAttributes = (attributeList, callback) => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.deleteUser = (callback, {}) => { + CognitoUser.prototype.deleteUser = (callback, {}) => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.setAuthenticationFlowType = type => {}; + CognitoUser.prototype.setAuthenticationFlowType = type => {}; - InternalCognitoUser.prototype.initiateAuth = ( - authenticationDetails, - callback - ) => { + CognitoUser.prototype.initiateAuth = (authenticationDetails, callback) => { callback.customChallenge('challengeParam'); }; - InternalCognitoUser.prototype.sendCustomChallengeAnswer = ( + CognitoUser.prototype.sendCustomChallengeAnswer = ( challengeAnswer, callback ) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.refreshSession = (refreshToken, callback) => { + CognitoUser.prototype.refreshSession = (refreshToken, callback) => { callback(null, 'session'); }; - InternalCognitoUser.prototype.getUsername = () => { + CognitoUser.prototype.getUsername = () => { return 'username'; }; - InternalCognitoUser.prototype.getUserData = callback => { + CognitoUser.prototype.getUserData = callback => { callback(null, 'data'); }; - InternalCognitoUser.prototype.setUserMfaPreference = ( + CognitoUser.prototype.setUserMfaPreference = ( smsMfaSettings, softwareTokenMfaSettings, callback @@ -222,69 +243,23 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'success'); }; - InternalCognitoUser.prototype.getCachedDeviceKeyAndPassword = () => { + CognitoUser.prototype.getCachedDeviceKeyAndPassword = () => { return 'success'; }; - InternalCognitoUser.prototype.setDeviceStatusRemembered = callback => { + CognitoUser.prototype.setDeviceStatusRemembered = callback => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.forgetDevice = callback => { + CognitoUser.prototype.forgetDevice = callback => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.listDevices = ( - limit, - paginationToken, - callback - ) => { + CognitoUser.prototype.listDevices = (limit, paginationToken, callback) => { callback.onSuccess('success'); }; - // prettier-ignore - InternalCognitoUser.prototype.getSignInUserSession = function() { + CognitoUser.prototype.getSignInUserSession = function() { return this.signInUserSession; }; - const InternalCognitoUserPool = () => {}; - - InternalCognitoUserPool.prototype.InternalCognitoUserPool = options => { - InternalCognitoUserPool.prototype.options = options; - return InternalCognitoUserPool; - }; - - InternalCognitoUserPool.prototype.getCurrentUser = () => { - return { - username: 'username', - attributes: { email: 'test@test.com' }, - getSession: callback => { - // throw 3; - callback(null, { - getAccessToken: () => { - return { - decodePayload: () => 'payload', - getJwtToken: () => 'jwt', - }; - }, - }); - }, - }; - }; - - InternalCognitoUserPool.prototype.signUp = ( - username, - password, - signUpAttributeList, - validationData, - callback, - clientMetadata, - customUserAgentDetails? - ) => { - callback(null, 'signUpResult'); - }; - - return { - ...jest.requireActual('amazon-cognito-identity-js/internals'), - InternalCognitoUser, - InternalCognitoUserPool, - }; + return CognitoUser; }); const createMockLocalStorage = () => @@ -306,12 +281,11 @@ const createMockLocalStorage = () => import { AuthOptions, SignUpParams, AwsCognitoOAuthOpts } from '../src/types'; import { AuthClass as Auth } from '../src/Auth'; -import { InternalAuthClass } from '../src/internals/InternalAuth'; -import { AuthAction, Credentials, StorageHelper, Hub } from '@aws-amplify/core'; +import { Credentials, StorageHelper, Hub } from '@aws-amplify/core'; import { AuthError, NoUserPoolError } from '../src/Errors'; import { AuthErrorTypes } from '../src/types/Auth'; import { mockDeviceArray, transformedMockData } from './mockData'; -import { getAuthUserAgentDetails, getAuthUserAgentValue } from '../src/utils'; +import { InternalAuthClass } from '../src/internals/InternalAuth'; const authOptions: AuthOptions = { userPoolId: 'awsUserPoolsId', @@ -368,7 +342,7 @@ const authOptionsWithNoUserPoolId: AuthOptions = { mandatorySignIn: false, }; -const userPool = new InternalCognitoUserPool({ +const userPool = new CognitoUserPool({ UserPoolId: authOptions.userPoolId, ClientId: authOptions.userPoolWebClientId, }); @@ -404,7 +378,7 @@ const USER_ADMIN_SCOPE = 'aws.cognito.signin.user.admin'; describe('auth unit test', () => { describe('signUp', () => { test('happy case with object attr', async () => { - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); const auth = new Auth(authOptions); const attrs = { @@ -422,7 +396,7 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); const auth = new Auth(authOptionsWithClientMetadata); const attrs = { @@ -436,7 +410,7 @@ describe('auth unit test', () => { }; await auth.signUp(attrs); - expect(await InternalCognitoUserPool.prototype.signUp).toBeCalledWith( + expect(await CognitoUserPool.prototype.signUp).toBeCalledWith( attrs.username, attrs.password, [ @@ -446,14 +420,13 @@ describe('auth unit test', () => { ], null, jasmine.any(Function), - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.SignUp) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); const auth = new Auth(authOptionsWithClientMetadata); const attrs = { @@ -470,7 +443,7 @@ describe('auth unit test', () => { }; await auth.signUp(attrs); - expect(await InternalCognitoUserPool.prototype.signUp).toBeCalledWith( + expect(await CognitoUserPool.prototype.signUp).toBeCalledWith( attrs.username, attrs.password, [ @@ -480,8 +453,7 @@ describe('auth unit test', () => { ], null, jasmine.any(Function), - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.SignUp) + { custom: 'value' } ); spyon.mockClear(); }); @@ -504,7 +476,7 @@ describe('auth unit test', () => { test('callback error', async () => { const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'signUp') + .spyOn(CognitoUserPool.prototype, 'signUp') .mockImplementationOnce( ( username, @@ -600,7 +572,7 @@ describe('auth unit test', () => { describe('autoSignInAfterSignUp', () => { test('happy case auto confirm', async () => { const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'signUp') + .spyOn(CognitoUserPool.prototype, 'signUp') .mockImplementationOnce( ( username, @@ -613,10 +585,7 @@ describe('auth unit test', () => { callback(null, signUpResult); } ); - const signInSpyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const signInSpyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptions); const attrs = { username: 'username', @@ -635,15 +604,12 @@ describe('auth unit test', () => { }); test('happy case confirmation code', async () => { - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); const confirmSpyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'confirmRegistration' ); - const signInSpyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const signInSpyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptions); const attrs = { username: 'username', @@ -665,11 +631,8 @@ describe('auth unit test', () => { test('happy case confirmation link', async () => { jest.useFakeTimers(); - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); - const signInSpyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); + const signInSpyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptionConfirmationLink); const attrs = { username: 'username', @@ -689,18 +652,15 @@ describe('auth unit test', () => { }); test('fail confirmation code', async () => { - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); const confirmSpyon = jest - .spyOn(InternalCognitoUser.prototype, 'confirmRegistration') + .spyOn(CognitoUser.prototype, 'confirmRegistration') .mockImplementationOnce( (confirmationCode, forceAliasCreation, callback) => { callback('err', null); } ); - const signInSpyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const signInSpyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptions); const attrs = { username: 'username', @@ -727,10 +687,7 @@ describe('auth unit test', () => { describe('confirmSignUp', () => { test('happy case', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'confirmRegistration' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'confirmRegistration'); const auth = new Auth(authOptions); expect.assertions(1); @@ -740,10 +697,7 @@ describe('auth unit test', () => { }); test('with options', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'confirmRegistration' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'confirmRegistration'); const auth = new Auth(authOptions); expect.assertions(1); @@ -757,34 +711,25 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'confirmRegistration' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'confirmRegistration'); const auth = new Auth(authOptionsWithClientMetadata); const code = 'code'; await auth.confirmSignUp('username', code); - expect( - await InternalCognitoUser.prototype.confirmRegistration - ).toBeCalledWith( + expect(await CognitoUser.prototype.confirmRegistration).toBeCalledWith( code, jasmine.any(Boolean), jasmine.any(Function), { foo: 'bar', - }, - getAuthUserAgentValue(AuthAction.ConfirmSignUp) + } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'confirmRegistration' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'confirmRegistration'); const auth = new Auth(authOptionsWithClientMetadata); const code = 'code'; @@ -792,23 +737,20 @@ describe('auth unit test', () => { clientMetadata: { custom: 'value' }, }); - expect( - await InternalCognitoUser.prototype.confirmRegistration - ).toBeCalledWith( + expect(await CognitoUser.prototype.confirmRegistration).toBeCalledWith( code, jasmine.any(Boolean), jasmine.any(Function), { custom: 'value', - }, - getAuthUserAgentValue(AuthAction.ConfirmSignUp) + } ); spyon.mockClear(); }); test('callback err', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'confirmRegistration') + .spyOn(CognitoUser.prototype, 'confirmRegistration') .mockImplementationOnce( (confirmationCode, forceAliasCreation, callback) => { callback('err', null); @@ -871,10 +813,7 @@ describe('auth unit test', () => { describe('resendSignUp', () => { test('happy case', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'resendConfirmationCode' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'resendConfirmationCode'); const auth = new Auth(authOptions); expect.assertions(1); @@ -890,46 +829,34 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'resendConfirmationCode' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'resendConfirmationCode'); const auth = new Auth(authOptionsWithClientMetadata); await auth.resendSignUp('username'); - expect( - await InternalCognitoUser.prototype.resendConfirmationCode - ).toBeCalledWith( + expect(await CognitoUser.prototype.resendConfirmationCode).toBeCalledWith( jasmine.any(Function), - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.ResendSignUp) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'resendConfirmationCode' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'resendConfirmationCode'); const auth = new Auth(authOptionsWithClientMetadata); await auth.resendSignUp('username', { custom: 'value' }); - expect( - await InternalCognitoUser.prototype.resendConfirmationCode - ).toBeCalledWith( + expect(await CognitoUser.prototype.resendConfirmationCode).toBeCalledWith( jasmine.any(Function), - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.ResendSignUp) + { custom: 'value' } ); spyon.mockClear(); }); test('callback err', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'resendConfirmationCode') + .spyOn(CognitoUser.prototype, 'resendConfirmationCode') .mockImplementationOnce(callback => { callback(new Error('err'), null); }); @@ -1025,19 +952,19 @@ describe('auth unit test', () => { describe('signIn', () => { test('happy case with password', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.onSuccess(session); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); @@ -1051,17 +978,12 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptionsWithClientMetadata); await auth.signIn('username', 'password'); - expect( - await InternalCognitoUser.prototype.authenticateUser - ).toBeCalledWith( + expect(await CognitoUser.prototype.authenticateUser).toBeCalledWith( { username: 'username', password: 'password', @@ -1069,24 +991,18 @@ describe('auth unit test', () => { clientMetadata: { foo: 'bar' }, authParameters: {}, }, - authCallbacks, - getAuthUserAgentValue(AuthAction.SignIn) + authCallbacks ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptionsWithClientMetadata); await auth.signIn('username', 'password', { custom: 'value' }); - expect( - await InternalCognitoUser.prototype.authenticateUser - ).toBeCalledWith( + expect(await CognitoUser.prototype.authenticateUser).toBeCalledWith( { username: 'username', password: 'password', @@ -1094,14 +1010,13 @@ describe('auth unit test', () => { clientMetadata: { custom: 'value' }, authParameters: {}, }, - authCallbacks, - getAuthUserAgentValue(AuthAction.SignIn) + authCallbacks ); spyon.mockClear(); }); test('happy case validationData parameter', async () => { - const spyon = jest.spyOn(InternalCognitoUserPool.prototype, 'signUp'); + const spyon = jest.spyOn(CognitoUserPool.prototype, 'signUp'); const auth = new Auth(authOptionsWithClientMetadata); const attrs: SignUpParams = { @@ -1135,27 +1050,26 @@ describe('auth unit test', () => { { Name: 'test', Value: '123' }, ], jasmine.any(Function), - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.SignUp) + { custom: 'value' } ); spyon.mockClear(); }); test('throw error if failed to call currentUserPoolUser after signing in', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.onSuccess(session); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.reject('User is disabled.'); }); @@ -1173,7 +1087,7 @@ describe('auth unit test', () => { test('happy case using cookie storage', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((_authenticationDetails, callback) => { callback.onSuccess(session); }); @@ -1182,14 +1096,14 @@ describe('auth unit test', () => { ...authOptions, cookieStorage: { domain: '.example.com' }, }); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, Storage: new CookieStorage({ domain: '.yourdomain.com' }), }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); @@ -1203,7 +1117,7 @@ describe('auth unit test', () => { test('onFailure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.onFailure('err'); }); @@ -1222,12 +1136,12 @@ describe('auth unit test', () => { test('mfaRequired', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.mfaRequired('SELECT_MFA_TYPE', 'challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1246,12 +1160,12 @@ describe('auth unit test', () => { test('mfaSetup', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.mfaSetup('MFA_SETUP', 'challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1270,12 +1184,12 @@ describe('auth unit test', () => { test('totpRequired', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.totpRequired('SOFTWARE_TOKEN_MFA', 'challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1294,12 +1208,12 @@ describe('auth unit test', () => { test('selectMFAType', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.selectMFAType('SELECT_MFA_TYPE', 'challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1318,12 +1232,12 @@ describe('auth unit test', () => { test('newPasswordRequired', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.newPasswordRequired('userAttributes', 'requiredAttributes'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1345,18 +1259,15 @@ describe('auth unit test', () => { test('customChallenge', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'authenticateUser') + .spyOn(CognitoUser.prototype, 'authenticateUser') .mockImplementationOnce((authenticationDetails, callback) => { callback.customChallenge('challengeParam'); }); const spyon2 = jest - .spyOn( - InternalCognitoUser.prototype as any, - 'setAuthenticationFlowType' - ) + .spyOn(CognitoUser.prototype as any, 'setAuthenticationFlowType') .mockImplementationOnce(type => {}); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1374,10 +1285,7 @@ describe('auth unit test', () => { }); test('no userPool', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); // @ts-ignore const auth = new Auth(authOptionsWithNoUserPoolId); @@ -1393,10 +1301,7 @@ describe('auth unit test', () => { }); test('no username', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'authenticateUser' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'authenticateUser'); const auth = new Auth(authOptions); expect.assertions(1); @@ -1413,12 +1318,12 @@ describe('auth unit test', () => { describe('confirmSignIn', () => { test('happy case', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendMFACode') + .spyOn(CognitoUser.prototype, 'sendMFACode') .mockImplementationOnce((code, callback) => { callback.onSuccess(session); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1431,13 +1336,13 @@ describe('auth unit test', () => { test('happy case attributes are appended', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendMFACode') + .spyOn(CognitoUser.prototype, 'sendMFACode') .mockImplementationOnce((code, callback) => { callback.onSuccess(session); }); const hubSpy = jest.spyOn(Hub, 'dispatch'); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1458,9 +1363,9 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'sendMFACode'); + const spyon = jest.spyOn(CognitoUser.prototype, 'sendMFACode'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1468,23 +1373,22 @@ describe('auth unit test', () => { await auth.confirmSignIn(user, code); - expect(await InternalCognitoUser.prototype.sendMFACode).toBeCalledWith( + expect(await CognitoUser.prototype.sendMFACode).toBeCalledWith( code, { onSuccess: jasmine.any(Function), onFailure: jasmine.any(Function), }, undefined, - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.ConfirmSignIn) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'sendMFACode'); + const spyon = jest.spyOn(CognitoUser.prototype, 'sendMFACode'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1492,15 +1396,14 @@ describe('auth unit test', () => { await auth.confirmSignIn(user, code, 'SMS_MFA', { custom: 'value' }); - expect(await InternalCognitoUser.prototype.sendMFACode).toBeCalledWith( + expect(await CognitoUser.prototype.sendMFACode).toBeCalledWith( code, { onSuccess: jasmine.any(Function), onFailure: jasmine.any(Function), }, 'SMS_MFA', - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.ConfirmSignIn) + { custom: 'value' } ); spyon.mockClear(); }); @@ -1508,18 +1411,18 @@ describe('auth unit test', () => { test('currentUserPoolUser fails but hub event still dispatches', async () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendMFACode') + .spyOn(CognitoUser.prototype, 'sendMFACode') .mockImplementationOnce((code, callback) => { callback.onSuccess(session); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.reject('Could not get current user.'); }); const hubSpy = jest.spyOn(Hub, 'dispatch'); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1540,12 +1443,12 @@ describe('auth unit test', () => { test('onFailure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendMFACode') + .spyOn(CognitoUser.prototype, 'sendMFACode') .mockImplementationOnce((code, callback) => { callback.onFailure('err'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1560,10 +1463,10 @@ describe('auth unit test', () => { }); test('no code', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'sendMFACode'); + const spyon = jest.spyOn(CognitoUser.prototype, 'sendMFACode'); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1582,13 +1485,13 @@ describe('auth unit test', () => { describe('completeNewPassword', () => { test('happy case', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'completeNewPasswordChallenge') + .spyOn(CognitoUser.prototype, 'completeNewPasswordChallenge') .mockImplementationOnce((password, requiredAttributes, callback) => { callback.onSuccess(session); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1603,11 +1506,11 @@ describe('auth unit test', () => { test('happy case clientMetadata default', async () => { const spyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'completeNewPasswordChallenge' ); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1615,7 +1518,7 @@ describe('auth unit test', () => { await auth.completeNewPassword(user, 'password', {}); expect( - await InternalCognitoUser.prototype.completeNewPasswordChallenge + await CognitoUser.prototype.completeNewPasswordChallenge ).toBeCalledWith( 'password', {}, @@ -1626,19 +1529,18 @@ describe('auth unit test', () => { mfaSetup: jasmine.any(Function), totpRequired: jasmine.any(Function), }, - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.CompleteNewPassword) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata default', async () => { const spyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'completeNewPasswordChallenge' ); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1646,7 +1548,7 @@ describe('auth unit test', () => { await auth.completeNewPassword(user, 'password', {}, { custom: 'value' }); expect( - await InternalCognitoUser.prototype.completeNewPasswordChallenge + await CognitoUser.prototype.completeNewPasswordChallenge ).toBeCalledWith( 'password', {}, @@ -1657,21 +1559,20 @@ describe('auth unit test', () => { mfaSetup: jasmine.any(Function), totpRequired: jasmine.any(Function), }, - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.CompleteNewPassword) + { custom: 'value' } ); spyon.mockClear(); }); test('on Failure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'completeNewPasswordChallenge') + .spyOn(CognitoUser.prototype, 'completeNewPasswordChallenge') .mockImplementationOnce((password, requiredAttributes, callback) => { callback.onFailure('err'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1688,13 +1589,13 @@ describe('auth unit test', () => { test('mfaRequired', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'completeNewPasswordChallenge') + .spyOn(CognitoUser.prototype, 'completeNewPasswordChallenge') .mockImplementationOnce((password, requiredAttributes, callback) => { callback.mfaRequired('SMS_MFA', 'challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1707,13 +1608,13 @@ describe('auth unit test', () => { test('mfaSetup', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'completeNewPasswordChallenge') + .spyOn(CognitoUser.prototype, 'completeNewPasswordChallenge') .mockImplementationOnce((password, requiredAttributes, callback) => { callback.mfaSetup('MFA_SETUP', 'challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1726,7 +1627,7 @@ describe('auth unit test', () => { test('no password', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1746,20 +1647,17 @@ describe('auth unit test', () => { describe('userAttributes', () => { test('happy case', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(InternalAuthClass.prototype, 'userSession') .mockImplementationOnce(user => { return new Promise((res: any, rej) => { res('session'); }); }); - const spyon2 = jest.spyOn( - InternalCognitoUser.prototype, - 'getUserAttributes' - ); + const spyon2 = jest.spyOn(CognitoUser.prototype, 'getUserAttributes'); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1773,7 +1671,7 @@ describe('auth unit test', () => { test('get userattributes failed', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(InternalAuthClass.prototype, 'userSession') .mockImplementationOnce(user => { return new Promise((res: any, rej) => { res('session'); @@ -1781,13 +1679,13 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalCognitoUser.prototype, 'getUserAttributes') + .spyOn(CognitoUser.prototype, 'getUserAttributes') .mockImplementationOnce(callback => { callback(new Error('err')); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1811,19 +1709,19 @@ describe('auth unit test', () => { }); test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(InternalAuthClass.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(session); @@ -1839,19 +1737,19 @@ describe('auth unit test', () => { test('no current session', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(auth, 'userSession') .mockImplementationOnce(() => { return Promise.reject('cannot get the session'); }); @@ -1871,7 +1769,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.reject('no current user'); }); @@ -1906,13 +1804,13 @@ describe('auth unit test', () => { describe('currentAuthenticatedUser', () => { test('happy case with source userpool', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -1943,7 +1841,7 @@ describe('auth unit test', () => { }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1961,13 +1859,13 @@ describe('auth unit test', () => { describe('userSession test', () => { test('happy case', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementationOnce((callback: any) => { callback(null, session); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -1981,21 +1879,17 @@ describe('auth unit test', () => { test('debouncer happy case', async () => { const concurrency = 10; const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') - - .mockImplementationOnce( - // prettier-ignore - function(callback: any) { + .spyOn(CognitoUser.prototype, 'getSession') + .mockImplementationOnce(function(callback: any) { this.signInUserSession = session; callback(null, session); - } - ); + }); expect.assertions(2 * concurrency + 1); const auth = new Auth(authOptions); const promiseArr = Array.from({ length: concurrency }, async () => { - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2013,13 +1907,13 @@ describe('auth unit test', () => { test('callback error', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementationOnce((callback: any) => { callback('err', null); }); @@ -2038,14 +1932,14 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementationOnce((callback: any) => { callback('err', null); }); expect.assertions(2); try { const promiseArr = Array.from({ length: 10 }, async () => { - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2074,12 +1968,12 @@ describe('auth unit test', () => { test('refresh token revoked case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => user); const getSessionSpy = jest .spyOn(user, 'getSession') @@ -2110,16 +2004,16 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const credentialsClearSpy = jest.spyOn(Credentials, 'clear'); const hubSpy = jest.spyOn(Hub, 'dispatch'); - let user: InternalCognitoUser | null = null; + let user: CognitoUser | null = null; const getSessionSpy = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementationOnce((callback: any) => { callback(new Error('Refresh Token has been revoked'), null); }); const userSignoutSpy = jest.fn(); expect.assertions(5); const promiseArr = Array.from({ length: 10 }, async () => { - user = new InternalCognitoUser({ + user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2194,7 +2088,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentSession') + .spyOn(auth, 'currentSession') .mockImplementationOnce(() => { return Promise.resolve('session' as any); }); @@ -2228,7 +2122,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentSession') + .spyOn(auth, 'currentSession') .mockImplementationOnce(() => { return Promise.reject('err' as any); }); @@ -2262,7 +2156,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentSession') + .spyOn(auth, 'currentSession') .mockImplementationOnce(() => { return Promise.resolve('session') as any; }); @@ -2297,12 +2191,12 @@ describe('auth unit test', () => { describe('verifyUserAttribute test', () => { test('happy case', async () => { const spyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'getAttributeVerificationCode' ); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2316,13 +2210,13 @@ describe('auth unit test', () => { test('onFailure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getAttributeVerificationCode') + .spyOn(CognitoUser.prototype, 'getAttributeVerificationCode') .mockImplementationOnce((attr, callback) => { callback.onFailure('err' as any); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2340,13 +2234,10 @@ describe('auth unit test', () => { describe('verifyUserAttributeSubmit', () => { test('happy case', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'verifyAttribute' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'verifyAttribute'); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2361,13 +2252,13 @@ describe('auth unit test', () => { test('onFailure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'verifyAttribute') + .spyOn(CognitoUser.prototype, 'verifyAttribute') .mockImplementationOnce((attr, code, callback) => { callback.onFailure('err' as any); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2384,7 +2275,7 @@ describe('auth unit test', () => { test('code empty', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2403,13 +2294,13 @@ describe('auth unit test', () => { describe('verifyCurrentUserAttribute test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2417,7 +2308,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_verifyUserAttribute') + .spyOn(InternalAuthClass.prototype, 'verifyUserAttribute') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(); @@ -2427,17 +2318,8 @@ describe('auth unit test', () => { await auth.verifyCurrentUserAttribute('attr'); expect.assertions(2); - console.log('??', spyon.mock.calls); - expect(spyon).toBeCalledWith( - undefined, - getAuthUserAgentDetails(AuthAction.VerifyCurrentUserAttribute) - ); - expect(spyon2).toBeCalledWith( - user, - 'attr', - undefined, - getAuthUserAgentDetails(AuthAction.VerifyCurrentUserAttribute) - ); + expect(spyon).toBeCalled(); + expect(spyon2).toBeCalledWith(user, 'attr', undefined); spyon.mockClear(); spyon2.mockClear(); @@ -2447,13 +2329,13 @@ describe('auth unit test', () => { describe('verifyCurrentUserAttributeSubmit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2461,7 +2343,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_verifyUserAttributeSubmit') + .spyOn(InternalAuthClass.prototype, 'verifyUserAttributeSubmit') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(); @@ -2471,16 +2353,8 @@ describe('auth unit test', () => { await auth.verifyCurrentUserAttributeSubmit('attr', 'code'); expect.assertions(2); - expect(spyon).toBeCalledWith( - undefined, - getAuthUserAgentDetails(AuthAction.VerifyCurrentUserAttributeSubmit) - ); - expect(spyon2).toBeCalledWith( - user, - 'attr', - 'code', - getAuthUserAgentDetails(AuthAction.VerifyCurrentUserAttributeSubmit) - ); + expect(spyon).toBeCalled(); + expect(spyon2).toBeCalledWith(user, 'attr', 'code'); spyon.mockClear(); spyon2.mockClear(); @@ -2503,7 +2377,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2514,7 +2388,7 @@ describe('auth unit test', () => { return Promise.resolve(); }); const spyon2 = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -2531,7 +2405,7 @@ describe('auth unit test', () => { test('happy case for source userpool', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2548,11 +2422,11 @@ describe('auth unit test', () => { }); }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); - const spyon2 = jest.spyOn(InternalCognitoUser.prototype, 'signOut'); + const spyon2 = jest.spyOn(CognitoUser.prototype, 'signOut'); // @ts-ignore await auth.signOut(); @@ -2567,7 +2441,7 @@ describe('auth unit test', () => { test('happy case for globalSignOut', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2578,11 +2452,11 @@ describe('auth unit test', () => { return Promise.resolve(); }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); - const spyon2 = jest.spyOn(InternalCognitoUser.prototype, 'globalSignOut'); + const spyon2 = jest.spyOn(CognitoUser.prototype, 'globalSignOut'); await auth.signOut({ global: true }); @@ -2605,7 +2479,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return null; }); @@ -2624,7 +2498,7 @@ describe('auth unit test', () => { describe('changePassword', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2632,7 +2506,7 @@ describe('auth unit test', () => { const newPassword = 'newPassword1.'; const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(InternalAuthClass.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(session); @@ -2648,9 +2522,9 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'changePassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'changePassword'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2659,22 +2533,21 @@ describe('auth unit test', () => { await auth.changePassword(user, oldPassword, newPassword); - expect(await InternalCognitoUser.prototype.changePassword).toBeCalledWith( + expect(await CognitoUser.prototype.changePassword).toBeCalledWith( oldPassword, newPassword, jasmine.any(Function), { foo: 'bar', - }, - getAuthUserAgentValue(AuthAction.ChangePassword) + } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'changePassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'changePassword'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -2685,14 +2558,13 @@ describe('auth unit test', () => { custom: 'value', }); - expect(await InternalCognitoUser.prototype.changePassword).toBeCalledWith( + expect(await CognitoUser.prototype.changePassword).toBeCalledWith( oldPassword, newPassword, jasmine.any(Function), { custom: 'value', - }, - getAuthUserAgentValue(AuthAction.ChangePassword) + } ); spyon.mockClear(); }); @@ -2700,7 +2572,7 @@ describe('auth unit test', () => { describe('forgotPassword', () => { test('happy case', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptions); @@ -2711,44 +2583,42 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptionsWithClientMetadata); await auth.forgotPassword('username'); - expect(await InternalCognitoUser.prototype.forgotPassword).toBeCalledWith( + expect(await CognitoUser.prototype.forgotPassword).toBeCalledWith( { inputVerificationCode: jasmine.any(Function), onFailure: jasmine.any(Function), onSuccess: jasmine.any(Function), }, - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.ForgotPassword) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptionsWithClientMetadata); await auth.forgotPassword('username', { custom: 'value' }); - expect(await InternalCognitoUser.prototype.forgotPassword).toBeCalledWith( + expect(await CognitoUser.prototype.forgotPassword).toBeCalledWith( { inputVerificationCode: jasmine.any(Function), onFailure: jasmine.any(Function), onSuccess: jasmine.any(Function), }, - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.ForgotPassword) + { custom: 'value' } ); spyon.mockClear(); }); test('onFailure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'forgotPassword') + .spyOn(CognitoUser.prototype, 'forgotPassword') .mockImplementationOnce(callback => { callback.onFailure(new Error('err')); }); @@ -2767,7 +2637,7 @@ describe('auth unit test', () => { test('inputVerificationCode', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'forgotPassword') + .spyOn(CognitoUser.prototype, 'forgotPassword') .mockImplementationOnce(callback => { callback.inputVerificationCode('data'); }); @@ -2781,7 +2651,7 @@ describe('auth unit test', () => { }); test('no user pool id', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptionsWithNoUserPoolId); const errorMessage = new NoUserPoolError( @@ -2800,7 +2670,7 @@ describe('auth unit test', () => { }); test('no username', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptions); @@ -2816,10 +2686,7 @@ describe('auth unit test', () => { describe('forgotPasswordSubmit', () => { test('happy case', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'confirmPassword' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'confirmPassword'); const auth = new Auth(authOptions); @@ -2832,10 +2699,7 @@ describe('auth unit test', () => { }); test('happy case', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'confirmPassword' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'confirmPassword'); const auth = new Auth(authOptions); @@ -2846,7 +2710,7 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptionsWithClientMetadata); const username = 'username'; const code = 'code'; @@ -2854,23 +2718,20 @@ describe('auth unit test', () => { await auth.forgotPasswordSubmit(username, code, password); - expect( - await InternalCognitoUser.prototype.confirmPassword - ).toBeCalledWith( + expect(await CognitoUser.prototype.confirmPassword).toBeCalledWith( code, password, { onFailure: jasmine.any(Function), onSuccess: jasmine.any(Function), }, - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.ForgotPasswordSubmit) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'forgotPassword'); + const spyon = jest.spyOn(CognitoUser.prototype, 'forgotPassword'); const auth = new Auth(authOptionsWithClientMetadata); const username = 'username'; const code = 'code'; @@ -2880,24 +2741,21 @@ describe('auth unit test', () => { custom: 'value', }); - expect( - await InternalCognitoUser.prototype.confirmPassword - ).toBeCalledWith( + expect(await CognitoUser.prototype.confirmPassword).toBeCalledWith( code, password, { onFailure: jasmine.any(Function), onSuccess: jasmine.any(Function), }, - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.ForgotPasswordSubmit) + { custom: 'value' } ); spyon.mockClear(); }); test('confirmPassword failed', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'confirmPassword') + .spyOn(CognitoUser.prototype, 'confirmPassword') .mockImplementationOnce((code, password, callback) => { callback.onFailure(new Error('err')); }); @@ -2972,13 +2830,13 @@ describe('auth unit test', () => { describe('currentUserInfo test', () => { test('happy case with aws or userpool source', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2986,7 +2844,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_userAttributes') + .spyOn(InternalAuthClass.prototype, 'userAttributes') .mockImplementationOnce(() => { auth['credentials'] = { IdentityPoolId: 'identityPoolId', @@ -3039,13 +2897,13 @@ describe('auth unit test', () => { test('return empty object if error happens', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res({ @@ -3055,7 +2913,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_userAttributes') + .spyOn(InternalAuthClass.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res, rej) => { rej('err'); @@ -3088,14 +2946,14 @@ describe('auth unit test', () => { test('no current userpool user', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); auth['credentials_source'] = 'aws'; const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(null); @@ -3117,7 +2975,7 @@ describe('auth unit test', () => { test('federated user', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3140,7 +2998,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3152,7 +3010,7 @@ describe('auth unit test', () => { }; const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(InternalAuthClass.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(session); @@ -3166,49 +3024,37 @@ describe('auth unit test', () => { }); test('happy case clientMetadata default', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'updateAttributes' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'updateAttributes'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); await auth.updateUserAttributes(user, {}); - expect( - await InternalCognitoUser.prototype.updateAttributes - ).toBeCalledWith( + expect(await CognitoUser.prototype.updateAttributes).toBeCalledWith( [], jasmine.any(Function), - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.UpdateUserAttributes) + { foo: 'bar' } ); spyon.mockClear(); }); test('happy case clientMetadata parameter', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'updateAttributes' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'updateAttributes'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); await auth.updateUserAttributes(user, {}, { custom: 'value' }); - expect( - await InternalCognitoUser.prototype.updateAttributes - ).toBeCalledWith( + expect(await CognitoUser.prototype.updateAttributes).toBeCalledWith( [], jasmine.any(Function), - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.UpdateUserAttributes) + { custom: 'value' } ); spyon.mockClear(); }); @@ -3216,14 +3062,14 @@ describe('auth unit test', () => { test('error hub event', async done => { expect.assertions(3); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'updateAttributes') + .spyOn(CognitoUser.prototype, 'updateAttributes') .mockImplementationOnce((attrs, callback: any) => { callback(new Error('Error'), null, null); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3266,13 +3112,13 @@ describe('auth unit test', () => { ], }; const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'updateAttributes') + .spyOn(CognitoUser.prototype, 'updateAttributes') .mockImplementationOnce((attrs, callback: any) => { callback(null, 'SUCCESS', codeDeliverDetailsResult); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3316,7 +3162,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3324,7 +3170,7 @@ describe('auth unit test', () => { const attributeNames = ['email', 'phone_number']; const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userSession') + .spyOn(InternalAuthClass.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise(res => { res(session); @@ -3340,24 +3186,18 @@ describe('auth unit test', () => { }); test('happy case to call with expected attributes', async () => { - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'deleteAttributes' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'deleteAttributes'); const auth = new Auth(authOptionsWithClientMetadata); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); await auth.deleteUserAttributes(user, ['email', 'phone_number']); - expect( - await InternalCognitoUser.prototype.deleteAttributes - ).toBeCalledWith( + expect(await CognitoUser.prototype.deleteAttributes).toBeCalledWith( ['email', 'phone_number'], - jasmine.any(Function), - getAuthUserAgentValue(AuthAction.DeleteUserAttributes) + jasmine.any(Function) ); spyon.mockClear(); }); @@ -3370,23 +3210,23 @@ describe('auth unit test', () => { beforeEach(() => { jest.clearAllMocks(); auth = new Auth(authOptions); - user = new InternalCognitoUser({ + user = new CognitoUser({ Username: 'raz', Pool: userPool, }); - userPool = new InternalCognitoUserPool({ + userPool = new CognitoUserPool({ UserPoolId: authOptions.userPoolId, ClientId: authOptions.userPoolWebClientId, }); }); test('Happy path should delete a user', async () => { const spy1 = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spy2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); @@ -3410,7 +3250,7 @@ describe('auth unit test', () => { test('no user should throw error', async () => { const spy1 = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return null; }); @@ -3425,12 +3265,12 @@ describe('auth unit test', () => { test('no session should throw error', async () => { const spy1 = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spy2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(new Error('no session'), null); }); @@ -3444,7 +3284,7 @@ describe('auth unit test', () => { test('getSession call fail should signout user', async () => { jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); @@ -3474,17 +3314,17 @@ describe('auth unit test', () => { test('cognito deleteUser call fails...', async () => { const spy1 = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spy2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); const spy3 = jest - .spyOn(InternalCognitoUser.prototype, 'deleteUser') + .spyOn(CognitoUser.prototype, 'deleteUser') .mockImplementationOnce((callback: any) => { return callback(new Error('Cognito deleteUser error'), null); }); @@ -3577,7 +3417,7 @@ describe('auth unit test', () => { return Promise.resolve('cred' as any); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentAuthenticatedUser') + .spyOn(InternalAuthClass.prototype, 'currentAuthenticatedUser') .mockImplementation(() => { if (!user) return Promise.reject('error'); else return Promise.resolve(user); @@ -3651,7 +3491,7 @@ describe('auth unit test', () => { return Promise.resolve('cred' as any); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentAuthenticatedUser') + .spyOn(InternalAuthClass.prototype, 'currentAuthenticatedUser') .mockImplementation(() => { if (!user) return Promise.reject('error'); else return Promise.resolve(user); @@ -3682,7 +3522,15 @@ describe('auth unit test', () => { throw new Error('no user logged in'); }); - jest.spyOn(StorageHelper.prototype, 'getStorage'); + jest + .spyOn(StorageHelper.prototype, 'getStorage') + .mockImplementation(() => { + return { + setItem() { + return null; + }, + }; + }); }); test('User Pools Code Flow', async () => { @@ -3741,7 +3589,7 @@ describe('auth unit test', () => { (oauthStorage.getState as jest.Mock).mockReturnValueOnce(state); await (auth as any)._handleAuthResponse(url); - expect(handleAuthResponseSpy).toHaveBeenCalledWith(url, undefined); + expect(handleAuthResponseSpy).toHaveBeenCalledWith(url); expect(replaceStateSpy).toHaveBeenCalledWith( {}, null, @@ -3806,7 +3654,7 @@ describe('auth unit test', () => { await (auth as any)._handleAuthResponse(url); - expect(handleAuthResponseSpy).toHaveBeenCalledWith(url, undefined); + expect(handleAuthResponseSpy).toHaveBeenCalledWith(url); expect(replaceStateSpy).toHaveBeenCalledWith( {}, null, @@ -3870,7 +3718,7 @@ describe('auth unit test', () => { }?code=${code}`; await (auth as any)._handleAuthResponse(url); - expect(handleAuthResponseSpy).toHaveBeenCalledWith(url, undefined); + expect(handleAuthResponseSpy).toHaveBeenCalledWith(url); expect(replaceStateSpy).toHaveBeenCalledWith( {}, null, @@ -3882,7 +3730,7 @@ describe('auth unit test', () => { describe('verifiedContact test', () => { test('happy case with unverified', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userAttributes') + .spyOn(InternalAuthClass.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res: any, rej) => { res([ @@ -3899,7 +3747,7 @@ describe('auth unit test', () => { }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3914,7 +3762,7 @@ describe('auth unit test', () => { test('happy case with verified', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userAttributes') + .spyOn(InternalAuthClass.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res: any, rej) => { res([ @@ -3939,7 +3787,7 @@ describe('auth unit test', () => { }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -3954,7 +3802,7 @@ describe('auth unit test', () => { test('happy case with verified as strings', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype as any, '_userAttributes') + .spyOn(InternalAuthClass.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res: any, rej) => { res([ @@ -3979,7 +3827,7 @@ describe('auth unit test', () => { }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4000,24 +3848,24 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spyon2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); const spyon3 = jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementationOnce((callback: any) => { const data = { PreferredMfaSetting: 'SMS', @@ -4051,13 +3899,13 @@ describe('auth unit test', () => { test('no current user', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return null; }); @@ -4073,7 +3921,7 @@ describe('auth unit test', () => { test('No userPool in config', async () => { const auth = new Auth(authOptionsWithNoUserPoolId); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4090,23 +3938,23 @@ describe('auth unit test', () => { test('get session error', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spyon2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback('err', null); }); - const spyon3 = jest.spyOn(InternalCognitoUser.prototype, 'getUserData'); + const spyon3 = jest.spyOn(CognitoUser.prototype, 'getUserData'); expect.assertions(2); try { @@ -4119,12 +3967,12 @@ describe('auth unit test', () => { test('get session error - refresh token revoked should signout user', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); @@ -4158,30 +4006,27 @@ describe('auth unit test', () => { .mockImplementation(createMockLocalStorage); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementationOnce((callback: any) => { callback(new Error('User is disabled.'), null); }); - const userSignoutSpy = jest.spyOn( - InternalCognitoUser.prototype, - 'signOut' - ); + const userSignoutSpy = jest.spyOn(CognitoUser.prototype, 'signOut'); jest .spyOn(CognitoUserSession.prototype, 'getAccessToken') @@ -4220,30 +4065,27 @@ describe('auth unit test', () => { }); const auth = new Auth(authOptionsWithHostedUIConfig); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementationOnce((callback: any) => { callback(new Error('User is disabled.'), null); }); - const userSignoutSpy = jest.spyOn( - InternalCognitoUser.prototype, - 'signOut' - ); + const userSignoutSpy = jest.spyOn(CognitoUser.prototype, 'signOut'); jest .spyOn(CognitoUserSession.prototype, 'getAccessToken') @@ -4264,23 +4106,23 @@ describe('auth unit test', () => { test('bypass the error if the user is not deleted or disabled', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spyon2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); const spyon3 = jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementationOnce((callback: any) => { callback( { @@ -4309,24 +4151,24 @@ describe('auth unit test', () => { test('directly return the user if no permission(scope) to get the user data', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const spyon = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => { return user; }); const spyon2 = jest - .spyOn(InternalCognitoUser.prototype, 'getSession') + .spyOn(CognitoUser.prototype, 'getSession') .mockImplementation((callback: any) => { return callback(null, session); }); const spyon3 = jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementationOnce((callback: any) => { const data = { PreferredMfaSetting: 'SMS', @@ -4356,17 +4198,17 @@ describe('auth unit test', () => { describe('sendCustomChallengeAnswer', () => { test('happy case', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendCustomChallengeAnswer') + .spyOn(CognitoUser.prototype, 'sendCustomChallengeAnswer') .mockImplementationOnce((challengeResponses, callback) => { callback.onSuccess(session); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const userAfterCustomChallengeAnswer = Object.assign( - new InternalCognitoUser({ + new CognitoUser({ Username: 'username', Pool: userPool, }), @@ -4377,7 +4219,7 @@ describe('auth unit test', () => { ); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); @@ -4398,15 +4240,15 @@ describe('auth unit test', () => { const auth = new Auth(authOptionsWithClientMetadata); const spyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'sendCustomChallengeAnswer' ); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4414,13 +4256,8 @@ describe('auth unit test', () => { await auth.sendCustomChallengeAnswer(user, 'answer'); expect( - await InternalCognitoUser.prototype.sendCustomChallengeAnswer - ).toBeCalledWith( - 'answer', - authCallbacks, - { foo: 'bar' }, - getAuthUserAgentValue(AuthAction.SendCustomChallengeAnswer) - ); + await CognitoUser.prototype.sendCustomChallengeAnswer + ).toBeCalledWith('answer', authCallbacks, { foo: 'bar' }); spyon.mockClear(); }); @@ -4428,15 +4265,15 @@ describe('auth unit test', () => { const auth = new Auth(authOptionsWithClientMetadata); const spyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'sendCustomChallengeAnswer' ); const spyon2 = jest - .spyOn(InternalAuthClass.prototype as any, '_currentUserPoolUser') + .spyOn(auth, 'currentUserPoolUser') .mockImplementationOnce(() => { return Promise.resolve(user); }); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4444,29 +4281,24 @@ describe('auth unit test', () => { await auth.sendCustomChallengeAnswer(user, 'answer', { custom: 'value' }); expect( - await InternalCognitoUser.prototype.sendCustomChallengeAnswer - ).toBeCalledWith( - 'answer', - authCallbacks, - { custom: 'value' }, - getAuthUserAgentValue(AuthAction.SendCustomChallengeAnswer) - ); + await CognitoUser.prototype.sendCustomChallengeAnswer + ).toBeCalledWith('answer', authCallbacks, { custom: 'value' }); spyon.mockClear(); }); test('customChallenge', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendCustomChallengeAnswer') + .spyOn(CognitoUser.prototype, 'sendCustomChallengeAnswer') .mockImplementationOnce((challengeResponses, callback) => { callback.customChallenge('challengeParam'); }); const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); const userAfterCustomChallengeAnswer = Object.assign( - new InternalCognitoUser({ + new CognitoUser({ Username: 'username', Pool: userPool, }), @@ -4489,14 +4321,14 @@ describe('auth unit test', () => { test('onFailure', async () => { const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'sendCustomChallengeAnswer') + .spyOn(CognitoUser.prototype, 'sendCustomChallengeAnswer') .mockImplementationOnce((challengeResponses, callback) => { callback.onFailure('err'); }); const auth = new Auth(authOptions); const userAfterCustomChallengeAnswer = Object.assign( - new InternalCognitoUser({ + new CognitoUser({ Username: 'username', Pool: userPool, }), @@ -4521,13 +4353,13 @@ describe('auth unit test', () => { test('no userPool', async () => { const spyon = jest.spyOn( - InternalCognitoUser.prototype, + CognitoUser.prototype, 'sendCustomChallengeAnswer' ); const auth = new Auth(authOptionsWithNoUserPoolId); const userAfterCustomChallengeAnswer = Object.assign( - new InternalCognitoUser({ + new CognitoUser({ Username: 'username', Pool: userPool, }), @@ -4566,7 +4398,6 @@ describe('auth unit test', () => { describe('Device Tracking', () => { test('remember device happy path', async () => { const auth = new Auth(authOptions); - const spyon = jest .spyOn(CognitoUserSession.prototype, 'getAccessToken') .mockImplementationOnce(() => { @@ -4580,7 +4411,7 @@ describe('auth unit test', () => { }); const spyOnCognito = jest - .spyOn(InternalCognitoUser.prototype, 'setDeviceStatusRemembered') + .spyOn(CognitoUser.prototype, 'setDeviceStatusRemembered') .mockImplementationOnce( (obj: { onSuccess: (success: string) => void; @@ -4614,7 +4445,7 @@ describe('auth unit test', () => { }); const spyOnCognito = jest - .spyOn(InternalCognitoUser.prototype, 'forgetDevice') + .spyOn(CognitoUser.prototype, 'forgetDevice') .mockImplementationOnce( (obj: { onSuccess: (success: string) => void; @@ -4649,7 +4480,7 @@ describe('auth unit test', () => { }); const spyOnCognito = jest - .spyOn(InternalCognitoUser.prototype, 'listDevices') + .spyOn(CognitoUser.prototype, 'listDevices') .mockImplementationOnce( ( MAX_DEVICES, @@ -4688,7 +4519,7 @@ describe('auth unit test', () => { }); const spyOnCognito = jest - .spyOn(InternalCognitoUser.prototype, 'listDevices') + .spyOn(CognitoUser.prototype, 'listDevices') .mockImplementationOnce( ( MAX_DEVICES, @@ -4729,7 +4560,7 @@ describe('auth unit test', () => { test('happy path', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4748,7 +4579,7 @@ describe('auth unit test', () => { test('should allow bypassCache', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4762,25 +4593,19 @@ describe('auth unit test', () => { }); const res = await auth.getPreferredMFA(user, { bypassCache: true }); expect(res).toEqual('SMS'); - expect(getUserDataSpy).toHaveBeenCalledWith( - expect.any(Function), - { - bypassCache: true, - }, - getAuthUserAgentValue(AuthAction.GetPreferredMFA) - ); + expect(getUserDataSpy).toHaveBeenCalledWith(expect.any(Function), { + bypassCache: true, + }); }); test('get user data error because user is deleted, disabled or token has been revoked', async () => { const auth = new Auth(authOptions); - console.log('mock class definition', InternalCognitoUser); - console.log('auth class def', Auth); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementation(() => user); const getUserDataSpy = jest .spyOn(user, 'getUserData') @@ -4796,13 +4621,9 @@ describe('auth unit test', () => { await expect( auth.getPreferredMFA(user, { bypassCache: true }) ).rejects.toThrow('Access Token has been revoked'); - expect(getUserDataSpy).toHaveBeenCalledWith( - expect.any(Function), - { - bypassCache: true, - }, - getAuthUserAgentValue(AuthAction.GetPreferredMFA) - ); + expect(getUserDataSpy).toHaveBeenCalledWith(expect.any(Function), { + bypassCache: true, + }); expect(userSignoutSpy).toHaveBeenCalledTimes(1); expect(credentialsClearSpy).toHaveBeenCalledTimes(1); expect(hubSpy).toHaveBeenCalledWith( @@ -4828,7 +4649,7 @@ describe('auth unit test', () => { it('happy path', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); @@ -4845,16 +4666,11 @@ describe('auth unit test', () => { expect(setUserMfaPreferenceSpy).toHaveBeenCalledWith( null, { Enabled: true, PreferredMfa: true }, - expect.any(Function), - getAuthUserAgentValue(AuthAction.SetPreferredMFA) - ); - expect(getUserDataSpy).toHaveBeenCalledWith( - expect.any(Function), - { - bypassCache: true, - }, - getAuthUserAgentValue(AuthAction.SetPreferredMFA) + expect.any(Function) ); + expect(getUserDataSpy).toHaveBeenCalledWith(expect.any(Function), { + bypassCache: true, + }); // once at the beginning, once after calling setUserMfaPreference expect(getUserDataSpy).toHaveBeenCalledTimes(2); expect(res).toStrictEqual('success'); @@ -4862,12 +4678,12 @@ describe('auth unit test', () => { test('get user data error because user is deleted, disabled or token has been revoked', async () => { const auth = new Auth(authOptions); - const user = new InternalCognitoUser({ + const user = new CognitoUser({ Username: 'username', Pool: userPool, }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => user); const getUserDataSpy = jest .spyOn(user, 'getUserData') @@ -4882,13 +4698,9 @@ describe('auth unit test', () => { await expect( auth.setPreferredMFA(user, 'SOFTWARE_TOKEN_MFA') ).rejects.toThrow('Access Token has been revoked'); - expect(getUserDataSpy).toHaveBeenCalledWith( - expect.any(Function), - { - bypassCache: true, - }, - getAuthUserAgentValue(AuthAction.SetPreferredMFA) - ); + expect(getUserDataSpy).toHaveBeenCalledWith(expect.any(Function), { + bypassCache: true, + }); expect(userSignoutSpy).toHaveBeenCalledTimes(1); expect(credentialsClearSpy).toHaveBeenCalledTimes(1); expect(hubSpy).toHaveBeenCalledWith( diff --git a/packages/auth/__tests__/hosted-ui.test.ts b/packages/auth/__tests__/hosted-ui.test.ts index eed9ea69ee7..cee6df74044 100644 --- a/packages/auth/__tests__/hosted-ui.test.ts +++ b/packages/auth/__tests__/hosted-ui.test.ts @@ -1,21 +1,20 @@ import { CognitoUser, + CognitoUserPool, CognitoUserSession, CognitoAccessToken, CognitoIdToken, } from 'amazon-cognito-identity-js'; -import { InternalCognitoUserPool } from 'amazon-cognito-identity-js/internals'; +jest.mock('amazon-cognito-identity-js/lib/CognitoUserPool', () => { + const CognitoUserPool = () => {}; -jest.mock('amazon-cognito-identity-js/internals', () => { - const InternalCognitoUserPool = () => {}; - - InternalCognitoUserPool.prototype.InternalCognitoUserPool = options => { - InternalCognitoUserPool.prototype.options = options; - return InternalCognitoUserPool; + CognitoUserPool.prototype.CognitoUserPool = options => { + CognitoUserPool.prototype.options = options; + return CognitoUserPool; }; - InternalCognitoUserPool.prototype.getCurrentUser = () => { + CognitoUserPool.prototype.getCurrentUser = () => { return { username: 'username', getSession: callback => { @@ -31,7 +30,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { }; }; - InternalCognitoUserPool.prototype.signUp = ( + CognitoUserPool.prototype.signUp = ( username, password, signUpAttributeList, @@ -42,10 +41,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'signUpResult'); }; - return { - ...jest.requireActual('amazon-cognito-identity-js/internals'), - InternalCognitoUserPool, - }; + return CognitoUserPool; }); jest.mock('amazon-cognito-identity-js/lib/CognitoUser', () => { @@ -184,7 +180,7 @@ const authOptionsWithOAuth: AuthOptions = { }, }; -const userPool = new InternalCognitoUserPool({ +const userPool = new CognitoUserPool({ UserPoolId: authOptionsWithOAuth.userPoolId, ClientId: authOptionsWithOAuth.userPoolWebClientId, }); @@ -213,7 +209,7 @@ describe('Hosted UI tests', () => { Pool: userPool, }); const spyonGetCurrentUser = jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -290,7 +286,7 @@ describe('Hosted UI tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -344,7 +340,7 @@ describe('Hosted UI tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -398,7 +394,7 @@ describe('Hosted UI tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -444,7 +440,7 @@ describe('Hosted UI tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -504,7 +500,7 @@ describe('Hosted UI tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); @@ -551,7 +547,7 @@ describe('Hosted UI tests', () => { }); jest - .spyOn(InternalCognitoUserPool.prototype, 'getCurrentUser') + .spyOn(CognitoUserPool.prototype, 'getCurrentUser') .mockImplementationOnce(() => { return user; }); diff --git a/packages/auth/__tests__/totp-unit-test.ts b/packages/auth/__tests__/totp-unit-test.ts index 20f8e119035..892de6a4587 100644 --- a/packages/auth/__tests__/totp-unit-test.ts +++ b/packages/auth/__tests__/totp-unit-test.ts @@ -34,19 +34,19 @@ jest.mock('amazon-cognito-identity-js/lib/CognitoIdToken', () => { return CognitoIdToken; }); -jest.mock('amazon-cognito-identity-js/internals', () => { - const InternalCognitoUserPool = () => {}; +jest.mock('amazon-cognito-identity-js/lib/CognitoUserPool', () => { + const CognitoUserPool = () => {}; - InternalCognitoUserPool.prototype.InternalCognitoUserPool = options => { - InternalCognitoUserPool.prototype.options = options; - return InternalCognitoUserPool; + CognitoUserPool.prototype.CognitoUserPool = options => { + CognitoUserPool.prototype.options = options; + return CognitoUserPool; }; - InternalCognitoUserPool.prototype.getCurrentUser = () => { + CognitoUserPool.prototype.getCurrentUser = () => { return 'currentUser'; }; - InternalCognitoUserPool.prototype.signUp = ( + CognitoUserPool.prototype.signUp = ( username, password, signUpAttributeList, @@ -56,49 +56,50 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'signUpResult'); }; - const InternalCognitoUser = () => {}; + return CognitoUserPool; +}); + +jest.mock('amazon-cognito-identity-js/lib/CognitoUser', () => { + const CognitoUser = () => {}; - InternalCognitoUser.prototype.InternalCognitoUser = options => { - InternalCognitoUser.prototype.options = options; - return InternalCognitoUser; + CognitoUser.prototype.CognitoUser = options => { + CognitoUser.prototype.options = options; + return CognitoUser; }; - InternalCognitoUser.prototype.getSession = callback => { + CognitoUser.prototype.getSession = callback => { // throw 3; callback(null, 'session'); }; - InternalCognitoUser.prototype.getUserAttributes = callback => { + CognitoUser.prototype.getUserAttributes = callback => { callback(null, 'attributes'); }; - InternalCognitoUser.prototype.getAttributeVerificationCode = ( - attr, - callback - ) => { + CognitoUser.prototype.getAttributeVerificationCode = (attr, callback) => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.verifyAttribute = (attr, code, callback) => { + CognitoUser.prototype.verifyAttribute = (attr, code, callback) => { callback.onSuccess('success'); }; - InternalCognitoUser.prototype.authenticateUser = ( + CognitoUser.prototype.authenticateUser = ( authenticationDetails, callback ) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.sendMFACode = (code, callback) => { + CognitoUser.prototype.sendMFACode = (code, callback) => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.resendConfirmationCode = callback => { + CognitoUser.prototype.resendConfirmationCode = callback => { callback(null, 'result'); }; - InternalCognitoUser.prototype.changePassword = ( + CognitoUser.prototype.changePassword = ( oldPassword, newPassword, callback @@ -106,21 +107,17 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.forgotPassword = callback => { + CognitoUser.prototype.forgotPassword = callback => { callback.onSuccess(); }; - InternalCognitoUser.prototype.confirmPassword = ( - code, - password, - callback - ) => { + CognitoUser.prototype.confirmPassword = (code, password, callback) => { callback.onSuccess(); }; - InternalCognitoUser.prototype.signOut = () => {}; + CognitoUser.prototype.signOut = () => {}; - InternalCognitoUser.prototype.confirmRegistration = ( + CognitoUser.prototype.confirmRegistration = ( confirmationCode, forceAliasCreation, callback @@ -128,7 +125,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'Success'); }; - InternalCognitoUser.prototype.completeNewPasswordChallenge = ( + CognitoUser.prototype.completeNewPasswordChallenge = ( password, requiredAttributes, callback @@ -136,30 +133,27 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback.onSuccess('session'); }; - InternalCognitoUser.prototype.updateAttributes = ( - attributeList, - callback - ) => { + CognitoUser.prototype.updateAttributes = (attributeList, callback) => { callback(null, 'SUCCESS'); }; - InternalCognitoUser.prototype.getMFAOptions = callback => { + CognitoUser.prototype.getMFAOptions = callback => { callback(null, 'mfaOptions'); }; - InternalCognitoUser.prototype.disableMFA = callback => { + CognitoUser.prototype.disableMFA = callback => { callback(null, 'Success'); }; - InternalCognitoUser.prototype.enableMFA = callback => { + CognitoUser.prototype.enableMFA = callback => { callback(null, 'Success'); }; - InternalCognitoUser.prototype.associateSoftwareToken = callback => { + CognitoUser.prototype.associateSoftwareToken = callback => { callback.associateSecretCode('secretCode'); }; - InternalCognitoUser.prototype.verifySoftwareToken = ( + CognitoUser.prototype.verifySoftwareToken = ( challengeAnswer, device, callback @@ -167,7 +161,7 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback.onSuccess('Success'); }; - InternalCognitoUser.prototype.setUserMfaPreference = ( + CognitoUser.prototype.setUserMfaPreference = ( smsMfaSettings, totpMfaSettings, callback @@ -175,38 +169,31 @@ jest.mock('amazon-cognito-identity-js/internals', () => { callback(null, 'Success'); }; - InternalCognitoUser.prototype.getUserData = callback => { + CognitoUser.prototype.getUserData = callback => { callback(null, { PreferredMfaSetting: 'SMS_MFA', }); }; - InternalCognitoUser.prototype.getUsername = () => { + CognitoUser.prototype.getUsername = () => { return 'testUsername'; }; - InternalCognitoUser.prototype.getSignInUserSession = () => { + CognitoUser.prototype.getSignInUserSession = () => { return session; }; - return { - ...jest.requireActual('amazon-cognito-identity-js/internals'), - InternalCognitoUser, - InternalCognitoUserPool, - }; + return CognitoUser; }); import { AuthClass as Auth } from '../src/Auth'; import { + CognitoUserPool, CognitoUser, CognitoUserSession, CognitoIdToken, CognitoAccessToken, } from 'amazon-cognito-identity-js'; -import { - InternalCognitoUser, - InternalCognitoUserPool, -} from 'amazon-cognito-identity-js/internals'; import { Hub } from '@aws-amplify/core'; import { InternalAuthClass } from '../src/internals/InternalAuth'; @@ -228,7 +215,7 @@ const authOptionsWithNoUserPoolId = { }, }; -const userPool = new InternalCognitoUserPool({ +const userPool = new CognitoUserPool({ UserPoolId: authOptions.Auth.userPoolId, ClientId: authOptions.Auth.userPoolWebClientId, }); @@ -251,7 +238,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'getMFAOptions'); + const spyon = jest.spyOn(CognitoUser.prototype, 'getMFAOptions'); expect(await auth.getMFAOptions(user)).toBe('mfaOptions'); expect(spyon).toBeCalled(); @@ -262,7 +249,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getMFAOptions') + .spyOn(CognitoUser.prototype, 'getMFAOptions') .mockImplementationOnce(callback => { callback(new Error('err'), null); }); @@ -279,7 +266,7 @@ describe('auth unit test', () => { describe('disableMFA test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'disableMFA'); + const spyon = jest.spyOn(CognitoUser.prototype, 'disableMFA'); expect(await auth.disableSMS(user)).toBe('Success'); expect(spyon).toBeCalled(); @@ -290,7 +277,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'disableMFA') + .spyOn(CognitoUser.prototype, 'disableMFA') .mockImplementationOnce(callback => { callback(new Error('err'), null); }); @@ -308,7 +295,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const spyon = jest.spyOn(InternalCognitoUser.prototype, 'enableMFA'); + const spyon = jest.spyOn(CognitoUser.prototype, 'enableMFA'); expect(await auth.enableSMS(user)).toBe('Success'); expect(spyon).toBeCalled(); @@ -319,7 +306,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'enableMFA') + .spyOn(CognitoUser.prototype, 'enableMFA') .mockImplementationOnce(callback => { callback(new Error('err'), null); }); @@ -337,10 +324,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'associateSoftwareToken' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'associateSoftwareToken'); expect(await auth.setupTOTP(user)).toBe('secretCode'); expect(spyon).toBeCalled(); @@ -351,7 +335,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'associateSoftwareToken') + .spyOn(CognitoUser.prototype, 'associateSoftwareToken') .mockImplementationOnce(callback => { callback.onFailure('err'); }); @@ -376,11 +360,8 @@ describe('auth unit test', () => { }); happyCaseUser.getSignInUserSession = () => null; - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'verifySoftwareToken' - ); - const spyon2 = jest.spyOn(InternalCognitoUser.prototype, 'getUsername'); + const spyon = jest.spyOn(CognitoUser.prototype, 'verifySoftwareToken'); + const spyon2 = jest.spyOn(CognitoUser.prototype, 'getUsername'); const hubSpy = jest.spyOn(Hub, 'dispatch'); expect(await auth.verifyTotpToken(happyCaseUser, 'challengeAnswer')).toBe( @@ -403,11 +384,8 @@ describe('auth unit test', () => { Username: 'username', Pool: userPool, }); - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'verifySoftwareToken' - ); - const spyon2 = jest.spyOn(InternalCognitoUser.prototype, 'getUsername'); + const spyon = jest.spyOn(CognitoUser.prototype, 'verifySoftwareToken'); + const spyon2 = jest.spyOn(CognitoUser.prototype, 'getUsername'); const hubSpy = jest.spyOn(Hub, 'dispatch'); expect(await auth.verifyTotpToken(happyCaseUser, 'challengeAnswer')).toBe( @@ -432,7 +410,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'verifySoftwareToken') + .spyOn(CognitoUser.prototype, 'verifySoftwareToken') .mockImplementationOnce((challengeAnswer, device, callback) => { callback.onFailure(new Error('err')); }); @@ -450,10 +428,7 @@ describe('auth unit test', () => { test('happy case', async () => { const auth = new Auth(authOptions); - const spyon = jest.spyOn( - InternalCognitoUser.prototype, - 'setUserMfaPreference' - ); + const spyon = jest.spyOn(CognitoUser.prototype, 'setUserMfaPreference'); const spyon2 = jest .spyOn(InternalAuthClass.prototype, 'getPreferredMFA') .mockImplementationOnce(() => { @@ -472,7 +447,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'setUserMfaPreference') + .spyOn(CognitoUser.prototype, 'setUserMfaPreference') .mockImplementationOnce((smsMfaSettings, totpMfaSettings, callback) => { const err = { message: 'User has not verified software token mfa', @@ -518,7 +493,7 @@ describe('auth unit test', () => { const auth = new Auth(authOptions); const spyon = jest - .spyOn(InternalCognitoUser.prototype, 'getUserData') + .spyOn(CognitoUser.prototype, 'getUserData') .mockImplementationOnce(callback => { callback(new Error('err'), null); }); diff --git a/packages/auth/package.json b/packages/auth/package.json index 06a2797d680..765ba285906 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -61,7 +61,7 @@ "name": "Auth (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Auth }", - "limit": "57.19 kB" + "limit": "56.38 kB" } ], "jest": { diff --git a/packages/auth/src/OAuth/OAuth.ts b/packages/auth/src/OAuth/OAuth.ts index a8458c25646..5bf25f6bdd0 100644 --- a/packages/auth/src/OAuth/OAuth.ts +++ b/packages/auth/src/OAuth/OAuth.ts @@ -117,7 +117,7 @@ export default class OAuth { this._urlOpener(URL, redirectSignIn); } - private async _handleCodeFlow(currentUrl: string, userAgentValue?: string) { + private async _handleCodeFlow(currentUrl: string) { /* Convert URL into an object with parameters as keys { redirect_uri: 'http://localhost:3000/', response_type: 'code', ...} */ const { code } = (parse(currentUrl).query || '') @@ -169,12 +169,17 @@ export default class OAuth { .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&'); + const customUserAgentDetails: CustomUserAgentDetails = { + category: Category.Auth, + action: AuthAction.FederatedSignIn, + }; + const { access_token, refresh_token, id_token, error } = await ( (await fetch(oAuthTokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', - [USER_AGENT_HEADER]: userAgentValue, + [USER_AGENT_HEADER]: getAmplifyUserAgent(customUserAgentDetails), }, body, })) as any @@ -212,10 +217,7 @@ export default class OAuth { }; } - public async handleAuthResponse( - currentUrl?: string, - userAgentValue?: string - ) { + public async handleAuthResponse(currentUrl?: string) { try { const urlParams = currentUrl ? ({ @@ -242,10 +244,7 @@ export default class OAuth { `Starting ${this._config.responseType} flow with ${currentUrl}` ); if (this._config.responseType === 'code') { - return { - ...(await this._handleCodeFlow(currentUrl, userAgentValue)), - state, - }; + return { ...(await this._handleCodeFlow(currentUrl)), state }; } else { return { ...(await this._handleImplicitFlow(currentUrl)), state }; } diff --git a/packages/auth/src/internals/InternalAuth.ts b/packages/auth/src/internals/InternalAuth.ts index 3584e5b78a4..b25bfc6a90e 100644 --- a/packages/auth/src/internals/InternalAuth.ts +++ b/packages/auth/src/internals/InternalAuth.ts @@ -25,11 +25,9 @@ import { import { Amplify, - AuthAction, ConsoleLogger as Logger, Credentials, CustomUserAgentDetails, - getAmplifyUserAgent, Hub, StorageHelper, ICredentials, @@ -42,6 +40,7 @@ import { } from '@aws-amplify/core'; import { CookieStorage, + CognitoUserPool, AuthenticationDetails, ICognitoUserPoolData, ICognitoUserData, @@ -61,8 +60,6 @@ import { import { addAuthCategoryToCognitoUserAgent, addFrameworkToCognitoUserAgent, - InternalCognitoUser, - InternalCognitoUserPool, } from 'amazon-cognito-identity-js/internals'; import { parse } from 'url'; @@ -75,7 +72,6 @@ import { CognitoHostedUIIdentityProvider, IAuthDevice, } from '../types/Auth'; -import { getAuthUserAgentDetails, getAuthUserAgentValue } from '../utils'; const logger = new Logger('AuthClass'); const USER_ADMIN_SCOPE = 'aws.cognito.signin.user.admin'; @@ -105,7 +101,7 @@ const MAX_AUTOSIGNIN_POLLING_MS = 3 * 60 * 1000; */ export class InternalAuthClass { private _config: AuthOptions; - private userPool: InternalCognitoUserPool = null; + private userPool: CognitoUserPool = null; private user: any = null; private _oAuthHandler: OAuth; private _storage; @@ -209,7 +205,7 @@ export class InternalAuthClass { }; userPoolData.Storage = this._storage; - this.userPool = new InternalCognitoUserPool( + this.userPool = new CognitoUserPool( userPoolData, this.wrapRefreshSessionCallback ); @@ -412,10 +408,6 @@ export class InternalAuthClass { logger.debug('signUp validation data:', validationData); return new Promise((resolve, reject) => { - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.SignUp, - customUserAgentDetails - ); this.userPool.signUp( username, password, @@ -441,15 +433,13 @@ export class InternalAuthClass { password, autoSignInValidationData, autoSignInClientMetaData, - data, - userAgentDetails + data ); } resolve(data); } }, - clientMetadata, - getAmplifyUserAgent(userAgentDetails) + clientMetadata ); }); } @@ -459,8 +449,7 @@ export class InternalAuthClass { password: string, validationData: {}, clientMetadata: any, - data: any, - customUserAgentDetails: CustomUserAgentDetails + data: any ) { this.autoSignInInitiated = true; const authDetails = new AuthenticationDetails({ @@ -470,34 +459,24 @@ export class InternalAuthClass { ClientMetadata: clientMetadata, }); if (data.userConfirmed) { - this.signInAfterUserConfirmed(authDetails, customUserAgentDetails); + this.signInAfterUserConfirmed(authDetails); } else if (this._config.signUpVerificationMethod === 'link') { - this.handleLinkAutoSignIn(authDetails, customUserAgentDetails); + this.handleLinkAutoSignIn(authDetails); } else { - this.handleCodeAutoSignIn(authDetails, customUserAgentDetails); + this.handleCodeAutoSignIn(authDetails); } } - private handleCodeAutoSignIn( - authDetails: AuthenticationDetails, - customUserAgentDetails: CustomUserAgentDetails - ) { + private handleCodeAutoSignIn(authDetails: AuthenticationDetails) { const listenEvent = ({ payload }) => { if (payload.event === 'confirmSignUp') { - this.signInAfterUserConfirmed( - authDetails, - customUserAgentDetails, - listenEvent - ); + this.signInAfterUserConfirmed(authDetails, listenEvent); } }; Hub.listen('auth', listenEvent); } - private handleLinkAutoSignIn( - authDetails: AuthenticationDetails, - customUserAgentDetails: CustomUserAgentDetails - ) { + private handleLinkAutoSignIn(authDetails: AuthenticationDetails) { this._storage.setItem('amplify-polling-started', 'true'); const start = Date.now(); const autoSignInPollingIntervalId = setInterval(() => { @@ -512,8 +491,7 @@ export class InternalAuthClass { } else { this.signInAfterUserConfirmed( authDetails, - customUserAgentDetails, - undefined, + null, autoSignInPollingIntervalId ); } @@ -522,7 +500,6 @@ export class InternalAuthClass { private async signInAfterUserConfirmed( authDetails: AuthenticationDetails, - customUserAgentDetails: CustomUserAgentDetails, listenEvent?: HubCallback, autoSignInPollingIntervalId?: ReturnType ) { @@ -550,10 +527,8 @@ export class InternalAuthClass { error => { logger.error(error); this._storage.removeItem('amplify-auto-sign-in'); - }, - customUserAgentDetails - ), - getAmplifyUserAgent(customUserAgentDetails) + } + ) ); } catch (error) { logger.error(error); @@ -621,8 +596,7 @@ export class InternalAuthClass { resolve(data); } }, - clientMetadata, - getAuthUserAgentValue(AuthAction.ConfirmSignUp, customUserAgentDetails) + clientMetadata ); }); } @@ -653,17 +627,13 @@ export class InternalAuthClass { const user = this.createCognitoUser(username); return new Promise((resolve, reject) => { - user.resendConfirmationCode( - (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }, - clientMetadata, - getAuthUserAgentValue(AuthAction.ResendSignUp, customUserAgentDetails) - ); + user.resendConfirmationCode((err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }, clientMetadata); }); } @@ -713,29 +683,24 @@ export class InternalAuthClass { ValidationData: validationData, ClientMetadata: clientMetadata, }); - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.SignIn, - customUserAgentDetails - ); if (password) { - return this.signInWithPassword(authDetails, userAgentDetails); + return this.signInWithPassword(authDetails); } else { - return this.signInWithoutPassword(authDetails, userAgentDetails); + return this.signInWithoutPassword(authDetails); } } /** * Return an object with the authentication callbacks - * @param {InternalCognitoUser} user - the cognito user object + * @param {CognitoUser} user - the cognito user object * @param {} resolve - function called when resolving the current step * @param {} reject - function called when rejecting the current step * @return - an object with the callback methods for user authentication */ private authCallbacks( - user: InternalCognitoUser, - resolve: (value?: InternalCognitoUser | any) => void, - reject: (value?: any) => void, - customUserAgentDetails: CustomUserAgentDetails + user: CognitoUser, + resolve: (value?: CognitoUser | any) => void, + reject: (value?: any) => void ): IAuthenticationCallback { const that = this; return { @@ -753,10 +718,7 @@ export class InternalAuthClass { try { // In order to get user attributes and MFA methods // We need to trigger currentUserPoolUser again - const currentUser = await this._currentUserPoolUser( - undefined, - customUserAgentDetails - ); + const currentUser = await this.currentUserPoolUser(); that.user = currentUser; dispatchAuthEvent( 'signIn', @@ -825,13 +787,11 @@ export class InternalAuthClass { * Sign in with a password * @private * @param {AuthenticationDetails} authDetails - the user sign in data - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @return - A promise resolves the CognitoUser object if success or mfa required */ private signInWithPassword( - authDetails: AuthenticationDetails, - customUserAgentDetails: CustomUserAgentDetails - ): Promise { + authDetails: AuthenticationDetails + ): Promise { if (this.pendingSignIn) { throw new Error('Pending sign-in attempt already in progress'); } @@ -850,10 +810,8 @@ export class InternalAuthClass { error => { this.pendingSignIn = null; reject(error); - }, - customUserAgentDetails - ), - getAmplifyUserAgent(customUserAgentDetails) + } + ) ); }); @@ -864,22 +822,16 @@ export class InternalAuthClass { * Sign in without a password * @private * @param {AuthenticationDetails} authDetails - the user sign in data - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves the InternalCognitoUser object if success or mfa required + * @return - A promise resolves the CognitoUser object if success or mfa required */ private signInWithoutPassword( - authDetails: AuthenticationDetails, - customUserAgentDetails: CustomUserAgentDetails - ): Promise { + authDetails: AuthenticationDetails + ): Promise { const user = this.createCognitoUser(authDetails.getUsername()); user.setAuthenticationFlowType('CUSTOM_AUTH'); return new Promise((resolve, reject) => { - user.initiateAuth( - authDetails, - this.authCallbacks(user, resolve, reject, customUserAgentDetails), - getAmplifyUserAgent(customUserAgentDetails) - ); + user.initiateAuth(authDetails, this.authCallbacks(user, resolve, reject)); }); } @@ -896,10 +848,8 @@ export class InternalAuthClass { user: CognitoUser | any, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; - return new Promise((res, rej) => { - internalUser.getMFAOptions((err, mfaOptions) => { + user.getMFAOptions((err, mfaOptions) => { if (err) { logger.debug('get MFA Options failed', err); rej(err); @@ -908,7 +858,7 @@ export class InternalAuthClass { logger.debug('get MFA options success', mfaOptions); res(mfaOptions); return; - }, getAuthUserAgentValue(AuthAction.GetMFAOptions, customUserAgentDetails)); + }); }); } @@ -923,23 +873,18 @@ export class InternalAuthClass { params?: GetPreferredMFAOpts, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; const that = this; return new Promise((res, rej) => { const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn const bypassCache = params ? params.bypassCache : false; - const userAgentValue = getAuthUserAgentValue( - AuthAction.GetPreferredMFA, - customUserAgentDetails - ); - internalUser.getUserData( + user.getUserData( async (err, data) => { if (err) { logger.debug('getting preferred mfa failed', err); if (this.isSessionInvalid(err)) { try { - await this.cleanUpInvalidSession(user, userAgentValue); + await this.cleanUpInvalidSession(user); } catch (cleanUpError) { rej( new Error( @@ -962,8 +907,7 @@ export class InternalAuthClass { return; } }, - { bypassCache, clientMetadata }, - userAgentValue + { bypassCache, clientMetadata } ); }); } @@ -998,37 +942,29 @@ export class InternalAuthClass { return ret; } - private _getUserData( - user: InternalCognitoUser, - params, - userAgentValue: string - ) { + private _getUserData(user, params) { return new Promise((res, rej) => { - user.getUserData( - async (err, data) => { - if (err) { - logger.debug('getting user data failed', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user, userAgentValue); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } + user.getUserData(async (err, data) => { + if (err) { + logger.debug('getting user data failed', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; } - rej(err); - return; - } else { - res(data); } - }, - params, - userAgentValue - ); + rej(err); + return; + } else { + res(data); + } + }, params); }); } @@ -1044,21 +980,12 @@ export class InternalAuthClass { mfaMethod: 'TOTP' | 'SMS' | 'NOMFA' | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA', customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; - const userAgentValue = getAuthUserAgentValue( - AuthAction.SetPreferredMFA, - customUserAgentDetails - ); const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn - const userData = await this._getUserData( - user, - { - bypassCache: true, - clientMetadata, - }, - userAgentValue - ); + const userData = await this._getUserData(user, { + bypassCache: true, + clientMetadata, + }); let smsMfaSettings = null; let totpMfaSettings = null; @@ -1121,7 +1048,7 @@ export class InternalAuthClass { const that = this; return new Promise((res, rej) => { - internalUser.setUserMfaPreference( + user.setUserMfaPreference( smsMfaSettings, totpMfaSettings, (err, result) => { @@ -1132,13 +1059,13 @@ export class InternalAuthClass { logger.debug('Set user mfa success', result); logger.debug('Caching the latest user data into local'); // cache the latest result into user data - internalUser.getUserData( + user.getUserData( async (err, data) => { if (err) { logger.debug('getting user data failed', err); if (this.isSessionInvalid(err)) { try { - await this.cleanUpInvalidSession(user, userAgentValue); + await this.cleanUpInvalidSession(user); } catch (cleanUpError) { rej( new Error( @@ -1156,11 +1083,9 @@ export class InternalAuthClass { { bypassCache: true, clientMetadata, - }, - userAgentValue + } ); - }, - userAgentValue + } ); }); } @@ -1176,10 +1101,8 @@ export class InternalAuthClass { user: CognitoUser, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser = user as InternalCognitoUser; - return new Promise((res, rej) => { - internalUser.disableMFA((err, data) => { + user.disableMFA((err, data) => { if (err) { logger.debug('disable mfa failed', err); rej(err); @@ -1188,7 +1111,7 @@ export class InternalAuthClass { logger.debug('disable mfa succeed', data); res(data); return; - }, getAuthUserAgentValue(AuthAction.DisableSMS, customUserAgentDetails)); + }); }); } @@ -1203,10 +1126,8 @@ export class InternalAuthClass { user: CognitoUser, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser = user as InternalCognitoUser; - return new Promise((res, rej) => { - internalUser.enableMFA((err, data) => { + user.enableMFA((err, data) => { if (err) { logger.debug('enable mfa failed', err); rej(err); @@ -1215,7 +1136,7 @@ export class InternalAuthClass { logger.debug('enable mfa succeed', data); res(data); return; - }, getAuthUserAgentValue(AuthAction.EnableSMS, customUserAgentDetails)); + }); }); } @@ -1229,24 +1150,19 @@ export class InternalAuthClass { user: CognitoUser | any, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; - return new Promise((res, rej) => { - internalUser.associateSoftwareToken( - { - onFailure: err => { - logger.debug('associateSoftwareToken failed', err); - rej(err); - return; - }, - associateSecretCode: secretCode => { - logger.debug('associateSoftwareToken success', secretCode); - res(secretCode); - return; - }, + user.associateSoftwareToken({ + onFailure: err => { + logger.debug('associateSoftwareToken failed', err); + rej(err); + return; }, - getAuthUserAgentValue(AuthAction.SetupTOTP, customUserAgentDetails) - ); + associateSecretCode: secretCode => { + logger.debug('associateSoftwareToken success', secretCode); + res(secretCode); + return; + }, + }); }); } @@ -1263,50 +1179,38 @@ export class InternalAuthClass { customUserAgentDetails?: CustomUserAgentDetails ): Promise { logger.debug('verification totp token', user, challengeAnswer); - const internalUser: InternalCognitoUser | any = user; let signInUserSession; - if ( - internalUser && - typeof internalUser.getSignInUserSession === 'function' - ) { - signInUserSession = (user as InternalCognitoUser).getSignInUserSession(); + if (user && typeof user.getSignInUserSession === 'function') { + signInUserSession = (user as CognitoUser).getSignInUserSession(); } const isLoggedIn = signInUserSession?.isValid(); return new Promise((res, rej) => { - internalUser.verifySoftwareToken( - challengeAnswer, - 'My TOTP device', - { - onFailure: err => { - logger.debug('verifyTotpToken failed', err); - rej(err); - return; - }, - onSuccess: data => { - if (!isLoggedIn) { - dispatchAuthEvent( - 'signIn', - internalUser, - `A user ${internalUser.getUsername()} has been signed in` - ); - } + user.verifySoftwareToken(challengeAnswer, 'My TOTP device', { + onFailure: err => { + logger.debug('verifyTotpToken failed', err); + rej(err); + return; + }, + onSuccess: data => { + if (!isLoggedIn) { dispatchAuthEvent( - 'verify', - internalUser, - `A user ${internalUser.getUsername()} has been verified` + 'signIn', + user, + `A user ${user.getUsername()} has been signed in` ); - logger.debug('verifyTotpToken success', data); - res(data); - return; - }, + } + dispatchAuthEvent( + 'verify', + user, + `A user ${user.getUsername()} has been verified` + ); + logger.debug('verifyTotpToken success', data); + res(data); + return; }, - getAuthUserAgentValue( - AuthAction.VerifyTotpToken, - customUserAgentDetails - ) - ); + }); }); } @@ -1325,19 +1229,13 @@ export class InternalAuthClass { clientMetadata: ClientMetaData = this._config.clientMetadata, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; - if (!code) { return this.rejectAuthError(AuthErrorTypes.EmptyCode); } const that = this; - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.ConfirmSignIn, - customUserAgentDetails - ); return new Promise((resolve, reject) => { - internalUser.sendMFACode( + user.sendMFACode( code, { onSuccess: async session => { @@ -1349,24 +1247,19 @@ export class InternalAuthClass { } catch (e) { logger.debug('cannot get cognito credentials', e); } finally { - that.user = internalUser; + that.user = user; try { - const currentUser = await this._currentUserPoolUser( - undefined, - userAgentDetails - ); - Object.assign(internalUser, { - attributes: currentUser.attributes, - }); + const currentUser = await this.currentUserPoolUser(); + user.attributes = currentUser.attributes; } catch (e) { logger.debug('cannot get updated Cognito User', e); } dispatchAuthEvent( 'signIn', - internalUser, - `A user ${internalUser.getUsername()} has been signed in` + user, + `A user ${user.getUsername()} has been signed in` ); - resolve(internalUser); + resolve(user); } }, onFailure: err => { @@ -1375,8 +1268,7 @@ export class InternalAuthClass { }, }, mfaType, - clientMetadata, - getAmplifyUserAgent(userAgentDetails) + clientMetadata ); }); } @@ -1388,15 +1280,13 @@ export class InternalAuthClass { clientMetadata: ClientMetaData = this._config.clientMetadata, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; - if (!password) { return this.rejectAuthError(AuthErrorTypes.EmptyPassword); } const that = this; return new Promise((resolve, reject) => { - internalUser.completeNewPasswordChallenge( + user.completeNewPasswordChallenge( password, requiredAttributes, { @@ -1409,13 +1299,13 @@ export class InternalAuthClass { } catch (e) { logger.debug('cannot get cognito credentials', e); } finally { - that.user = internalUser; + that.user = user; dispatchAuthEvent( 'signIn', - internalUser, - `A user ${internalUser.getUsername()} has been signed in` + user, + `A user ${user.getUsername()} has been signed in` ); - resolve(internalUser); + resolve(user); } }, onFailure: err => { @@ -1429,28 +1319,24 @@ export class InternalAuthClass { }, mfaRequired: (challengeName, challengeParam) => { logger.debug('signIn MFA required'); - internalUser['challengeName'] = challengeName; - internalUser['challengeParam'] = challengeParam; - resolve(internalUser); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); }, mfaSetup: (challengeName, challengeParam) => { logger.debug('signIn mfa setup', challengeName); - internalUser['challengeName'] = challengeName; - internalUser['challengeParam'] = challengeParam; - resolve(internalUser); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); }, totpRequired: (challengeName, challengeParam) => { logger.debug('signIn mfa setup', challengeName); - internalUser['challengeName'] = challengeName; - internalUser['challengeParam'] = challengeParam; - resolve(internalUser); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); }, }, - clientMetadata, - getAuthUserAgentValue( - AuthAction.CompleteNewPassword, - customUserAgentDetails - ) + clientMetadata ); }); } @@ -1461,7 +1347,6 @@ export class InternalAuthClass { * @param {String} challengeResponses - The confirmation code * @param {ClientMetaData} clientMetadata - optional client metadata defaults to config * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * */ public sendCustomChallengeAnswer( user: CognitoUser | any, @@ -1469,8 +1354,6 @@ export class InternalAuthClass { clientMetadata: ClientMetaData = this._config.clientMetadata, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; - if (!this.userPool) { return this.rejectNoUserPool(); } @@ -1479,16 +1362,11 @@ export class InternalAuthClass { } const that = this; - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.SendCustomChallengeAnswer, - customUserAgentDetails - ); return new Promise((resolve, reject) => { - internalUser.sendCustomChallengeAnswer( + user.sendCustomChallengeAnswer( challengeResponses, - this.authCallbacks(internalUser, resolve, reject, userAgentDetails), - clientMetadata, - getAmplifyUserAgent(userAgentDetails) + this.authCallbacks(user, resolve, reject), + clientMetadata ); }); } @@ -1505,25 +1383,16 @@ export class InternalAuthClass { attributeNames: string[], customUserAgentDetails?: CustomUserAgentDetails ) { - const internalUser: InternalCognitoUser | any = user; const that = this; - const userAgentValue = getAuthUserAgentValue( - AuthAction.DeleteUserAttributes, - customUserAgentDetails - ); return new Promise((resolve, reject) => { - that._userSession(userAgentValue, internalUser).then(session => { - internalUser.deleteAttributes( - attributeNames, - (err, result) => { - if (err) { - return reject(err); - } else { - return resolve(result); - } - }, - userAgentValue - ); + that.userSession(user).then(session => { + user.deleteAttributes(attributeNames, (err, result) => { + if (err) { + return reject(err); + } else { + return resolve(result); + } + }); }); }); } @@ -1550,26 +1419,18 @@ export class InternalAuthClass { return new Promise(async (res, rej) => { if (this.userPool) { - const internalUser = - this.userPool.getCurrentUser() as InternalCognitoUser; + const user = this.userPool.getCurrentUser(); - if (!internalUser) { + if (!user) { logger.debug('Failed to get user from user pool'); return rej(new Error('No current user.')); } else { - const userAgentValue = getAuthUserAgentValue( - AuthAction.DeleteUser, - customUserAgentDetails - ); - internalUser.getSession(async (err, session) => { + user.getSession(async (err, session) => { if (err) { logger.debug('Failed to get the user session', err); if (this.isSessionInvalid(err)) { try { - await this.cleanUpInvalidSession( - internalUser, - userAgentValue - ); + await this.cleanUpInvalidSession(user); } catch (cleanUpError) { rej( new Error( @@ -1581,40 +1442,36 @@ export class InternalAuthClass { } return rej(err); } else { - internalUser.deleteUser( - (err, result: string) => { - if (err) { - rej(err); + user.deleteUser((err, result: string) => { + if (err) { + rej(err); + } else { + dispatchAuthEvent( + 'userDeleted', + result, + 'The authenticated user has been deleted.' + ); + user.signOut(); + this.user = null; + try { + this.cleanCachedItems(); // clean aws credentials + } catch (e) { + // TODO: change to rejects in refactor + logger.debug('failed to clear cached items'); + } + + if (isSignedInHostedUI) { + this.oAuthSignOutRedirect(res, rej); } else { dispatchAuthEvent( - 'userDeleted', - result, - 'The authenticated user has been deleted.' + 'signOut', + this.user, + `A user has been signed out` ); - internalUser.signOut(undefined, userAgentValue); - this.user = null; - try { - this.cleanCachedItems(); // clean aws credentials - } catch (e) { - // TODO: change to rejects in refactor - logger.debug('failed to clear cached items'); - } - - if (isSignedInHostedUI) { - this.oAuthSignOutRedirect(res, rej); - } else { - dispatchAuthEvent( - 'signOut', - this.user, - `A user has been signed out` - ); - res(result); - } + res(result); } - }, - undefined, - userAgentValue - ); + } + }); } }); } @@ -1639,15 +1496,10 @@ export class InternalAuthClass { clientMetadata: ClientMetaData = this._config.clientMetadata, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const internalUser: InternalCognitoUser | any = user; const attributeList: ICognitoUserAttributeData[] = []; const that = this; - const userAgentValue = getAuthUserAgentValue( - AuthAction.UpdateUserAttributes, - customUserAgentDetails - ); return new Promise((resolve, reject) => { - that._userSession(userAgentValue, internalUser).then(session => { + that.userSession(user).then(session => { for (const key in attributes) { if (key !== 'sub' && key.indexOf('_verified') < 0) { const attr: ICognitoUserAttributeData = { @@ -1657,7 +1509,7 @@ export class InternalAuthClass { attributeList.push(attr); } } - internalUser.updateAttributes( + user.updateAttributes( attributeList, (err, result, details) => { if (err) { @@ -1680,8 +1532,7 @@ export class InternalAuthClass { return resolve(result); } }, - clientMetadata, - userAgentValue + clientMetadata ); }); }); @@ -1717,27 +1568,15 @@ export class InternalAuthClass { user: CognitoUser | any, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - return this._userAttributes(user, customUserAgentDetails); - } - - private _userAttributes( - user: CognitoUser | any, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const internalUser: InternalCognitoUser | any = user; - const userAgentValue = getAuthUserAgentValue( - AuthAction.UserAttributes, - customUserAgentDetails - ); return new Promise((resolve, reject) => { - this._userSession(userAgentValue, internalUser).then(session => { - internalUser.getUserAttributes((err, attributes) => { + this.userSession(user).then(session => { + user.getUserAttributes((err, attributes) => { if (err) { reject(err); } else { resolve(attributes); } - }, userAgentValue); + }); }); }); } @@ -1747,13 +1586,7 @@ export class InternalAuthClass { customUserAgentDetails?: CustomUserAgentDetails ) { const that = this; - return this._userAttributes( - user, - getAuthUserAgentDetails( - AuthAction.VerifiedContact, - customUserAgentDetails - ) - ).then(attributes => { + return this.userAttributes(user).then(attributes => { const attrs = that.attributesToObject(attributes); const unverified = {}; const verified = {}; @@ -1854,11 +1687,8 @@ export class InternalAuthClass { ); } - private async cleanUpInvalidSession( - internalUser: InternalCognitoUser, - userAgentValue: string - ) { - internalUser.signOut(undefined, userAgentValue); + private async cleanUpInvalidSession(user: CognitoUser) { + user.signOut(); this.user = null; try { await this.cleanCachedItems(); // clean aws credentials @@ -1883,13 +1713,6 @@ export class InternalAuthClass { public currentUserPoolUser( params?: CurrentUserOpts, customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return this._currentUserPoolUser(params, customUserAgentDetails); - } - - private _currentUserPoolUser( - params?: CurrentUserOpts, - customUserAgentDetails?: CustomUserAgentDetails ): Promise { if (!this.userPool) { return this.rejectNoUserPool(); @@ -1930,10 +1753,9 @@ export class InternalAuthClass { }); } - const internalUser = - this.userPool.getCurrentUser() as InternalCognitoUser; + const user = this.userPool.getCurrentUser(); - if (!internalUser) { + if (!user) { logger.debug('Failed to get user from user pool'); rej('No current user'); return; @@ -1941,14 +1763,7 @@ export class InternalAuthClass { // refresh the session if the session expired. try { - const userAgentValue = getAuthUserAgentValue( - AuthAction.CurrentUserPoolUser, - customUserAgentDetails - ); - const session = await this._userSession( - userAgentValue, - internalUser - ); + const session = await this._userSession(user); // get user data from Cognito const bypassCache = params ? params.bypassCache : false; @@ -1962,16 +1777,13 @@ export class InternalAuthClass { // validate the token's scope first before calling this function const { scope = '' } = session.getAccessToken().decodePayload(); if (scope.split(' ').includes(USER_ADMIN_SCOPE)) { - internalUser.getUserData( + user.getUserData( async (err, data) => { if (err) { logger.debug('getting user data failed', err); if (this.isSessionInvalid(err)) { try { - await this.cleanUpInvalidSession( - internalUser, - userAgentValue - ); + await this.cleanUpInvalidSession(user); } catch (cleanUpError) { rej( new Error( @@ -1982,12 +1794,12 @@ export class InternalAuthClass { } rej(err); } else { - res(internalUser); + res(user); } return; } const preferredMFA = data.PreferredMfaSetting || 'NOMFA'; - const attributeList: CognitoUserAttribute[] = []; + const attributeList = []; for (let i = 0; i < data.UserAttributes.length; i++) { const attribute = { @@ -1999,18 +1811,17 @@ export class InternalAuthClass { } const attributes = this.attributesToObject(attributeList); - Object.assign(internalUser, { attributes, preferredMFA }); - return res(internalUser); + Object.assign(user, { attributes, preferredMFA }); + return res(user); }, - { bypassCache, clientMetadata }, - userAgentValue + { bypassCache, clientMetadata } ); } else { logger.debug( `Unable to get the user data because the ${USER_ADMIN_SCOPE} ` + `is not in the scopes of the access token` ); - return res(internalUser); + return res(user); } } catch (err) { rej(err); @@ -2033,14 +1844,7 @@ export class InternalAuthClass { * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @return - A promise resolves to current authenticated CognitoUser if success */ - public currentAuthenticatedUser( - params?: CurrentUserOpts, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return this._currentAuthenticatedUser(params, customUserAgentDetails); - } - - private async _currentAuthenticatedUser( + public async currentAuthenticatedUser( params?: CurrentUserOpts, customUserAgentDetails?: CustomUserAgentDetails ): Promise { @@ -2075,13 +1879,7 @@ export class InternalAuthClass { logger.debug('get current authenticated userpool user'); let user = null; try { - user = await this._currentUserPoolUser( - params, - getAuthUserAgentDetails( - AuthAction.CurrentAuthenticatedUser, - customUserAgentDetails - ) - ); + user = await this.currentUserPoolUser(params); } catch (e) { if (e === 'No userPool') { logger.error( @@ -2104,18 +1902,8 @@ export class InternalAuthClass { */ public currentSession( customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return this._currentSession(customUserAgentDetails); - } - - private _currentSession( - customUserAgentDetails?: CustomUserAgentDetails ): Promise { const that = this; - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.CurrentSession, - customUserAgentDetails - ); logger.debug('Getting current session'); // Purposely not calling the reject method here because we don't need a console error if (!this.userPool) { @@ -2124,10 +1912,10 @@ export class InternalAuthClass { return new Promise((res, rej) => { that - ._currentUserPoolUser(undefined, userAgentDetails) + .currentUserPoolUser() .then(user => { that - ._userSession(getAmplifyUserAgent(userAgentDetails), user) + .userSession(user) .then(session => { res(session); return; @@ -2146,11 +1934,8 @@ export class InternalAuthClass { }); } - private async _userSession( - userAgentValue: string, - internalUser?: InternalCognitoUser - ): Promise { - if (!internalUser) { + private async _userSession(user?: CognitoUser): Promise { + if (!user) { logger.debug('the user is null'); return this.rejectAuthError(AuthErrorTypes.NoUserSession); } @@ -2160,19 +1945,13 @@ export class InternalAuthClass { if (this.inflightSessionPromiseCounter === 0) { this.inflightSessionPromise = new Promise( (res, rej) => { - internalUser.getSession( + user.getSession( async (err, session) => { if (err) { - logger.debug( - 'Failed to get the session from user', - internalUser - ); + logger.debug('Failed to get the session from user', user); if (this.isSessionInvalid(err)) { try { - await this.cleanUpInvalidSession( - internalUser, - userAgentValue - ); + await this.cleanUpInvalidSession(user); } catch (cleanUpError) { rej( new Error( @@ -2190,8 +1969,7 @@ export class InternalAuthClass { return; } }, - { clientMetadata }, - userAgentValue + { clientMetadata } ); } ); @@ -2202,7 +1980,7 @@ export class InternalAuthClass { const userSession = await this.inflightSessionPromise; // Set private member. Avoid user.setSignInUserSession() to prevent excessive localstorage refresh. // @ts-ignore - internalUser.signInUserSession = userSession; + user.signInUserSession = userSession; return userSession!; } finally { this.inflightSessionPromiseCounter--; @@ -2219,10 +1997,7 @@ export class InternalAuthClass { user, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - return this._userSession( - getAuthUserAgentValue(AuthAction.UserSession, customUserAgentDetails), - user - ); + return this._userSession(user); } /** @@ -2256,12 +2031,7 @@ export class InternalAuthClass { // refresh the jwt token here if necessary return this.Credentials.refreshFederatedToken(federatedInfo); } else { - return this._currentSession( - getAuthUserAgentDetails( - AuthAction.CurrentUserCredentials, - customUserAgentDetails - ) - ) + return this.currentSession() .then(session => { logger.debug('getting session success', session); return this.Credentials.set(session, 'session'); @@ -2284,8 +2054,6 @@ export class InternalAuthClass { * Initiate an attribute confirmation request * @param {Object} user - The CognitoUser * @param {Object} attr - The attributes to be verified - * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @return - A promise resolves to callback data if success */ public verifyUserAttribute( @@ -2294,24 +2062,8 @@ export class InternalAuthClass { clientMetadata: ClientMetaData = this._config.clientMetadata, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - return this._verifyUserAttribute( - user, - attr, - clientMetadata, - customUserAgentDetails - ); - } - - private _verifyUserAttribute( - user: CognitoUser | any, - attr: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const internalUser: InternalCognitoUser | any = user; - return new Promise((resolve, reject) => { - internalUser.getAttributeVerificationCode( + user.getAttributeVerificationCode( attr, { onSuccess(success) { @@ -2321,11 +2073,7 @@ export class InternalAuthClass { return reject(err); }, }, - clientMetadata, - getAuthUserAgentValue( - AuthAction.VerifyUserAttribute, - customUserAgentDetails - ) + clientMetadata ); }); } @@ -2343,45 +2091,22 @@ export class InternalAuthClass { attr: string, code: string, customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return this._verifyUserAttributeSubmit( - user, - attr, - code, - customUserAgentDetails - ); - } - - private _verifyUserAttributeSubmit( - user: CognitoUser | any, - attr: string, - code: string, - customUserAgentDetails?: CustomUserAgentDetails ): Promise { if (!code) { return this.rejectAuthError(AuthErrorTypes.EmptyCode); } - const internalUser: InternalCognitoUser | any = user; return new Promise((resolve, reject) => { - internalUser.verifyAttribute( - attr, - code, - { - onSuccess(data) { - resolve(data); - return; - }, - onFailure(err) { - reject(err); - return; - }, + user.verifyAttribute(attr, code, { + onSuccess(data) { + resolve(data); + return; }, - getAuthUserAgentValue( - AuthAction.VerifyUserAttributeSubmit, - customUserAgentDetails - ) - ); + onFailure(err) { + reject(err); + return; + }, + }); }); } @@ -2389,16 +2114,10 @@ export class InternalAuthClass { attr: string, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.VerifyCurrentUserAttribute, - customUserAgentDetails - ); const that = this; return that - ._currentUserPoolUser(undefined, userAgentDetails) - .then(user => - that._verifyUserAttribute(user, attr, undefined, userAgentDetails) - ); + .currentUserPoolUser() + .then(user => that.verifyUserAttribute(user, attr)); } /** @@ -2413,22 +2132,15 @@ export class InternalAuthClass { code: string, customUserAgentDetails?: CustomUserAgentDetails ): Promise { - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.VerifyCurrentUserAttributeSubmit, - customUserAgentDetails - ); const that = this; return that - ._currentUserPoolUser(undefined, userAgentDetails) - .then(user => - that._verifyUserAttributeSubmit(user, attr, code, userAgentDetails) - ); + .currentUserPoolUser() + .then(user => that.verifyUserAttributeSubmit(user, attr, code)); } private async cognitoIdentitySignOut( opts: SignOutOpts, - internalUser: InternalCognitoUser | any, - userAgentValue: string + user: CognitoUser | any ) { try { await this._storageSync; @@ -2443,21 +2155,18 @@ export class InternalAuthClass { return new Promise((res, rej) => { if (opts && opts.global) { - logger.debug('user global sign out', internalUser); + logger.debug('user global sign out', user); // in order to use global signout // we must validate the user as an authenticated user by using getSession const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn - internalUser.getSession( + user.getSession( async (err, result) => { if (err) { logger.debug('failed to get the user session', err); if (this.isSessionInvalid(err)) { try { - await this.cleanUpInvalidSession( - internalUser, - userAgentValue - ); + await this.cleanUpInvalidSession(user); } catch (cleanUpError) { rej( new Error( @@ -2469,36 +2178,32 @@ export class InternalAuthClass { } return rej(err); } - internalUser.globalSignOut( - { - onSuccess: data => { - logger.debug('global sign out success'); - if (isSignedInHostedUI) { - this.oAuthSignOutRedirect(res, rej); - } else { - return res(); - } - }, - onFailure: err => { - logger.debug('global sign out failed', err); - return rej(err); - }, + user.globalSignOut({ + onSuccess: data => { + logger.debug('global sign out success'); + if (isSignedInHostedUI) { + this.oAuthSignOutRedirect(res, rej); + } else { + return res(); + } }, - userAgentValue - ); + onFailure: err => { + logger.debug('global sign out failed', err); + return rej(err); + }, + }); }, - { clientMetadata }, - userAgentValue + { clientMetadata } ); } else { - logger.debug('user sign out', internalUser); - internalUser.signOut(() => { + logger.debug('user sign out', user); + user.signOut(() => { if (isSignedInHostedUI) { this.oAuthSignOutRedirect(res, rej); } else { return res(); } - }, userAgentValue); + }); } }); } @@ -2545,14 +2250,9 @@ export class InternalAuthClass { } if (this.userPool) { - const internalUser = - this.userPool.getCurrentUser() as InternalCognitoUser; - if (internalUser) { - await this.cognitoIdentitySignOut( - opts, - internalUser, - getAuthUserAgentValue(AuthAction.SignOut, customUserAgentDetails) - ); + const user = this.userPool.getCurrentUser(); + if (user) { + await this.cognitoIdentitySignOut(opts, user); } else { logger.debug('no current Cognito user'); } @@ -2591,15 +2291,9 @@ export class InternalAuthClass { clientMetadata: ClientMetaData = this._config.clientMetadata, customUserAgentDetails?: CustomUserAgentDetails ): Promise<'SUCCESS'> { - const internalUser: InternalCognitoUser | any = user; - const userAgentValue = getAuthUserAgentValue( - AuthAction.ChangePassword, - customUserAgentDetails - ); - return new Promise((resolve, reject) => { - this._userSession(userAgentValue, internalUser).then(session => { - internalUser.changePassword( + this.userSession(user).then(session => { + user.changePassword( oldPassword, newPassword, (err, data) => { @@ -2610,8 +2304,7 @@ export class InternalAuthClass { return resolve(data); } }, - clientMetadata, - userAgentValue + clientMetadata ); }); }); @@ -2636,9 +2329,9 @@ export class InternalAuthClass { return this.rejectAuthError(AuthErrorTypes.EmptyUsername); } - const internalUser = this.createCognitoUser(username); + const user = this.createCognitoUser(username); return new Promise((resolve, reject) => { - internalUser.forgotPassword( + user.forgotPassword( { onSuccess: () => { resolve(); @@ -2657,15 +2350,14 @@ export class InternalAuthClass { inputVerificationCode: data => { dispatchAuthEvent( 'forgotPassword', - internalUser, + user, `${username} has initiated forgot password flow` ); resolve(data); return; }, }, - clientMetadata, - getAuthUserAgentValue(AuthAction.ForgotPassword, customUserAgentDetails) + clientMetadata ); }); } @@ -2699,16 +2391,16 @@ export class InternalAuthClass { return this.rejectAuthError(AuthErrorTypes.EmptyPassword); } - const internalUser = this.createCognitoUser(username); + const user = this.createCognitoUser(username); return new Promise((resolve, reject) => { - internalUser.confirmPassword( + user.confirmPassword( code, password, { onSuccess: success => { dispatchAuthEvent( 'forgotPasswordSubmit', - internalUser, + user, `${username} forgotPasswordSubmit successful` ); resolve(success); @@ -2724,11 +2416,7 @@ export class InternalAuthClass { return; }, }, - clientMetadata, - getAuthUserAgentValue( - AuthAction.ForgotPasswordSubmit, - customUserAgentDetails - ) + clientMetadata ); }); } @@ -2743,25 +2431,17 @@ export class InternalAuthClass { customUserAgentDetails?: CustomUserAgentDetails ) { const source = this.Credentials.getCredSource(); - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.CurrentUserInfo, - customUserAgentDetails - ); if (!source || source === 'aws' || source === 'userPool') { - const internalUser: InternalCognitoUser = await this._currentUserPoolUser( - undefined, - userAgentDetails - ).catch(err => logger.error(err)); - if (!internalUser) { + const user = await this.currentUserPoolUser().catch(err => + logger.error(err) + ); + if (!user) { return null; } try { - const attributes = await this._userAttributes( - internalUser, - userAgentDetails - ); + const attributes = await this.userAttributes(user); const userAttrs: object = this.attributesToObject(attributes); let credentials = null; try { @@ -2775,7 +2455,7 @@ export class InternalAuthClass { const info = { id: credentials ? credentials.identityId : undefined, - username: internalUser.getUsername(), + username: user.getUsername(), attributes: userAttrs, }; return info; @@ -2841,14 +2521,6 @@ export class InternalAuthClass { ? this._config.oauth.redirectSignIn : this._config.oauth.redirectUri; - this._storage.setItem( - 'aws-amplify-federatedUserAgent', - getAuthUserAgentValue( - AuthAction.FederatedSignIn, - customUserAgentDetails - ) - ); - this._oAuthHandler.oauthSignIn( this._config.oauth.responseType, this._config.oauth.domain, @@ -2878,7 +2550,7 @@ export class InternalAuthClass { { provider, token, identity_id, user, expires_at }, 'federation' ); - const currentUser = await this._currentAuthenticatedUser(); + const currentUser = await this.currentAuthenticatedUser(); dispatchAuthEvent( 'signIn', currentUser, @@ -2929,15 +2601,9 @@ export class InternalAuthClass { if (hasCodeOrError || hasTokenOrError) { this._storage.setItem('amplify-redirected-from-hosted-ui', 'true'); - const userAgentValue = - this._storage.getItem('aws-amplify-federatedUserAgent') || undefined; - this._storage.removeItem('aws-amplify-federatedUserAgent'); try { const { accessToken, idToken, refreshToken, state } = - await this._oAuthHandler.handleAuthResponse( - currentUrl, - userAgentValue - ); + await this._oAuthHandler.handleAuthResponse(currentUrl); const session = new CognitoUserSession({ IdToken: new CognitoIdToken({ IdToken: idToken }), RefreshToken: new CognitoRefreshToken({ @@ -3079,7 +2745,7 @@ export class InternalAuthClass { ); } - private createCognitoUser(username: string): InternalCognitoUser { + private createCognitoUser(username: string): CognitoUser { const userData: ICognitoUserData = { Username: username, Pool: this.userPool, @@ -3088,11 +2754,11 @@ export class InternalAuthClass { const { authenticationFlowType } = this._config; - const internalUser = new InternalCognitoUser(userData); + const user = new CognitoUser(userData); if (authenticationFlowType) { - internalUser.setAuthenticationFlowType(authenticationFlowType); + user.setAuthenticationFlowType(authenticationFlowType); } - return internalUser; + return user; } private _isValidAuthStorage(obj) { @@ -3127,105 +2793,78 @@ export class InternalAuthClass { public async rememberDevice( customUserAgentDetails?: CustomUserAgentDetails ): Promise { - let internalUser: InternalCognitoUser | any; - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.RememberDevice, - customUserAgentDetails - ); + let currUser; try { - internalUser = await this._currentUserPoolUser( - undefined, - userAgentDetails - ); + currUser = await this.currentUserPoolUser(); } catch (error) { logger.debug('The user is not authenticated by the error', error); return Promise.reject('The user is not authenticated'); } - internalUser.getCachedDeviceKeyAndPassword(); + currUser.getCachedDeviceKeyAndPassword(); return new Promise((res, rej) => { - internalUser.setDeviceStatusRemembered( - { - onSuccess: data => { - res(data); - }, - onFailure: err => { - if (err.code === 'InvalidParameterException') { - rej(new AuthError(AuthErrorTypes.DeviceConfig)); - } else if (err.code === 'NetworkError') { - rej(new AuthError(AuthErrorTypes.NetworkError)); - } else { - rej(err); - } - }, + currUser.setDeviceStatusRemembered({ + onSuccess: data => { + res(data); }, - getAmplifyUserAgent(userAgentDetails) - ); + onFailure: err => { + if (err.code === 'InvalidParameterException') { + rej(new AuthError(AuthErrorTypes.DeviceConfig)); + } else if (err.code === 'NetworkError') { + rej(new AuthError(AuthErrorTypes.NetworkError)); + } else { + rej(err); + } + }, + }); }); } public async forgetDevice( customUserAgentDetails?: CustomUserAgentDetails ): Promise { - let internalUser: InternalCognitoUser | any; - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.ForgetDevice, - customUserAgentDetails - ); + let currUser; try { - internalUser = await this._currentUserPoolUser( - undefined, - userAgentDetails - ); + currUser = await this.currentUserPoolUser(); } catch (error) { logger.debug('The user is not authenticated by the error', error); return Promise.reject('The user is not authenticated'); } - internalUser.getCachedDeviceKeyAndPassword(); + currUser.getCachedDeviceKeyAndPassword(); return new Promise((res, rej) => { - internalUser.forgetDevice( - { - onSuccess: data => { - res(data); - }, - onFailure: err => { - if (err.code === 'InvalidParameterException') { - rej(new AuthError(AuthErrorTypes.DeviceConfig)); - } else if (err.code === 'NetworkError') { - rej(new AuthError(AuthErrorTypes.NetworkError)); - } else { - rej(err); - } - }, + currUser.forgetDevice({ + onSuccess: data => { + res(data); }, - getAmplifyUserAgent(userAgentDetails) - ); + onFailure: err => { + if (err.code === 'InvalidParameterException') { + rej(new AuthError(AuthErrorTypes.DeviceConfig)); + } else if (err.code === 'NetworkError') { + rej(new AuthError(AuthErrorTypes.NetworkError)); + } else { + rej(err); + } + }, + }); }); } public async fetchDevices( customUserAgentDetails?: CustomUserAgentDetails ): Promise { - let internalUser: InternalCognitoUser | any; - const userAgentDetails = getAuthUserAgentDetails( - AuthAction.FetchDevices, - customUserAgentDetails - ); + let currUser; try { - internalUser = await this._currentUserPoolUser( - undefined, - userAgentDetails - ); + currUser = await this.currentUserPoolUser(); } catch (error) { logger.debug('The user is not authenticated by the error', error); throw new Error('The user is not authenticated'); } - internalUser.getCachedDeviceKeyAndPassword(); + currUser.getCachedDeviceKeyAndPassword(); return new Promise((res, rej) => { const cb = { onSuccess(data) { @@ -3253,12 +2892,7 @@ export class InternalAuthClass { } }, }; - internalUser.listDevices( - MAX_DEVICES, - null, - cb, - getAmplifyUserAgent(userAgentDetails) - ); + currUser.listDevices(MAX_DEVICES, null, cb); }); } } diff --git a/packages/auth/src/utils.ts b/packages/auth/src/utils.ts deleted file mode 100644 index 233395b0828..00000000000 --- a/packages/auth/src/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - AuthAction, - Category, - CustomUserAgentDetails, - getAmplifyUserAgent, -} from '@aws-amplify/core'; - -export function getAuthUserAgentValue( - action: AuthAction, - customUserAgentDetails?: CustomUserAgentDetails -) { - return getAmplifyUserAgent({ - category: Category.Auth, - action, - ...customUserAgentDetails, - }); -} - -export function getAuthUserAgentDetails( - action: AuthAction, - customUserAgentDetails?: CustomUserAgentDetails -): CustomUserAgentDetails { - return { category: Category.Auth, action, ...customUserAgentDetails }; -} diff --git a/packages/core/package.json b/packages/core/package.json index 733b25004ba..c26c8c2279c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -104,7 +104,7 @@ "name": "Core (Credentials)", "path": "./lib-esm/index.js", "import": "{ Credentials }", - "limit": "13.78 kB" + "limit": "13.45 kB" }, { "name": "Core (Signer)", diff --git a/packages/core/src/Platform/types.ts b/packages/core/src/Platform/types.ts index af10b9ef787..b2a7e98e701 100644 --- a/packages/core/src/Platform/types.ts +++ b/packages/core/src/Platform/types.ts @@ -54,44 +54,40 @@ export enum ApiAction { Head = '7', } export enum AuthAction { - SignUp = '1', - ConfirmSignUp = '2', - ResendSignUp = '3', - SignIn = '4', - GetMFAOptions = '5', - GetPreferredMFA = '6', - SetPreferredMFA = '7', - DisableSMS = '8', - EnableSMS = '9', - SetupTOTP = '10', - VerifyTotpToken = '11', - ConfirmSignIn = '12', - CompleteNewPassword = '13', - SendCustomChallengeAnswer = '14', - DeleteUserAttributes = '15', - DeleteUser = '16', - UpdateUserAttributes = '17', - UserAttributes = '18', - CurrentUserPoolUser = '19', - CurrentAuthenticatedUser = '20', - CurrentSession = '21', - VerifyUserAttribute = '22', - VerifyUserAttributeSubmit = '23', - VerifyCurrentUserAttribute = '24', - VerifyCurrentUserAttributeSubmit = '25', - SignOut = '26', - ChangePassword = '27', - ForgotPassword = '28', - ForgotPasswordSubmit = '29', + // SignUp = '1', + // ConfirmSignUp = '2', + // ResendSignUp = '3', + // SignIn = '4', + // GetMFAOptions = '5', + // GetPreferredMFA = '6', + // SetPreferredMFA = '7', + // DisableSMS = '8', + // EnableSMS = '9', + // SetupTOTP = '10', + // VerifyTotpToken = '11', + // ConfirmSignIn = '12', + // CompleteNewPassword = '13', + // SendCustomChallengeAnswer = '14', + // DeleteUserAttributes = '15', + // DeleteUser = '16', + // UpdateUserAttributes = '17', + // UserAttributes = '18', + // CurrentUserPoolUser = '19', + // CurrentAuthenticatedUser = '20', + // CurrentSession = '21', + // VerifyUserAttribute = '22', + // VerifyUserAttributeSubmit = '23', + // VerifyCurrentUserAttribute = '24', + // VerifyCurrentUserAttributeSubmit = '25', + // SignOut = '26', + // ChangePassword = '27', + // ForgotPassword = '28', + // ForgotPasswordSubmit = '29', FederatedSignIn = '30', - VerifiedContact = '31', - UserSession = '32', - CurrentUserCredentials = '33', - CurrentCredentials = '34', - CurrentUserInfo = '35', - RememberDevice = '36', - ForgetDevice = '37', - FetchDevices = '38', + // CurrentUserInfo = '31', + // RememberDevice = '32', + // ForgetDevice = '33', + // FetchDevices = '34', } export enum DataStoreAction { Subscribe = '1', diff --git a/packages/datastore/package.json b/packages/datastore/package.json index 865a15eaeef..3519ccac09b 100644 --- a/packages/datastore/package.json +++ b/packages/datastore/package.json @@ -73,7 +73,7 @@ "name": "DataStore (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, DataStore }", - "limit": "139.12 kB" + "limit": "138.27 kB" } ], "jest": { diff --git a/packages/geo/package.json b/packages/geo/package.json index a08e5657744..1d06adf2b45 100644 --- a/packages/geo/package.json +++ b/packages/geo/package.json @@ -58,7 +58,7 @@ "name": "Geo (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Geo }", - "limit": "52.45 kB" + "limit": "52.12 kB" } ], "jest": { diff --git a/packages/interactions/package.json b/packages/interactions/package.json index 396e7e1b236..a88fad4dfd2 100644 --- a/packages/interactions/package.json +++ b/packages/interactions/package.json @@ -59,7 +59,7 @@ "name": "Interactions (top-level class with Lex v2)", "path": "./lib-esm/index.js", "import": "{ Amplify, Interactions, AWSLexV2Provider }", - "limit": "76.35 kB" + "limit": "76.02 kB" } ], "jest": { diff --git a/packages/notifications/package.json b/packages/notifications/package.json index 7ecf2cb599f..4e52fc542ef 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -66,13 +66,13 @@ "name": "Notifications (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Notifications }", - "limit": "30.67 kB" + "limit": "30.5 kB" }, { "name": "Notifications (with Analytics)", "path": "./lib-esm/index.js", "import": "{ Amplify, Notifications, Analytics }", - "limit": "30.68 kB" + "limit": "30.5 kB" } ] } diff --git a/packages/predictions/package.json b/packages/predictions/package.json index 62f6bbef761..6f4bf2ce234 100644 --- a/packages/predictions/package.json +++ b/packages/predictions/package.json @@ -63,25 +63,25 @@ "name": "Predictions (all providers)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIPredictionsProvider }", - "limit": "103.87 kB" + "limit": "103.55 kB" }, { "name": "Predictions (Convert provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIConvertPredictionsProvider }", - "limit": "58.32 kB" + "limit": "58 kB" }, { "name": "Predictions (Identify provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIIdentifyPredictionsProvider }", - "limit": "73.9 kB" + "limit": "73.57 kB" }, { "name": "Predictions (Interpret provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIInterpretPredictionsProvider }", - "limit": "43.77 kB" + "limit": "43.45 kB" } ], "jest": { diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index d8908214f52..2bcd3047e00 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -65,13 +65,13 @@ "name": "PubSub (IoT provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, PubSub, AWSIoTProvider }", - "limit": "82.2 kB" + "limit": "81.39 kB" }, { "name": "PubSub (Mqtt provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, PubSub, MqttOverWSProvider }", - "limit": "82.07 kB" + "limit": "81.26 kB" } ], "jest": { diff --git a/packages/storage/package.json b/packages/storage/package.json index b0853eff423..bc438cc8dd7 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -60,7 +60,7 @@ "name": "Storage (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Storage }", - "limit": "39.29 kB" + "limit": "38.96 kB" } ], "jest": { From 0bf2d233ef51cc029d1714d40f7a9f493f9a9a5c Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:33:42 -0500 Subject: [PATCH 08/16] Revert "feat: custom user agent InternalCognitoUserPool (#11716)" This reverts commit 94bfc5f5bce7fab723591d99daf7cd9185443b89. --- .../__tests__/internalsIndex.test.js | 13 ++ .../amazon-cognito-identity-js/index.d.ts | 17 +- .../internals/index.d.ts | 42 +--- .../amazon-cognito-identity-js/src/Client.js | 1 - .../src/CognitoUserPool.js | 167 ++++++++++++++- .../src/internals/InternalCognitoUserPool.js | 196 ------------------ .../src/internals/index.js | 1 - packages/auth/package.json | 2 +- 8 files changed, 194 insertions(+), 245 deletions(-) create mode 100644 packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js delete mode 100644 packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js diff --git a/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js b/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js new file mode 100644 index 00000000000..12b4ffa9676 --- /dev/null +++ b/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js @@ -0,0 +1,13 @@ +import * as exported from '../src/internals/index'; + +describe('import * keys', () => { + it('should match snapshot', () => { + expect(Object.keys(exported)).toMatchInlineSnapshot(` + Array [ + "addAuthCategoryToCognitoUserAgent", + "addFrameworkToCognitoUserAgent", + "InternalCognitoUser", + ] + `); + }); +}); diff --git a/packages/amazon-cognito-identity-js/index.d.ts b/packages/amazon-cognito-identity-js/index.d.ts index ca8dfaf330e..2c526568121 100644 --- a/packages/amazon-cognito-identity-js/index.d.ts +++ b/packages/amazon-cognito-identity-js/index.d.ts @@ -1,4 +1,4 @@ -import { InternalCognitoUser, InternalCognitoUserPool } from './internals'; +import { InternalCognitoUser } from './internals'; declare module 'amazon-cognito-identity-js' { //import * as AWS from "aws-sdk"; @@ -320,7 +320,18 @@ declare module 'amazon-cognito-identity-js' { AdvancedSecurityDataCollectionFlag?: boolean; } - export class CognitoUserPool extends InternalCognitoUserPool { + export class CognitoUserPool { + constructor( + data: ICognitoUserPoolData, + wrapRefreshSessionCallback?: ( + target: NodeCallback.Any + ) => NodeCallback.Any + ); + + public getUserPoolId(): string; + public getUserPoolName(): string; + public getClientId(): string; + public signUp( username: string, password: string, @@ -329,6 +340,8 @@ declare module 'amazon-cognito-identity-js' { callback: NodeCallback, clientMetadata?: ClientMetadata ): void; + + public getCurrentUser(): CognitoUser | null; } export interface ICognitoUserSessionData { diff --git a/packages/amazon-cognito-identity-js/internals/index.d.ts b/packages/amazon-cognito-identity-js/internals/index.d.ts index 5bb20d17a63..f1160c9563d 100644 --- a/packages/amazon-cognito-identity-js/internals/index.d.ts +++ b/packages/amazon-cognito-identity-js/internals/index.d.ts @@ -1,50 +1,24 @@ import { + NodeCallback, + UpdateAttributesNodeCallback, + ClientMetadata, + IAuthenticationCallback, + IMfaSettings, AuthenticationDetails, + ICognitoUserData, + GetSessionOptions, ChallengeName, - ClientMetadata, + CognitoUserSession, CognitoRefreshToken, - CognitoUser, CognitoUserAttribute, - CognitoUserSession, - GetSessionOptions, - IAuthenticationCallback, ICognitoUserAttributeData, - ICognitoUserData, - ICognitoUserPoolData, - IMfaSettings, - ISignUpResult, MFAOption, - NodeCallback, - UpdateAttributesNodeCallback, UserData, } from 'amazon-cognito-identity-js'; export const addAuthCategoryToCognitoUserAgent: () => void; export const addFrameworkToCognitoUserAgent: (content: string) => void; -export class InternalCognitoUserPool { - constructor( - data: ICognitoUserPoolData, - wrapRefreshSessionCallback?: (target: NodeCallback.Any) => NodeCallback.Any - ); - - public getUserPoolId(): string; - public getUserPoolName(): string; - public getClientId(): string; - - public signUp( - username: string, - password: string, - userAttributes: CognitoUserAttribute[], - validationData: CognitoUserAttribute[], - callback: NodeCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - - public getCurrentUser(): CognitoUser | null; -} - export class InternalCognitoUser { constructor(data: ICognitoUserData); diff --git a/packages/amazon-cognito-identity-js/src/Client.js b/packages/amazon-cognito-identity-js/src/Client.js index 4ab28a4be4e..199432eb44a 100644 --- a/packages/amazon-cognito-identity-js/src/Client.js +++ b/packages/amazon-cognito-identity-js/src/Client.js @@ -83,7 +83,6 @@ export default class Client { * @param {string} operation API operation * @param {object} params Input parameters * @param {function} callback Callback called when a response is returned - * @param {string} userAgentValue Optional string containing custom user agent value * @returns {void} */ request(operation, params, callback, userAgentValue) { diff --git a/packages/amazon-cognito-identity-js/src/CognitoUserPool.js b/packages/amazon-cognito-identity-js/src/CognitoUserPool.js index d71a575311e..3f188234291 100644 --- a/packages/amazon-cognito-identity-js/src/CognitoUserPool.js +++ b/packages/amazon-cognito-identity-js/src/CognitoUserPool.js @@ -3,13 +3,83 @@ * SPDX-License-Identifier: Apache-2.0 */ +import Client from './Client'; import CognitoUser from './CognitoUser'; -import { InternalCognitoUserPool } from './internals'; +import StorageHelper from './StorageHelper'; const USER_POOL_ID_MAX_LENGTH = 55; /** @class */ -export default class CognitoUserPool extends InternalCognitoUserPool { +export default class CognitoUserPool { + /** + * Constructs a new CognitoUserPool object + * @param {object} data Creation options. + * @param {string} data.UserPoolId Cognito user pool id. + * @param {string} data.ClientId User pool application client id. + * @param {string} data.endpoint Optional custom service endpoint. + * @param {object} data.fetchOptions Optional options for fetch API. + * (only credentials option is supported) + * @param {object} data.Storage Optional storage object. + * @param {boolean} data.AdvancedSecurityDataCollectionFlag Optional: + * boolean flag indicating if the data collection is enabled + * to support cognito advanced security features. By default, this + * flag is set to true. + */ + constructor(data, wrapRefreshSessionCallback) { + const { + UserPoolId, + ClientId, + endpoint, + fetchOptions, + AdvancedSecurityDataCollectionFlag, + } = data || {}; + if (!UserPoolId || !ClientId) { + throw new Error('Both UserPoolId and ClientId are required.'); + } + if (UserPoolId.length > USER_POOL_ID_MAX_LENGTH || !/^[\w-]+_[0-9a-zA-Z]+$/.test(UserPoolId)) { + throw new Error('Invalid UserPoolId format.'); + } + const region = UserPoolId.split('_')[0]; + + this.userPoolId = UserPoolId; + this.clientId = ClientId; + + this.client = new Client(region, endpoint, fetchOptions); + + /** + * By default, AdvancedSecurityDataCollectionFlag is set to true, + * if no input value is provided. + */ + this.advancedSecurityDataCollectionFlag = + AdvancedSecurityDataCollectionFlag !== false; + + this.storage = data.Storage || new StorageHelper().getStorage(); + + if (wrapRefreshSessionCallback) { + this.wrapRefreshSessionCallback = wrapRefreshSessionCallback; + } + } + + /** + * @returns {string} the user pool id + */ + getUserPoolId() { + return this.userPoolId; + } + + /** + * @returns {string} the user pool name + */ + getUserPoolName() { + return this.getUserPoolId().split('_')[1]; + } + + /** + * @returns {string} the client id + */ + getClientId() { + return this.clientId; + } /** * @typedef {object} SignUpResult @@ -35,13 +105,90 @@ export default class CognitoUserPool extends InternalCognitoUserPool { callback, clientMetadata ) { - return super.signUp( - username, - password, - userAttributes, - validationData, - callback, - clientMetadata - ); + const jsonReq = { + ClientId: this.clientId, + Username: username, + Password: password, + UserAttributes: userAttributes, + ValidationData: validationData, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData(username)) { + jsonReq.UserContextData = this.getUserContextData(username); + } + this.client.request('SignUp', jsonReq, (err, data) => { + if (err) { + return callback(err, null); + } + + const cognitoUser = { + Username: username, + Pool: this, + Storage: this.storage, + }; + + const returnData = { + user: new CognitoUser(cognitoUser), + userConfirmed: data.UserConfirmed, + userSub: data.UserSub, + codeDeliveryDetails: data.CodeDeliveryDetails, + }; + + return callback(null, returnData); + }); + } + + /** + * method for getting the current user of the application from the local storage + * + * @returns {CognitoUser} the user retrieved from storage + */ + getCurrentUser() { + const lastUserKey = `CognitoIdentityServiceProvider.${this.clientId}.LastAuthUser`; + + const lastAuthUser = this.storage.getItem(lastUserKey); + if (lastAuthUser) { + const cognitoUser = { + Username: lastAuthUser, + Pool: this, + Storage: this.storage, + }; + + return new CognitoUser(cognitoUser); + } + + return null; + } + + /** + * This method returns the encoded data string used for cognito advanced security feature. + * This would be generated only when developer has included the JS used for collecting the + * data on their client. Please refer to documentation to know more about using AdvancedSecurity + * features + * @param {string} username the username for the context data + * @returns {string} the user context data + **/ + getUserContextData(username) { + if (typeof AmazonCognitoAdvancedSecurityData === 'undefined') { + return undefined; + } + /* eslint-disable */ + const amazonCognitoAdvancedSecurityDataConst = AmazonCognitoAdvancedSecurityData; + /* eslint-enable */ + + if (this.advancedSecurityDataCollectionFlag) { + const advancedSecurityData = amazonCognitoAdvancedSecurityDataConst.getData( + username, + this.userPoolId, + this.clientId + ); + if (advancedSecurityData) { + const userContextData = { + EncodedData: advancedSecurityData, + }; + return userContextData; + } + } + return {}; } } diff --git a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js b/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js deleted file mode 100644 index af98946441e..00000000000 --- a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUserPool.js +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import Client from '../Client'; -import CognitoUser from '../CognitoUser'; -import StorageHelper from '../StorageHelper'; - -const USER_POOL_ID_MAX_LENGTH = 55; - -/** @class */ -export class InternalCognitoUserPool { - /** - * Constructs a new CognitoUserPool object - * @param {object} data Creation options. - * @param {string} data.UserPoolId Cognito user pool id. - * @param {string} data.ClientId User pool application client id. - * @param {string} data.endpoint Optional custom service endpoint. - * @param {object} data.fetchOptions Optional options for fetch API. - * (only credentials option is supported) - * @param {object} data.Storage Optional storage object. - * @param {boolean} data.AdvancedSecurityDataCollectionFlag Optional: - * boolean flag indicating if the data collection is enabled - * to support cognito advanced security features. By default, this - * flag is set to true. - */ - constructor(data, wrapRefreshSessionCallback) { - const { - UserPoolId, - ClientId, - endpoint, - fetchOptions, - AdvancedSecurityDataCollectionFlag, - } = data || {}; - if (!UserPoolId || !ClientId) { - throw new Error('Both UserPoolId and ClientId are required.'); - } - if (UserPoolId.length > USER_POOL_ID_MAX_LENGTH || !/^[\w-]+_[0-9a-zA-Z]+$/.test(UserPoolId)) { - throw new Error('Invalid UserPoolId format.'); - } - const region = UserPoolId.split('_')[0]; - - this.userPoolId = UserPoolId; - this.clientId = ClientId; - - this.client = new Client(region, endpoint, fetchOptions); - - /** - * By default, AdvancedSecurityDataCollectionFlag is set to true, - * if no input value is provided. - */ - this.advancedSecurityDataCollectionFlag = - AdvancedSecurityDataCollectionFlag !== false; - - this.storage = data.Storage || new StorageHelper().getStorage(); - - if (wrapRefreshSessionCallback) { - this.wrapRefreshSessionCallback = wrapRefreshSessionCallback; - } - } - - /** - * @returns {string} the user pool id - */ - getUserPoolId() { - return this.userPoolId; - } - - /** - * @returns {string} the user pool name - */ - getUserPoolName() { - return this.getUserPoolId().split('_')[1]; - } - - /** - * @returns {string} the client id - */ - getClientId() { - return this.clientId; - } - - /** - * @typedef {object} SignUpResult - * @property {CognitoUser} user New user. - * @property {bool} userConfirmed If the user is already confirmed. - */ - /** - * method for signing up a user - * @param {string} username User's username. - * @param {string} password Plain-text initial password entered by user. - * @param {(AttributeArg[])=} userAttributes New user attributes. - * @param {(AttributeArg[])=} validationData Application metadata. - * @param {(AttributeArg[])=} clientMetadata Client metadata. - * @param {nodeCallback} callback Called on error or with the new user. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - signUp( - username, - password, - userAttributes, - validationData, - callback, - clientMetadata, - userAgentValue - ) { - const jsonReq = { - ClientId: this.clientId, - Username: username, - Password: password, - UserAttributes: userAttributes, - ValidationData: validationData, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData(username)) { - jsonReq.UserContextData = this.getUserContextData(username); - } - this.client.request('SignUp', jsonReq, (err, data) => { - if (err) { - return callback(err, null); - } - - const cognitoUser = { - Username: username, - Pool: this, - Storage: this.storage, - }; - - const returnData = { - user: new CognitoUser(cognitoUser), - userConfirmed: data.UserConfirmed, - userSub: data.UserSub, - codeDeliveryDetails: data.CodeDeliveryDetails, - }; - - return callback(null, returnData); - }, userAgentValue); - } - - /** - * method for getting the current user of the application from the local storage - * - * @returns {CognitoUser} the user retrieved from storage - */ - getCurrentUser() { - const lastUserKey = `CognitoIdentityServiceProvider.${this.clientId}.LastAuthUser`; - - const lastAuthUser = this.storage.getItem(lastUserKey); - if (lastAuthUser) { - const cognitoUser = { - Username: lastAuthUser, - Pool: this, - Storage: this.storage, - }; - - return new CognitoUser(cognitoUser); - } - - return null; - } - - /** - * This method returns the encoded data string used for cognito advanced security feature. - * This would be generated only when developer has included the JS used for collecting the - * data on their client. Please refer to documentation to know more about using AdvancedSecurity - * features - * @param {string} username the username for the context data - * @returns {string} the user context data - **/ - getUserContextData(username) { - if (typeof AmazonCognitoAdvancedSecurityData === 'undefined') { - return undefined; - } - /* eslint-disable */ - const amazonCognitoAdvancedSecurityDataConst = AmazonCognitoAdvancedSecurityData; - /* eslint-enable */ - - if (this.advancedSecurityDataCollectionFlag) { - const advancedSecurityData = amazonCognitoAdvancedSecurityDataConst.getData( - username, - this.userPoolId, - this.clientId - ); - if (advancedSecurityData) { - const userContextData = { - EncodedData: advancedSecurityData, - }; - return userContextData; - } - } - return {}; - } -} diff --git a/packages/amazon-cognito-identity-js/src/internals/index.js b/packages/amazon-cognito-identity-js/src/internals/index.js index f2ea4e2869e..5de97fc3cc3 100644 --- a/packages/amazon-cognito-identity-js/src/internals/index.js +++ b/packages/amazon-cognito-identity-js/src/internals/index.js @@ -4,4 +4,3 @@ export { } from '../UserAgent'; export { InternalCognitoUser } from './InternalCognitoUser'; -export { InternalCognitoUserPool } from './InternalCognitoUserPool'; diff --git a/packages/auth/package.json b/packages/auth/package.json index 765ba285906..5b11293284c 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -61,7 +61,7 @@ "name": "Auth (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Auth }", - "limit": "56.38 kB" + "limit": "56.29 kB" } ], "jest": { From 71edbc4d47592af9850b7be7618a951c7fadef80 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:41:06 -0500 Subject: [PATCH 09/16] Revert "feat: custom user agent Auth changes for UI handoff (#11606)" This reverts commit 2790f0421b70977547378108e890d442637a45ba. --- .../auth/__tests__/auth-attribute-test.ts | 3 +- packages/auth/__tests__/auth-unit-test.ts | 51 +- packages/auth/__tests__/totp-unit-test.ts | 5 +- packages/auth/internals/package.json | 8 - packages/auth/package.json | 5 +- packages/auth/src/Auth.ts | 2486 +++++++++++++- packages/auth/src/internals/InternalAuth.ts | 2901 ----------------- packages/auth/src/internals/index.ts | 3 - packages/auth/tslint.json | 7 +- packages/datastore/package.json | 2 +- 10 files changed, 2442 insertions(+), 3029 deletions(-) delete mode 100644 packages/auth/internals/package.json delete mode 100644 packages/auth/src/internals/InternalAuth.ts delete mode 100644 packages/auth/src/internals/index.ts diff --git a/packages/auth/__tests__/auth-attribute-test.ts b/packages/auth/__tests__/auth-attribute-test.ts index 10963bcfd42..0050cb6ab8f 100644 --- a/packages/auth/__tests__/auth-attribute-test.ts +++ b/packages/auth/__tests__/auth-attribute-test.ts @@ -10,7 +10,6 @@ import { } from 'amazon-cognito-identity-js'; import { AuthOptions } from '../src/types'; -import { InternalAuthClass } from '../src/internals/InternalAuth'; const authOptions: AuthOptions = { userPoolId: 'us-west-2_0xxxxxxxx', @@ -23,7 +22,7 @@ const authOptions: AuthOptions = { describe('User-Attribute-validation', () => { it('Check-non-verified-attributes', async () => { const spyonAuthUserAttributes = jest - .spyOn(InternalAuthClass.prototype, 'userAttributes') + .spyOn(Auth.prototype, 'userAttributes') .mockImplementation((user: CognitoUser) => { const emailAttribute = new CognitoUserAttribute({ Name: 'email', diff --git a/packages/auth/__tests__/auth-unit-test.ts b/packages/auth/__tests__/auth-unit-test.ts index eaf6b022370..3a03f0f98a3 100644 --- a/packages/auth/__tests__/auth-unit-test.ts +++ b/packages/auth/__tests__/auth-unit-test.ts @@ -285,7 +285,6 @@ import { Credentials, StorageHelper, Hub } from '@aws-amplify/core'; import { AuthError, NoUserPoolError } from '../src/Errors'; import { AuthErrorTypes } from '../src/types/Auth'; import { mockDeviceArray, transformedMockData } from './mockData'; -import { InternalAuthClass } from '../src/internals/InternalAuth'; const authOptions: AuthOptions = { userPoolId: 'awsUserPoolsId', @@ -1647,7 +1646,7 @@ describe('auth unit test', () => { describe('userAttributes', () => { test('happy case', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userSession') + .spyOn(Auth.prototype, 'userSession') .mockImplementationOnce(user => { return new Promise((res: any, rej) => { res('session'); @@ -1671,7 +1670,7 @@ describe('auth unit test', () => { test('get userattributes failed', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userSession') + .spyOn(Auth.prototype, 'userSession') .mockImplementationOnce(user => { return new Promise((res: any, rej) => { res('session'); @@ -1721,7 +1720,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'userSession') + .spyOn(Auth.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(session); @@ -1810,7 +1809,7 @@ describe('auth unit test', () => { }); const spyon = jest - .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') + .spyOn(Auth.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2300,7 +2299,7 @@ describe('auth unit test', () => { }); const spyon = jest - .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') + .spyOn(Auth.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2308,7 +2307,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'verifyUserAttribute') + .spyOn(Auth.prototype, 'verifyUserAttribute') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(); @@ -2319,7 +2318,7 @@ describe('auth unit test', () => { expect.assertions(2); expect(spyon).toBeCalled(); - expect(spyon2).toBeCalledWith(user, 'attr', undefined); + expect(spyon2).toBeCalledWith(user, 'attr'); spyon.mockClear(); spyon2.mockClear(); @@ -2335,7 +2334,7 @@ describe('auth unit test', () => { }); const spyon = jest - .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') + .spyOn(Auth.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2343,7 +2342,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'verifyUserAttributeSubmit') + .spyOn(Auth.prototype, 'verifyUserAttributeSubmit') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(); @@ -2415,7 +2414,7 @@ describe('auth unit test', () => { }; const spyonAuth = jest - .spyOn(InternalAuthClass.prototype, 'currentUserCredentials') + .spyOn(Auth.prototype, 'currentUserCredentials') .mockImplementationOnce(() => { return new Promise((resolve, reject) => { resolve(); @@ -2506,7 +2505,7 @@ describe('auth unit test', () => { const newPassword = 'newPassword1.'; const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userSession') + .spyOn(Auth.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(session); @@ -2836,7 +2835,7 @@ describe('auth unit test', () => { }); const spyon = jest - .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') + .spyOn(Auth.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(user); @@ -2844,7 +2843,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'userAttributes') + .spyOn(Auth.prototype, 'userAttributes') .mockImplementationOnce(() => { auth['credentials'] = { IdentityPoolId: 'identityPoolId', @@ -2903,7 +2902,7 @@ describe('auth unit test', () => { }); const spyon = jest - .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') + .spyOn(Auth.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res({ @@ -2913,7 +2912,7 @@ describe('auth unit test', () => { }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'userAttributes') + .spyOn(Auth.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res, rej) => { rej('err'); @@ -2921,7 +2920,7 @@ describe('auth unit test', () => { }); const spyon3 = jest - .spyOn(InternalAuthClass.prototype, 'currentCredentials') + .spyOn(Auth.prototype, 'currentCredentials') .mockImplementationOnce(() => { return Promise.resolve({ IdentityPoolId: 'identityPoolId', @@ -2953,7 +2952,7 @@ describe('auth unit test', () => { auth['credentials_source'] = 'aws'; const spyon = jest - .spyOn(InternalAuthClass.prototype, 'currentUserPoolUser') + .spyOn(Auth.prototype, 'currentUserPoolUser') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(null); @@ -3010,7 +3009,7 @@ describe('auth unit test', () => { }; const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userSession') + .spyOn(Auth.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise((res, rej) => { res(session); @@ -3170,7 +3169,7 @@ describe('auth unit test', () => { const attributeNames = ['email', 'phone_number']; const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userSession') + .spyOn(Auth.prototype, 'userSession') .mockImplementationOnce(() => { return new Promise(res => { res(session); @@ -3417,7 +3416,7 @@ describe('auth unit test', () => { return Promise.resolve('cred' as any); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'currentAuthenticatedUser') + .spyOn(Auth.prototype, 'currentAuthenticatedUser') .mockImplementation(() => { if (!user) return Promise.reject('error'); else return Promise.resolve(user); @@ -3491,7 +3490,7 @@ describe('auth unit test', () => { return Promise.resolve('cred' as any); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'currentAuthenticatedUser') + .spyOn(Auth.prototype, 'currentAuthenticatedUser') .mockImplementation(() => { if (!user) return Promise.reject('error'); else return Promise.resolve(user); @@ -3517,7 +3516,7 @@ describe('auth unit test', () => { describe('handleAuthResponse test', () => { beforeAll(() => { jest - .spyOn(InternalAuthClass.prototype, 'currentAuthenticatedUser') + .spyOn(Auth.prototype, 'currentAuthenticatedUser') .mockImplementation(() => { throw new Error('no user logged in'); }); @@ -3730,7 +3729,7 @@ describe('auth unit test', () => { describe('verifiedContact test', () => { test('happy case with unverified', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userAttributes') + .spyOn(Auth.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res: any, rej) => { res([ @@ -3762,7 +3761,7 @@ describe('auth unit test', () => { test('happy case with verified', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userAttributes') + .spyOn(Auth.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res: any, rej) => { res([ @@ -3802,7 +3801,7 @@ describe('auth unit test', () => { test('happy case with verified as strings', async () => { const spyon = jest - .spyOn(InternalAuthClass.prototype, 'userAttributes') + .spyOn(Auth.prototype, 'userAttributes') .mockImplementationOnce(() => { return new Promise((res: any, rej) => { res([ diff --git a/packages/auth/__tests__/totp-unit-test.ts b/packages/auth/__tests__/totp-unit-test.ts index 892de6a4587..149a06a1994 100644 --- a/packages/auth/__tests__/totp-unit-test.ts +++ b/packages/auth/__tests__/totp-unit-test.ts @@ -195,7 +195,6 @@ import { CognitoAccessToken, } from 'amazon-cognito-identity-js'; import { Hub } from '@aws-amplify/core'; -import { InternalAuthClass } from '../src/internals/InternalAuth'; const authOptions: any = { Auth: { @@ -430,7 +429,7 @@ describe('auth unit test', () => { const spyon = jest.spyOn(CognitoUser.prototype, 'setUserMfaPreference'); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'getPreferredMFA') + .spyOn(Auth.prototype, 'getPreferredMFA') .mockImplementationOnce(() => { return Promise.resolve('SMS_MFA'); }); @@ -455,7 +454,7 @@ describe('auth unit test', () => { callback(new Error('err'), null); }); const spyon2 = jest - .spyOn(InternalAuthClass.prototype, 'getPreferredMFA') + .spyOn(Auth.prototype, 'getPreferredMFA') .mockImplementationOnce(() => { return Promise.resolve('SMS_MFA'); }); diff --git a/packages/auth/internals/package.json b/packages/auth/internals/package.json deleted file mode 100644 index 5ed72d2184b..00000000000 --- a/packages/auth/internals/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@aws-amplify/auth/internals", - "types": "../lib-esm/internals/index.d.ts", - "main": "../lib/internals/index.js", - "module": "../lib-esm/internals/index.js", - "react-native": "../lib-esm/internals/index.js", - "sideEffects": false -} diff --git a/packages/auth/package.json b/packages/auth/package.json index 5b11293284c..bf5db6fb0c9 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -43,8 +43,7 @@ "files": [ "lib", "lib-esm", - "src", - "internals" + "src" ], "dependencies": { "@aws-amplify/core": "5.8.3", @@ -61,7 +60,7 @@ "name": "Auth (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Auth }", - "limit": "56.29 kB" + "limit": "55.73 kB" } ], "jest": { diff --git a/packages/auth/src/Auth.ts b/packages/auth/src/Auth.ts index 509bd752167..685fff7eaf1 100644 --- a/packages/auth/src/Auth.ts +++ b/packages/auth/src/Auth.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { + AuthOptions, FederatedResponse, SignUpParams, FederatedUser, @@ -10,33 +11,300 @@ import { CurrentUserOpts, GetPreferredMFAOpts, SignInOpts, + isUsernamePasswordOpts, + isCognitoHostedOpts, + isFederatedSignInOptions, + isFederatedSignInOptionsCustom, + hasCustomState, FederatedSignInOptionsCustom, LegacyProvider, FederatedSignInOptions, + AwsCognitoOAuthOpts, ClientMetaData, } from './types'; -import { Amplify, ICredentials } from '@aws-amplify/core'; import { + Amplify, + ConsoleLogger as Logger, + Credentials, + Hub, + StorageHelper, + ICredentials, + Platform, + browserOrNode, + parseAWSExports, + UniversalStorage, + urlSafeDecode, + HubCallback, +} from '@aws-amplify/core'; +import { + CookieStorage, + CognitoUserPool, + AuthenticationDetails, + ICognitoUserPoolData, + ICognitoUserData, ISignUpResult, CognitoUser, MFAOption, CognitoUserSession, + IAuthenticationCallback, + ICognitoUserAttributeData, CognitoUserAttribute, + CognitoIdToken, + CognitoRefreshToken, + CognitoAccessToken, + NodeCallback, + CodeDeliveryDetails, } from 'amazon-cognito-identity-js'; +import { + addAuthCategoryToCognitoUserAgent, + addFrameworkToCognitoUserAgent, +} from 'amazon-cognito-identity-js/internals'; + +import { parse } from 'url'; +import OAuth from './OAuth/OAuth'; +import { default as urlListener } from './urlListener'; +import { AuthError, NoUserPoolError } from './Errors'; +import { + AuthErrorTypes, + AutoSignInOptions, + CognitoHostedUIIdentityProvider, + IAuthDevice, +} from './types/Auth'; + +const logger = new Logger('AuthClass'); +const USER_ADMIN_SCOPE = 'aws.cognito.signin.user.admin'; + +// 10 sec, following this guide https://www.nngroup.com/articles/response-times-3-important-limits/ +const OAUTH_FLOW_MS_TIMEOUT = 10 * 1000; + +const AMPLIFY_SYMBOL = ( + typeof Symbol !== 'undefined' && typeof Symbol.for === 'function' + ? Symbol.for('amplify_default') + : '@@amplify_default' +) as Symbol; + +const dispatchAuthEvent = (event: string, data: any, message: string) => { + Hub.dispatch('auth', { event, data, message }, 'Auth', AMPLIFY_SYMBOL); +}; -import { AuthError } from './Errors'; -import { IAuthDevice } from './types/Auth'; -import { InternalAuthClass } from './internals/InternalAuth'; +// Cognito Documentation for max device +// tslint:disable-next-line:max-line-length +// https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListDevices.html#API_ListDevices_RequestSyntax +const MAX_DEVICES = 60; + +const MAX_AUTOSIGNIN_POLLING_MS = 3 * 60 * 1000; /** * Provide authentication steps */ -export class AuthClass extends InternalAuthClass { +export class AuthClass { + private _config: AuthOptions; + private userPool: CognitoUserPool = null; + private user: any = null; + private _oAuthHandler: OAuth; + private _storage; + private _storageSync; + private oAuthFlowInProgress: boolean = false; + private pendingSignIn: ReturnType | null; + private autoSignInInitiated: boolean = false; + private inflightSessionPromise: Promise | null = null; + private inflightSessionPromiseCounter: number = 0; + Credentials = Credentials; + + /** + * Initialize Auth with AWS configurations + * @param {Object} config - Configuration of the Auth + */ + constructor(config: AuthOptions) { + this.configure(config); + this.currentCredentials = this.currentCredentials.bind(this); + this.currentUserCredentials = this.currentUserCredentials.bind(this); + + Hub.listen('auth', ({ payload }) => { + const { event } = payload; + switch (event) { + case 'verify': + case 'signIn': + this._storage.setItem('amplify-signin-with-hostedUI', 'false'); + break; + case 'signOut': + this._storage.removeItem('amplify-signin-with-hostedUI'); + break; + case 'cognitoHostedUI': + this._storage.setItem('amplify-signin-with-hostedUI', 'true'); + break; + } + }); + + addAuthCategoryToCognitoUserAgent(); + addFrameworkToCognitoUserAgent(Platform.framework); + Platform.observeFrameworkChanges(() => { + addFrameworkToCognitoUserAgent(Platform.framework); + }); + } + public getModuleName() { return 'Auth'; } + configure(config?) { + if (!config) return this._config || {}; + logger.debug('configure Auth'); + const conf = Object.assign( + {}, + this._config, + parseAWSExports(config).Auth, + config + ); + this._config = conf; + const { + userPoolId, + userPoolWebClientId, + cookieStorage, + oauth, + region, + identityPoolId, + mandatorySignIn, + refreshHandlers, + identityPoolRegion, + clientMetadata, + endpoint, + storage, + } = this._config; + + if (!storage) { + // backward compatability + if (cookieStorage) this._storage = new CookieStorage(cookieStorage); + else { + this._storage = config.ssr + ? new UniversalStorage() + : new StorageHelper().getStorage(); + } + } else { + if (!this._isValidAuthStorage(storage)) { + logger.error('The storage in the Auth config is not valid!'); + throw new Error('Empty storage object'); + } + this._storage = storage; + } + + this._storageSync = Promise.resolve(); + if (typeof this._storage['sync'] === 'function') { + this._storageSync = this._storage['sync'](); + } + + if (userPoolId) { + const userPoolData: ICognitoUserPoolData = { + UserPoolId: userPoolId, + ClientId: userPoolWebClientId, + endpoint, + }; + userPoolData.Storage = this._storage; + + this.userPool = new CognitoUserPool( + userPoolData, + this.wrapRefreshSessionCallback + ); + } + + this.Credentials.configure({ + mandatorySignIn, + region, + userPoolId, + identityPoolId, + refreshHandlers, + storage: this._storage, + identityPoolRegion, + }); + + // initialize cognitoauth client if hosted ui options provided + // to keep backward compatibility: + const cognitoHostedUIConfig = oauth + ? isCognitoHostedOpts(this._config.oauth) + ? oauth + : (oauth).awsCognito + : undefined; + + if (cognitoHostedUIConfig) { + const cognitoAuthParams = Object.assign( + { + cognitoClientId: userPoolWebClientId, + UserPoolId: userPoolId, + domain: cognitoHostedUIConfig['domain'], + scopes: cognitoHostedUIConfig['scope'], + redirectSignIn: cognitoHostedUIConfig['redirectSignIn'], + redirectSignOut: cognitoHostedUIConfig['redirectSignOut'], + responseType: cognitoHostedUIConfig['responseType'], + Storage: this._storage, + urlOpener: cognitoHostedUIConfig['urlOpener'], + clientMetadata, + }, + cognitoHostedUIConfig['options'] + ); + + this._oAuthHandler = new OAuth({ + scopes: cognitoAuthParams.scopes, + config: cognitoAuthParams, + cognitoClientId: cognitoAuthParams.cognitoClientId, + }); + + // **NOTE** - Remove this in a future major release as it is a breaking change + // Prevents _handleAuthResponse from being called multiple times in Expo + // See https://github.com/aws-amplify/amplify-js/issues/4388 + const usedResponseUrls = {}; + urlListener(({ url }) => { + if (usedResponseUrls[url]) { + return; + } + + usedResponseUrls[url] = true; + this._handleAuthResponse(url); + }); + } + + dispatchAuthEvent( + 'configured', + null, + `The Auth category has been configured successfully` + ); + + if ( + !this.autoSignInInitiated && + typeof this._storage['getItem'] === 'function' + ) { + const pollingInitiated = this.isTrueStorageValue( + 'amplify-polling-started' + ); + if (pollingInitiated) { + dispatchAuthEvent( + 'autoSignIn_failure', + null, + AuthErrorTypes.AutoSignInError + ); + this._storage.removeItem('amplify-auto-sign-in'); + } + this._storage.removeItem('amplify-polling-started'); + } + return this._config; + } + + wrapRefreshSessionCallback = (callback: NodeCallback.Any) => { + const wrapped: NodeCallback.Any = (error, data) => { + if (data) { + dispatchAuthEvent('tokenRefresh', undefined, `New token retrieved`); + } else { + dispatchAuthEvent( + 'tokenRefresh_failure', + error, + `Failed to retrieve new token` + ); + } + return callback(error, data); + }; + return wrapped; + } // prettier-ignore + /** * Sign up with username, password and other attributes like phone, email * @param {String | object} params - The user attributes used for signin @@ -47,7 +315,215 @@ export class AuthClass extends InternalAuthClass { params: string | SignUpParams, ...restOfAttrs: string[] ): Promise { - return super.signUp(params, restOfAttrs); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + + let username: string = null; + let password: string = null; + const attributes: CognitoUserAttribute[] = []; + let validationData: CognitoUserAttribute[] = null; + let clientMetadata; + let autoSignIn: AutoSignInOptions = { enabled: false }; + let autoSignInValidationData = {}; + let autoSignInClientMetaData: ClientMetaData = {}; + + if (params && typeof params === 'string') { + username = params; + password = restOfAttrs ? restOfAttrs[0] : null; + const email: string = restOfAttrs ? restOfAttrs[1] : null; + const phone_number: string = restOfAttrs ? restOfAttrs[2] : null; + + if (email) + attributes.push( + new CognitoUserAttribute({ Name: 'email', Value: email }) + ); + + if (phone_number) + attributes.push( + new CognitoUserAttribute({ + Name: 'phone_number', + Value: phone_number, + }) + ); + } else if (params && typeof params === 'object') { + username = params['username']; + password = params['password']; + + if (params && params.clientMetadata) { + clientMetadata = params.clientMetadata; + } else if (this._config.clientMetadata) { + clientMetadata = this._config.clientMetadata; + } + + const attrs = params['attributes']; + if (attrs) { + Object.keys(attrs).map(key => { + attributes.push( + new CognitoUserAttribute({ Name: key, Value: attrs[key] }) + ); + }); + } + + const validationDataObject = params['validationData']; + if (validationDataObject) { + validationData = []; + Object.keys(validationDataObject).map(key => { + validationData.push( + new CognitoUserAttribute({ + Name: key, + Value: validationDataObject[key], + }) + ); + }); + } + + autoSignIn = params.autoSignIn ?? { enabled: false }; + if (autoSignIn.enabled) { + this._storage.setItem('amplify-auto-sign-in', 'true'); + autoSignInValidationData = autoSignIn.validationData ?? {}; + autoSignInClientMetaData = autoSignIn.clientMetaData ?? {}; + } + } else { + return this.rejectAuthError(AuthErrorTypes.SignUpError); + } + + if (!username) { + return this.rejectAuthError(AuthErrorTypes.EmptyUsername); + } + if (!password) { + return this.rejectAuthError(AuthErrorTypes.EmptyPassword); + } + + logger.debug('signUp attrs:', attributes); + logger.debug('signUp validation data:', validationData); + + return new Promise((resolve, reject) => { + this.userPool.signUp( + username, + password, + attributes, + validationData, + (err, data) => { + if (err) { + dispatchAuthEvent( + 'signUp_failure', + err, + `${username} failed to signup` + ); + reject(err); + } else { + dispatchAuthEvent( + 'signUp', + data, + `${username} has signed up successfully` + ); + if (autoSignIn.enabled) { + this.handleAutoSignIn( + username, + password, + autoSignInValidationData, + autoSignInClientMetaData, + data + ); + } + resolve(data); + } + }, + clientMetadata + ); + }); + } + + private handleAutoSignIn( + username: string, + password: string, + validationData: {}, + clientMetadata: any, + data: any + ) { + this.autoSignInInitiated = true; + const authDetails = new AuthenticationDetails({ + Username: username, + Password: password, + ValidationData: validationData, + ClientMetadata: clientMetadata, + }); + if (data.userConfirmed) { + this.signInAfterUserConfirmed(authDetails); + } else if (this._config.signUpVerificationMethod === 'link') { + this.handleLinkAutoSignIn(authDetails); + } else { + this.handleCodeAutoSignIn(authDetails); + } + } + + private handleCodeAutoSignIn(authDetails: AuthenticationDetails) { + const listenEvent = ({ payload }) => { + if (payload.event === 'confirmSignUp') { + this.signInAfterUserConfirmed(authDetails, listenEvent); + } + }; + Hub.listen('auth', listenEvent); + } + + private handleLinkAutoSignIn(authDetails: AuthenticationDetails) { + this._storage.setItem('amplify-polling-started', 'true'); + const start = Date.now(); + const autoSignInPollingIntervalId = setInterval(() => { + if (Date.now() - start > MAX_AUTOSIGNIN_POLLING_MS) { + clearInterval(autoSignInPollingIntervalId); + dispatchAuthEvent( + 'autoSignIn_failure', + null, + 'Please confirm your account and use your credentials to sign in.' + ); + this._storage.removeItem('amplify-auto-sign-in'); + } else { + this.signInAfterUserConfirmed( + authDetails, + null, + autoSignInPollingIntervalId + ); + } + }, 5000); + } + + private async signInAfterUserConfirmed( + authDetails: AuthenticationDetails, + listenEvent?: HubCallback, + autoSignInPollingIntervalId?: ReturnType + ) { + const user = this.createCognitoUser(authDetails.getUsername()); + try { + await user.authenticateUser( + authDetails, + this.authCallbacks( + user, + value => { + dispatchAuthEvent( + 'autoSignIn', + value, + `${authDetails.getUsername()} has signed in successfully` + ); + if (listenEvent) { + Hub.remove('auth', listenEvent); + } + if (autoSignInPollingIntervalId) { + clearInterval(autoSignInPollingIntervalId); + this._storage.removeItem('amplify-polling-started'); + } + this._storage.removeItem('amplify-auto-sign-in'); + }, + error => { + logger.error(error); + this._storage.removeItem('amplify-auto-sign-in'); + } + ) + ); + } catch (error) { + logger.error(error); + } } /** @@ -62,7 +538,61 @@ export class AuthClass extends InternalAuthClass { code: string, options?: ConfirmSignUpOptions ): Promise { - return super.confirmSignUp(username, code, options); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + if (!username) { + return this.rejectAuthError(AuthErrorTypes.EmptyUsername); + } + if (!code) { + return this.rejectAuthError(AuthErrorTypes.EmptyCode); + } + + const user = this.createCognitoUser(username); + const forceAliasCreation = + options && typeof options.forceAliasCreation === 'boolean' + ? options.forceAliasCreation + : true; + + let clientMetadata; + if (options && options.clientMetadata) { + clientMetadata = options.clientMetadata; + } else if (this._config.clientMetadata) { + clientMetadata = this._config.clientMetadata; + } + return new Promise((resolve, reject) => { + user.confirmRegistration( + code, + forceAliasCreation, + (err, data) => { + if (err) { + reject(err); + } else { + dispatchAuthEvent( + 'confirmSignUp', + data, + `${username} has been confirmed successfully` + ); + const autoSignIn = this.isTrueStorageValue('amplify-auto-sign-in'); + if (autoSignIn && !this.autoSignInInitiated) { + dispatchAuthEvent( + 'autoSignIn_failure', + null, + AuthErrorTypes.AutoSignInError + ); + this._storage.removeItem('amplify-auto-sign-in'); + } + resolve(data); + } + }, + clientMetadata + ); + }); + } + + private isTrueStorageValue(value: string) { + const item = this._storage.getItem(value); + return item ? item === 'true' : false; } /** @@ -73,9 +603,25 @@ export class AuthClass extends InternalAuthClass { */ public resendSignUp( username: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.resendSignUp(username, clientMetadata); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + if (!username) { + return this.rejectAuthError(AuthErrorTypes.EmptyUsername); + } + + const user = this.createCognitoUser(username); + return new Promise((resolve, reject) => { + user.resendConfirmationCode((err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }, clientMetadata); + }); } /** @@ -88,9 +634,191 @@ export class AuthClass extends InternalAuthClass { public signIn( usernameOrSignInOpts: string | SignInOpts, pw?: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata + ): Promise { + if (!this.userPool) { + return this.rejectNoUserPool(); + } + + let username = null; + let password = null; + let validationData = {}; + + // for backward compatibility + if (typeof usernameOrSignInOpts === 'string') { + username = usernameOrSignInOpts; + password = pw; + } else if (isUsernamePasswordOpts(usernameOrSignInOpts)) { + if (typeof pw !== 'undefined') { + logger.warn( + 'The password should be defined under the first parameter object!' + ); + } + username = usernameOrSignInOpts.username; + password = usernameOrSignInOpts.password; + validationData = usernameOrSignInOpts.validationData; + } else { + return this.rejectAuthError(AuthErrorTypes.InvalidUsername); + } + if (!username) { + return this.rejectAuthError(AuthErrorTypes.EmptyUsername); + } + const authDetails = new AuthenticationDetails({ + Username: username, + Password: password, + ValidationData: validationData, + ClientMetadata: clientMetadata, + }); + if (password) { + return this.signInWithPassword(authDetails); + } else { + return this.signInWithoutPassword(authDetails); + } + } + + /** + * Return an object with the authentication callbacks + * @param {CognitoUser} user - the cognito user object + * @param {} resolve - function called when resolving the current step + * @param {} reject - function called when rejecting the current step + * @return - an object with the callback methods for user authentication + */ + private authCallbacks( + user: CognitoUser, + resolve: (value?: CognitoUser | any) => void, + reject: (value?: any) => void + ): IAuthenticationCallback { + const that = this; + return { + onSuccess: async session => { + logger.debug(session); + delete user['challengeName']; + delete user['challengeParam']; + try { + await this.Credentials.clear(); + const cred = await this.Credentials.set(session, 'session'); + logger.debug('succeed to get cognito credentials', cred); + } catch (e) { + logger.debug('cannot get cognito credentials', e); + } finally { + try { + // In order to get user attributes and MFA methods + // We need to trigger currentUserPoolUser again + const currentUser = await this.currentUserPoolUser(); + that.user = currentUser; + dispatchAuthEvent( + 'signIn', + currentUser, + `A user ${user.getUsername()} has been signed in` + ); + resolve(currentUser); + } catch (e) { + logger.error('Failed to get the signed in user', e); + reject(e); + } + } + }, + onFailure: err => { + logger.debug('signIn failure', err); + dispatchAuthEvent( + 'signIn_failure', + err, + `${user.getUsername()} failed to signin` + ); + reject(err); + }, + customChallenge: challengeParam => { + logger.debug('signIn custom challenge answer required'); + user['challengeName'] = 'CUSTOM_CHALLENGE'; + user['challengeParam'] = challengeParam; + resolve(user); + }, + mfaRequired: (challengeName, challengeParam) => { + logger.debug('signIn MFA required'); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + mfaSetup: (challengeName, challengeParam) => { + logger.debug('signIn mfa setup', challengeName); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + newPasswordRequired: (userAttributes, requiredAttributes) => { + logger.debug('signIn new password'); + user['challengeName'] = 'NEW_PASSWORD_REQUIRED'; + user['challengeParam'] = { + userAttributes, + requiredAttributes, + }; + resolve(user); + }, + totpRequired: (challengeName, challengeParam) => { + logger.debug('signIn totpRequired'); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + selectMFAType: (challengeName, challengeParam) => { + logger.debug('signIn selectMFAType', challengeName); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + }; + } + + /** + * Sign in with a password + * @private + * @param {AuthenticationDetails} authDetails - the user sign in data + * @return - A promise resolves the CognitoUser object if success or mfa required + */ + private signInWithPassword( + authDetails: AuthenticationDetails + ): Promise { + if (this.pendingSignIn) { + throw new Error('Pending sign-in attempt already in progress'); + } + + const user = this.createCognitoUser(authDetails.getUsername()); + + this.pendingSignIn = new Promise((resolve, reject) => { + user.authenticateUser( + authDetails, + this.authCallbacks( + user, + value => { + this.pendingSignIn = null; + resolve(value); + }, + error => { + this.pendingSignIn = null; + reject(error); + } + ) + ); + }); + + return this.pendingSignIn; + } + + /** + * Sign in without a password + * @private + * @param {AuthenticationDetails} authDetails - the user sign in data + * @return - A promise resolves the CognitoUser object if success or mfa required + */ + private signInWithoutPassword( + authDetails: AuthenticationDetails ): Promise { - return super.signIn(usernameOrSignInOpts, pw, clientMetadata); + const user = this.createCognitoUser(authDetails.getUsername()); + user.setAuthenticationFlowType('CUSTOM_AUTH'); + + return new Promise((resolve, reject) => { + user.initiateAuth(authDetails, this.authCallbacks(user, resolve, reject)); + }); } /** @@ -102,7 +830,18 @@ export class AuthClass extends InternalAuthClass { * @return - A promise resolves the current preferred mfa option if success */ public getMFAOptions(user: CognitoUser | any): Promise { - return super.getMFAOptions(user); + return new Promise((res, rej) => { + user.getMFAOptions((err, mfaOptions) => { + if (err) { + logger.debug('get MFA Options failed', err); + rej(err); + return; + } + logger.debug('get MFA options success', mfaOptions); + res(mfaOptions); + return; + }); + }); } /** @@ -114,7 +853,99 @@ export class AuthClass extends InternalAuthClass { user: CognitoUser | any, params?: GetPreferredMFAOpts ): Promise { - return super.getPreferredMFA(user, params); + const that = this; + return new Promise((res, rej) => { + const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn + + const bypassCache = params ? params.bypassCache : false; + user.getUserData( + async (err, data) => { + if (err) { + logger.debug('getting preferred mfa failed', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + } + rej(err); + return; + } + + const mfaType = that._getMfaTypeFromUserData(data); + if (!mfaType) { + rej('invalid MFA Type'); + return; + } else { + res(mfaType); + return; + } + }, + { bypassCache, clientMetadata } + ); + }); + } + + private _getMfaTypeFromUserData(data) { + let ret = null; + const preferredMFA = data.PreferredMfaSetting; + // if the user has used Auth.setPreferredMFA() to setup the mfa type + // then the "PreferredMfaSetting" would exist in the response + if (preferredMFA) { + ret = preferredMFA; + } else { + // if mfaList exists but empty, then its noMFA + const mfaList = data.UserMFASettingList; + if (!mfaList) { + // if SMS was enabled by using Auth.enableSMS(), + // the response would contain MFAOptions + // as for now Cognito only supports for SMS, so we will say it is 'SMS_MFA' + // if it does not exist, then it should be NOMFA + const MFAOptions = data.MFAOptions; + if (MFAOptions) { + ret = 'SMS_MFA'; + } else { + ret = 'NOMFA'; + } + } else if (mfaList.length === 0) { + ret = 'NOMFA'; + } else { + logger.debug('invalid case for getPreferredMFA', data); + } + } + return ret; + } + + private _getUserData(user, params) { + return new Promise((res, rej) => { + user.getUserData(async (err, data) => { + if (err) { + logger.debug('getting user data failed', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + } + rej(err); + return; + } else { + res(data); + } + }, params); + }); } /** @@ -123,11 +954,118 @@ export class AuthClass extends InternalAuthClass { * @param {string} mfaMethod - preferred mfa method * @return - A promise resolve if success */ - public setPreferredMFA( + public async setPreferredMFA( user: CognitoUser | any, mfaMethod: 'TOTP' | 'SMS' | 'NOMFA' | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' ): Promise { - return super.setPreferredMFA(user, mfaMethod); + const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn + + const userData = await this._getUserData(user, { + bypassCache: true, + clientMetadata, + }); + let smsMfaSettings = null; + let totpMfaSettings = null; + + switch (mfaMethod) { + case 'TOTP': + case 'SOFTWARE_TOKEN_MFA': + totpMfaSettings = { + PreferredMfa: true, + Enabled: true, + }; + break; + case 'SMS': + case 'SMS_MFA': + smsMfaSettings = { + PreferredMfa: true, + Enabled: true, + }; + break; + case 'NOMFA': + const mfaList = userData['UserMFASettingList']; + const currentMFAType = await this._getMfaTypeFromUserData(userData); + if (currentMFAType === 'NOMFA') { + return Promise.resolve('No change for mfa type'); + } else if (currentMFAType === 'SMS_MFA') { + smsMfaSettings = { + PreferredMfa: false, + Enabled: false, + }; + } else if (currentMFAType === 'SOFTWARE_TOKEN_MFA') { + totpMfaSettings = { + PreferredMfa: false, + Enabled: false, + }; + } else { + return this.rejectAuthError(AuthErrorTypes.InvalidMFA); + } + // if there is a UserMFASettingList in the response + // we need to disable every mfa type in that list + if (mfaList && mfaList.length !== 0) { + // to disable SMS or TOTP if exists in that list + mfaList.forEach(mfaType => { + if (mfaType === 'SMS_MFA') { + smsMfaSettings = { + PreferredMfa: false, + Enabled: false, + }; + } else if (mfaType === 'SOFTWARE_TOKEN_MFA') { + totpMfaSettings = { + PreferredMfa: false, + Enabled: false, + }; + } + }); + } + break; + default: + logger.debug('no validmfa method provided'); + return this.rejectAuthError(AuthErrorTypes.NoMFA); + } + + const that = this; + return new Promise((res, rej) => { + user.setUserMfaPreference( + smsMfaSettings, + totpMfaSettings, + (err, result) => { + if (err) { + logger.debug('Set user mfa preference error', err); + return rej(err); + } + logger.debug('Set user mfa success', result); + logger.debug('Caching the latest user data into local'); + // cache the latest result into user data + user.getUserData( + async (err, data) => { + if (err) { + logger.debug('getting user data failed', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + } + return rej(err); + } else { + return res(result); + } + }, + { + bypassCache: true, + clientMetadata, + } + ); + } + ); + }); } /** @@ -137,7 +1075,18 @@ export class AuthClass extends InternalAuthClass { * @return - A promise resolves is success */ public disableSMS(user: CognitoUser): Promise { - return super.disableSMS(user); + return new Promise((res, rej) => { + user.disableMFA((err, data) => { + if (err) { + logger.debug('disable mfa failed', err); + rej(err); + return; + } + logger.debug('disable mfa succeed', data); + res(data); + return; + }); + }); } /** @@ -147,7 +1096,18 @@ export class AuthClass extends InternalAuthClass { * @return - A promise resolves is success */ public enableSMS(user: CognitoUser): Promise { - return super.enableSMS(user); + return new Promise((res, rej) => { + user.enableMFA((err, data) => { + if (err) { + logger.debug('enable mfa failed', err); + rej(err); + return; + } + logger.debug('enable mfa succeed', data); + res(data); + return; + }); + }); } /** @@ -156,7 +1116,20 @@ export class AuthClass extends InternalAuthClass { * @return - A promise resolves with the secret code if success */ public setupTOTP(user: CognitoUser | any): Promise { - return super.setupTOTP(user); + return new Promise((res, rej) => { + user.associateSoftwareToken({ + onFailure: err => { + logger.debug('associateSoftwareToken failed', err); + rej(err); + return; + }, + associateSecretCode: secretCode => { + logger.debug('associateSoftwareToken success', secretCode); + res(secretCode); + return; + }, + }); + }); } /** @@ -169,7 +1142,40 @@ export class AuthClass extends InternalAuthClass { user: CognitoUser | any, challengeAnswer: string ): Promise { - return super.verifyTotpToken(user, challengeAnswer); + logger.debug('verification totp token', user, challengeAnswer); + + let signInUserSession; + if (user && typeof user.getSignInUserSession === 'function') { + signInUserSession = (user as CognitoUser).getSignInUserSession(); + } + const isLoggedIn = signInUserSession?.isValid(); + + return new Promise((res, rej) => { + user.verifySoftwareToken(challengeAnswer, 'My TOTP device', { + onFailure: err => { + logger.debug('verifyTotpToken failed', err); + rej(err); + return; + }, + onSuccess: data => { + if (!isLoggedIn) { + dispatchAuthEvent( + 'signIn', + user, + `A user ${user.getUsername()} has been signed in` + ); + } + dispatchAuthEvent( + 'verify', + user, + `A user ${user.getUsername()} has been verified` + ); + logger.debug('verifyTotpToken success', data); + res(data); + return; + }, + }); + }); } /** @@ -181,23 +1187,117 @@ export class AuthClass extends InternalAuthClass { user: CognitoUser | any, code: string, mfaType?: 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' | null, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.confirmSignIn(user, code, mfaType, clientMetadata); + if (!code) { + return this.rejectAuthError(AuthErrorTypes.EmptyCode); + } + + const that = this; + return new Promise((resolve, reject) => { + user.sendMFACode( + code, + { + onSuccess: async session => { + logger.debug(session); + try { + await this.Credentials.clear(); + const cred = await this.Credentials.set(session, 'session'); + logger.debug('succeed to get cognito credentials', cred); + } catch (e) { + logger.debug('cannot get cognito credentials', e); + } finally { + that.user = user; + try { + const currentUser = await this.currentUserPoolUser(); + user.attributes = currentUser.attributes; + } catch (e) { + logger.debug('cannot get updated Cognito User', e); + } + dispatchAuthEvent( + 'signIn', + user, + `A user ${user.getUsername()} has been signed in` + ); + resolve(user); + } + }, + onFailure: err => { + logger.debug('confirm signIn failure', err); + reject(err); + }, + }, + mfaType, + clientMetadata + ); + }); } public completeNewPassword( user: CognitoUser | any, password: string, requiredAttributes: any = {}, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.completeNewPassword( - user, - password, - requiredAttributes, - clientMetadata - ); + if (!password) { + return this.rejectAuthError(AuthErrorTypes.EmptyPassword); + } + + const that = this; + return new Promise((resolve, reject) => { + user.completeNewPasswordChallenge( + password, + requiredAttributes, + { + onSuccess: async session => { + logger.debug(session); + try { + await this.Credentials.clear(); + const cred = await this.Credentials.set(session, 'session'); + logger.debug('succeed to get cognito credentials', cred); + } catch (e) { + logger.debug('cannot get cognito credentials', e); + } finally { + that.user = user; + dispatchAuthEvent( + 'signIn', + user, + `A user ${user.getUsername()} has been signed in` + ); + resolve(user); + } + }, + onFailure: err => { + logger.debug('completeNewPassword failure', err); + dispatchAuthEvent( + 'completeNewPassword_failure', + err, + `${this.user} failed to complete the new password flow` + ); + reject(err); + }, + mfaRequired: (challengeName, challengeParam) => { + logger.debug('signIn MFA required'); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + mfaSetup: (challengeName, challengeParam) => { + logger.debug('signIn mfa setup', challengeName); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + totpRequired: (challengeName, challengeParam) => { + logger.debug('signIn mfa setup', challengeName); + user['challengeName'] = challengeName; + user['challengeParam'] = challengeParam; + resolve(user); + }, + }, + clientMetadata + ); + }); } /** @@ -208,13 +1308,23 @@ export class AuthClass extends InternalAuthClass { public sendCustomChallengeAnswer( user: CognitoUser | any, challengeResponses: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.sendCustomChallengeAnswer( - user, - challengeResponses, - clientMetadata - ); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + if (!challengeResponses) { + return this.rejectAuthError(AuthErrorTypes.EmptyChallengeResponse); + } + + const that = this; + return new Promise((resolve, reject) => { + user.sendCustomChallengeAnswer( + challengeResponses, + this.authCallbacks(user, resolve, reject), + clientMetadata + ); + }); } /** @@ -226,7 +1336,18 @@ export class AuthClass extends InternalAuthClass { user: CognitoUser | any, attributeNames: string[] ) { - return super.deleteUserAttributes(user, attributeNames); + const that = this; + return new Promise((resolve, reject) => { + that.userSession(user).then(session => { + user.deleteAttributes(attributeNames, (err, result) => { + if (err) { + return reject(err); + } else { + return resolve(result); + } + }); + }); + }); } /** @@ -234,8 +1355,81 @@ export class AuthClass extends InternalAuthClass { * @return {Promise} **/ // TODO: Check return type void - public deleteUser(): Promise { - return super.deleteUser(); + public async deleteUser(): Promise { + try { + await this._storageSync; + } catch (e) { + logger.debug('Failed to sync cache info into memory', e); + throw new Error(e); + } + + const isSignedInHostedUI = + this._oAuthHandler && + this._storage.getItem('amplify-signin-with-hostedUI') === 'true'; + + return new Promise(async (res, rej) => { + if (this.userPool) { + const user = this.userPool.getCurrentUser(); + + if (!user) { + logger.debug('Failed to get user from user pool'); + return rej(new Error('No current user.')); + } else { + user.getSession(async (err, session) => { + if (err) { + logger.debug('Failed to get the user session', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + } + return rej(err); + } else { + user.deleteUser((err, result: string) => { + if (err) { + rej(err); + } else { + dispatchAuthEvent( + 'userDeleted', + result, + 'The authenticated user has been deleted.' + ); + user.signOut(); + this.user = null; + try { + this.cleanCachedItems(); // clean aws credentials + } catch (e) { + // TODO: change to rejects in refactor + logger.debug('failed to clear cached items'); + } + + if (isSignedInHostedUI) { + this.oAuthSignOutRedirect(res, rej); + } else { + dispatchAuthEvent( + 'signOut', + this.user, + `A user has been signed out` + ); + res(result); + } + } + }); + } + }); + } + } else { + logger.debug('no Congito User pool'); + rej(new Error('Cognito User pool does not exist')); + } + }); } /** @@ -246,9 +1440,68 @@ export class AuthClass extends InternalAuthClass { public updateUserAttributes( user: CognitoUser | any, attributes: object, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.updateUserAttributes(user, attributes, clientMetadata); + const attributeList: ICognitoUserAttributeData[] = []; + const that = this; + return new Promise((resolve, reject) => { + that.userSession(user).then(session => { + for (const key in attributes) { + if (key !== 'sub' && key.indexOf('_verified') < 0) { + const attr: ICognitoUserAttributeData = { + Name: key, + Value: attributes[key], + }; + attributeList.push(attr); + } + } + user.updateAttributes( + attributeList, + (err, result, details) => { + if (err) { + dispatchAuthEvent( + 'updateUserAttributes_failure', + err, + 'Failed to update attributes' + ); + return reject(err); + } else { + const attrs = this.createUpdateAttributesResultList( + attributes as Record, + details?.CodeDeliveryDetailsList + ); + dispatchAuthEvent( + 'updateUserAttributes', + attrs, + 'Attributes successfully updated' + ); + return resolve(result); + } + }, + clientMetadata + ); + }); + }); + } + + private createUpdateAttributesResultList( + attributes: Record, + codeDeliveryDetailsList?: CodeDeliveryDetails[] + ): Record { + const attrs = {}; + Object.keys(attributes).forEach(key => { + attrs[key] = { + isUpdated: true, + }; + const codeDeliveryDetails = codeDeliveryDetailsList?.find( + value => value.AttributeName === key + ); + if (codeDeliveryDetails) { + attrs[key].isUpdated = false; + attrs[key].codeDeliveryDetails = codeDeliveryDetails; + } + }); + return attrs; } /** @@ -259,11 +1512,137 @@ export class AuthClass extends InternalAuthClass { public userAttributes( user: CognitoUser | any ): Promise { - return super.userAttributes(user); + return new Promise((resolve, reject) => { + this.userSession(user).then(session => { + user.getUserAttributes((err, attributes) => { + if (err) { + reject(err); + } else { + resolve(attributes); + } + }); + }); + }); } public verifiedContact(user: CognitoUser | any) { - return super.verifiedContact(user); + const that = this; + return this.userAttributes(user).then(attributes => { + const attrs = that.attributesToObject(attributes); + const unverified = {}; + const verified = {}; + if (attrs['email']) { + if (attrs['email_verified']) { + verified['email'] = attrs['email']; + } else { + unverified['email'] = attrs['email']; + } + } + if (attrs['phone_number']) { + if (attrs['phone_number_verified']) { + verified['phone_number'] = attrs['phone_number']; + } else { + unverified['phone_number'] = attrs['phone_number']; + } + } + return { + verified, + unverified, + }; + }); + } + + private isErrorWithMessage(err: any): err is { message: string } { + return ( + typeof err === 'object' && + Object.prototype.hasOwnProperty.call(err, 'message') + ); + } + + // Session revoked by another app + private isTokenRevokedError( + err: any + ): err is { message: 'Access Token has been revoked' } { + return ( + this.isErrorWithMessage(err) && + err.message === 'Access Token has been revoked' + ); + } + + private isRefreshTokenRevokedError( + err: any + ): err is { message: 'Refresh Token has been revoked' } { + return ( + this.isErrorWithMessage(err) && + err.message === 'Refresh Token has been revoked' + ); + } + + private isUserDisabledError( + err: any + ): err is { message: 'User is disabled.' } { + return this.isErrorWithMessage(err) && err.message === 'User is disabled.'; + } + + private isUserDoesNotExistError( + err: any + ): err is { message: 'User does not exist.' } { + return ( + this.isErrorWithMessage(err) && err.message === 'User does not exist.' + ); + } + + private isRefreshTokenExpiredError( + err: any + ): err is { message: 'Refresh Token has expired' } { + return ( + this.isErrorWithMessage(err) && + err.message === 'Refresh Token has expired' + ); + } + + private isPasswordResetRequiredError( + err: any + ): err is { message: 'Password reset required for the user' } { + return ( + this.isErrorWithMessage(err) && + err.message === 'Password reset required for the user' + ); + } + + private isSignedInHostedUI() { + return ( + this._oAuthHandler && + this._storage.getItem('amplify-signin-with-hostedUI') === 'true' + ); + } + + private isSessionInvalid(err: any) { + return ( + this.isUserDisabledError(err) || + this.isUserDoesNotExistError(err) || + this.isTokenRevokedError(err) || + this.isRefreshTokenRevokedError(err) || + this.isRefreshTokenExpiredError(err) || + this.isPasswordResetRequiredError(err) + ); + } + + private async cleanUpInvalidSession(user: CognitoUser) { + user.signOut(); + this.user = null; + try { + await this.cleanCachedItems(); // clean aws credentials + } catch (e) { + logger.debug('failed to clear cached items'); + } + if (this.isSignedInHostedUI()) { + return new Promise((res, rej) => { + this.oAuthSignOutRedirect(res, rej); + }); + } else { + dispatchAuthEvent('signOut', this.user, `A user has been signed out`); + } } /** @@ -273,7 +1652,128 @@ export class AuthClass extends InternalAuthClass { public currentUserPoolUser( params?: CurrentUserOpts ): Promise { - return super.currentUserPoolUser(params); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + + return new Promise((res, rej) => { + this._storageSync + .then(async () => { + if (this.isOAuthInProgress()) { + logger.debug('OAuth signIn in progress, waiting for resolution...'); + + await new Promise(res => { + const timeoutId = setTimeout(() => { + logger.debug('OAuth signIn in progress timeout'); + + Hub.remove('auth', hostedUISignCallback); + + res(); + }, OAUTH_FLOW_MS_TIMEOUT); + + Hub.listen('auth', hostedUISignCallback); + + function hostedUISignCallback({ payload }) { + const { event } = payload; + + if ( + event === 'cognitoHostedUI' || + event === 'cognitoHostedUI_failure' + ) { + logger.debug(`OAuth signIn resolved: ${event}`); + clearTimeout(timeoutId); + + Hub.remove('auth', hostedUISignCallback); + + res(); + } + } + }); + } + + const user = this.userPool.getCurrentUser(); + + if (!user) { + logger.debug('Failed to get user from user pool'); + rej('No current user'); + return; + } + + // refresh the session if the session expired. + try { + const session = await this._userSession(user); + + // get user data from Cognito + const bypassCache = params ? params.bypassCache : false; + + if (bypassCache) { + await this.Credentials.clear(); + } + + const clientMetadata = this._config.clientMetadata; + + // validate the token's scope first before calling this function + const { scope = '' } = session.getAccessToken().decodePayload(); + if (scope.split(' ').includes(USER_ADMIN_SCOPE)) { + user.getUserData( + async (err, data) => { + if (err) { + logger.debug('getting user data failed', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + rej(err); + } else { + res(user); + } + return; + } + const preferredMFA = data.PreferredMfaSetting || 'NOMFA'; + const attributeList = []; + + for (let i = 0; i < data.UserAttributes.length; i++) { + const attribute = { + Name: data.UserAttributes[i].Name, + Value: data.UserAttributes[i].Value, + }; + const userAttribute = new CognitoUserAttribute(attribute); + attributeList.push(userAttribute); + } + + const attributes = this.attributesToObject(attributeList); + Object.assign(user, { attributes, preferredMFA }); + return res(user); + }, + { bypassCache, clientMetadata } + ); + } else { + logger.debug( + `Unable to get the user data because the ${USER_ADMIN_SCOPE} ` + + `is not in the scopes of the access token` + ); + return res(user); + } + } catch (err) { + rej(err); + } + }) + .catch(e => { + logger.debug('Failed to sync cache info into memory', e); + return rej(e); + }); + }); + } + + private isOAuthInProgress(): boolean { + return this.oAuthFlowInProgress; } /** @@ -281,10 +1781,54 @@ export class AuthClass extends InternalAuthClass { * @param {CurrentUserOpts} - options for getting the current user * @return - A promise resolves to current authenticated CognitoUser if success */ - public currentAuthenticatedUser( + public async currentAuthenticatedUser( params?: CurrentUserOpts ): Promise { - return super.currentAuthenticatedUser(params); + logger.debug('getting current authenticated user'); + let federatedUser = null; + try { + await this._storageSync; + } catch (e) { + logger.debug('Failed to sync cache info into memory', e); + throw e; + } + + try { + const federatedInfo = JSON.parse( + this._storage.getItem('aws-amplify-federatedInfo') + ); + if (federatedInfo) { + federatedUser = { + ...federatedInfo.user, + token: federatedInfo.token, + }; + } + } catch (e) { + logger.debug('cannot load federated user from auth storage'); + } + + if (federatedUser) { + this.user = federatedUser; + logger.debug('get current authenticated federated user', this.user); + return this.user; + } else { + logger.debug('get current authenticated userpool user'); + let user = null; + try { + user = await this.currentUserPoolUser(params); + } catch (e) { + if (e === 'No userPool') { + logger.error( + 'Cannot get the current user because the user pool is missing. ' + + 'Please make sure the Auth module is configured with a valid Cognito User Pool ID' + ); + } + logger.debug('The user is not authenticated by the error', e); + return Promise.reject('The user is not authenticated'); + } + this.user = user; + return this.user; + } } /** @@ -292,7 +1836,88 @@ export class AuthClass extends InternalAuthClass { * @return - A promise resolves to session object if success */ public currentSession(): Promise { - return super.currentSession(); + const that = this; + logger.debug('Getting current session'); + // Purposely not calling the reject method here because we don't need a console error + if (!this.userPool) { + return Promise.reject(new Error('No User Pool in the configuration.')); + } + + return new Promise((res, rej) => { + that + .currentUserPoolUser() + .then(user => { + that + .userSession(user) + .then(session => { + res(session); + return; + }) + .catch(e => { + logger.debug('Failed to get the current session', e); + rej(e); + return; + }); + }) + .catch(e => { + logger.debug('Failed to get the current user', e); + rej(e); + return; + }); + }); + } + + private async _userSession(user?: CognitoUser): Promise { + if (!user) { + logger.debug('the user is null'); + return this.rejectAuthError(AuthErrorTypes.NoUserSession); + } + const clientMetadata = this._config.clientMetadata; + // Debouncing the concurrent userSession calls by caching the promise. + // This solution assumes users will always call this function with the same CognitoUser instance. + if (this.inflightSessionPromiseCounter === 0) { + this.inflightSessionPromise = new Promise( + (res, rej) => { + user.getSession( + async (err, session) => { + if (err) { + logger.debug('Failed to get the session from user', user); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + } + rej(err); + return; + } else { + logger.debug('Succeed to get the user session', session); + res(session); + return; + } + }, + { clientMetadata } + ); + } + ); + } + this.inflightSessionPromiseCounter++; + + try { + const userSession = await this.inflightSessionPromise; + // Set private member. Avoid user.setSignInUserSession() to prevent excessive localstorage refresh. + // @ts-ignore + user.signInUserSession = userSession; + return userSession!; + } finally { + this.inflightSessionPromiseCounter--; + } } /** @@ -301,19 +1926,52 @@ export class AuthClass extends InternalAuthClass { * @return - A promise resolves to the session */ public userSession(user): Promise { - return super.userSession(user); + return this._userSession(user); } /** * Get authenticated credentials of current user. * @return - A promise resolves to be current user's credentials */ - public currentUserCredentials(): Promise { - return super.currentUserCredentials(); + public async currentUserCredentials(): Promise { + logger.debug('Getting current user credentials'); + + try { + await this._storageSync; + } catch (e) { + logger.debug('Failed to sync cache info into memory', e); + throw e; + } + + // first to check whether there is federation info in the auth storage + let federatedInfo = null; + try { + federatedInfo = JSON.parse( + this._storage.getItem('aws-amplify-federatedInfo') + ); + } catch (e) { + logger.debug('failed to get or parse item aws-amplify-federatedInfo', e); + } + + if (federatedInfo) { + // refresh the jwt token here if necessary + return this.Credentials.refreshFederatedToken(federatedInfo); + } else { + return this.currentSession() + .then(session => { + logger.debug('getting session success', session); + return this.Credentials.set(session, 'session'); + }) + .catch(() => { + logger.debug('getting guest credentials'); + return this.Credentials.set(null, 'guest'); + }); + } } public currentCredentials(): Promise { - return super.currentCredentials(); + logger.debug('getting current credentials'); + return this.Credentials.get(); } /** @@ -325,9 +1983,22 @@ export class AuthClass extends InternalAuthClass { public verifyUserAttribute( user: CognitoUser | any, attr: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.verifyUserAttribute(user, attr, clientMetadata); + return new Promise((resolve, reject) => { + user.getAttributeVerificationCode( + attr, + { + onSuccess(success) { + return resolve(success); + }, + onFailure(err) { + return reject(err); + }, + }, + clientMetadata + ); + }); } /** @@ -342,11 +2013,29 @@ export class AuthClass extends InternalAuthClass { attr: string, code: string ): Promise { - return super.verifyUserAttributeSubmit(user, attr, code); + if (!code) { + return this.rejectAuthError(AuthErrorTypes.EmptyCode); + } + + return new Promise((resolve, reject) => { + user.verifyAttribute(attr, code, { + onSuccess(data) { + resolve(data); + return; + }, + onFailure(err) { + reject(err); + return; + }, + }); + }); } public verifyCurrentUserAttribute(attr: string): Promise { - return super.verifyCurrentUserAttribute(attr); + const that = this; + return that + .currentUserPoolUser() + .then(user => that.verifyUserAttribute(user, attr)); } /** @@ -359,7 +2048,105 @@ export class AuthClass extends InternalAuthClass { attr: string, code: string ): Promise { - return super.verifyCurrentUserAttributeSubmit(attr, code); + const that = this; + return that + .currentUserPoolUser() + .then(user => that.verifyUserAttributeSubmit(user, attr, code)); + } + + private async cognitoIdentitySignOut( + opts: SignOutOpts, + user: CognitoUser | any + ) { + try { + await this._storageSync; + } catch (e) { + logger.debug('Failed to sync cache info into memory', e); + throw e; + } + + const isSignedInHostedUI = + this._oAuthHandler && + this._storage.getItem('amplify-signin-with-hostedUI') === 'true'; + + return new Promise((res, rej) => { + if (opts && opts.global) { + logger.debug('user global sign out', user); + // in order to use global signout + // we must validate the user as an authenticated user by using getSession + const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn + + user.getSession( + async (err, result) => { + if (err) { + logger.debug('failed to get the user session', err); + if (this.isSessionInvalid(err)) { + try { + await this.cleanUpInvalidSession(user); + } catch (cleanUpError) { + rej( + new Error( + `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` + ) + ); + return; + } + } + return rej(err); + } + user.globalSignOut({ + onSuccess: data => { + logger.debug('global sign out success'); + if (isSignedInHostedUI) { + this.oAuthSignOutRedirect(res, rej); + } else { + return res(); + } + }, + onFailure: err => { + logger.debug('global sign out failed', err); + return rej(err); + }, + }); + }, + { clientMetadata } + ); + } else { + logger.debug('user sign out', user); + user.signOut(() => { + if (isSignedInHostedUI) { + this.oAuthSignOutRedirect(res, rej); + } else { + return res(); + } + }); + } + }); + } + + private oAuthSignOutRedirect( + resolve: () => void, + reject: (reason?: any) => void + ) { + const { isBrowser } = browserOrNode(); + + if (isBrowser) { + this.oAuthSignOutRedirectOrReject(reject); + } else { + this.oAuthSignOutAndResolve(resolve); + } + } + + private oAuthSignOutAndResolve(resolve: () => void) { + this._oAuthHandler.signOut(); + resolve(); + } + + private oAuthSignOutRedirectOrReject(reject: (reason?: any) => void) { + this._oAuthHandler.signOut(); // this method redirects url + + // App should be redirected to another url otherwise it will reject + setTimeout(() => reject(Error('Signout timeout fail')), 3000); } /** @@ -367,8 +2154,37 @@ export class AuthClass extends InternalAuthClass { * @ * @return - A promise resolved if success */ - public signOut(opts?: SignOutOpts): Promise { - return super.signOut(opts); + public async signOut(opts?: SignOutOpts): Promise { + try { + await this.cleanCachedItems(); + } catch (e) { + logger.debug('failed to clear cached items'); + } + + if (this.userPool) { + const user = this.userPool.getCurrentUser(); + if (user) { + await this.cognitoIdentitySignOut(opts, user); + } else { + logger.debug('no current Cognito user'); + } + } else { + logger.debug('no Cognito User pool'); + } + + /** + * Note for future refactor - no reliable way to get username with + * Cognito User Pools vs Identity when federating with Social Providers + * This is why we need a well structured session object that can be inspected + * and information passed back in the message below for Hub dispatch + */ + dispatchAuthEvent('signOut', this.user, `A user has been signed out`); + this.user = null; + } + + private async cleanCachedItems() { + // clear cognito cached item + await this.Credentials.clear(); } /** @@ -382,9 +2198,25 @@ export class AuthClass extends InternalAuthClass { user: CognitoUser | any, oldPassword: string, newPassword: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise<'SUCCESS'> { - return super.changePassword(user, oldPassword, newPassword, clientMetadata); + return new Promise((resolve, reject) => { + this.userSession(user).then(session => { + user.changePassword( + oldPassword, + newPassword, + (err, data) => { + if (err) { + logger.debug('change password failure', err); + return reject(err); + } else { + return resolve(data); + } + }, + clientMetadata + ); + }); + }); } /** @@ -394,9 +2226,46 @@ export class AuthClass extends InternalAuthClass { */ public forgotPassword( username: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.forgotPassword(username, clientMetadata); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + if (!username) { + return this.rejectAuthError(AuthErrorTypes.EmptyUsername); + } + + const user = this.createCognitoUser(username); + return new Promise((resolve, reject) => { + user.forgotPassword( + { + onSuccess: () => { + resolve(); + return; + }, + onFailure: err => { + logger.debug('forgot password failure', err); + dispatchAuthEvent( + 'forgotPassword_failure', + err, + `${username} forgotPassword failed` + ); + reject(err); + return; + }, + inputVerificationCode: data => { + dispatchAuthEvent( + 'forgotPassword', + user, + `${username} has initiated forgot password flow` + ); + resolve(data); + return; + }, + }, + clientMetadata + ); + }); } /** @@ -410,9 +2279,49 @@ export class AuthClass extends InternalAuthClass { username: string, code: string, password: string, - clientMetadata?: ClientMetaData + clientMetadata: ClientMetaData = this._config.clientMetadata ): Promise { - return super.forgotPasswordSubmit(username, code, password, clientMetadata); + if (!this.userPool) { + return this.rejectNoUserPool(); + } + if (!username) { + return this.rejectAuthError(AuthErrorTypes.EmptyUsername); + } + if (!code) { + return this.rejectAuthError(AuthErrorTypes.EmptyCode); + } + if (!password) { + return this.rejectAuthError(AuthErrorTypes.EmptyPassword); + } + + const user = this.createCognitoUser(username); + return new Promise((resolve, reject) => { + user.confirmPassword( + code, + password, + { + onSuccess: success => { + dispatchAuthEvent( + 'forgotPasswordSubmit', + user, + `${username} forgotPasswordSubmit successful` + ); + resolve(success); + return; + }, + onFailure: err => { + dispatchAuthEvent( + 'forgotPasswordSubmit_failure', + err, + `${username} forgotPasswordSubmit failed` + ); + reject(err); + return; + }, + }, + clientMetadata + ); + }); } /** @@ -420,22 +2329,60 @@ export class AuthClass extends InternalAuthClass { * @async * @return {Object }- current User's information */ - public currentUserInfo() { - return super.currentUserInfo(); + public async currentUserInfo() { + const source = this.Credentials.getCredSource(); + + if (!source || source === 'aws' || source === 'userPool') { + const user = await this.currentUserPoolUser().catch(err => + logger.error(err) + ); + if (!user) { + return null; + } + + try { + const attributes = await this.userAttributes(user); + const userAttrs: object = this.attributesToObject(attributes); + let credentials = null; + try { + credentials = await this.currentCredentials(); + } catch (e) { + logger.debug( + 'Failed to retrieve credentials while getting current user info', + e + ); + } + + const info = { + id: credentials ? credentials.identityId : undefined, + username: user.getUsername(), + attributes: userAttrs, + }; + return info; + } catch (err) { + logger.error('currentUserInfo error', err); + return {}; + } + } + + if (source === 'federated') { + const user = this.user; + return user ? user : {}; + } } - public federatedSignIn( + public async federatedSignIn( options?: FederatedSignInOptions ): Promise; - public federatedSignIn( + public async federatedSignIn( provider: LegacyProvider, response: FederatedResponse, user: FederatedUser ): Promise; - public federatedSignIn( + public async federatedSignIn( options?: FederatedSignInOptionsCustom ): Promise; - public federatedSignIn( + public async federatedSignIn( providerOrOptions: | LegacyProvider | FederatedSignInOptions @@ -443,7 +2390,230 @@ export class AuthClass extends InternalAuthClass { response?: FederatedResponse, user?: FederatedUser ): Promise { - return super.federatedSignIn(providerOrOptions, response, user); + if (!this._config.identityPoolId && !this._config.userPoolId) { + throw new Error( + `Federation requires either a User Pool or Identity Pool in config` + ); + } + + // Ensure backwards compatability + if (typeof providerOrOptions === 'undefined') { + if (this._config.identityPoolId && !this._config.userPoolId) { + throw new Error( + `Federation with Identity Pools requires tokens passed as arguments` + ); + } + } + + if ( + isFederatedSignInOptions(providerOrOptions) || + isFederatedSignInOptionsCustom(providerOrOptions) || + hasCustomState(providerOrOptions) || + typeof providerOrOptions === 'undefined' + ) { + const options = providerOrOptions || { + provider: CognitoHostedUIIdentityProvider.Cognito, + }; + const provider = isFederatedSignInOptions(options) + ? options.provider + : (options as FederatedSignInOptionsCustom).customProvider; + + const customState = isFederatedSignInOptions(options) + ? options.customState + : (options as FederatedSignInOptionsCustom).customState; + + if (this._config.userPoolId) { + const client_id = isCognitoHostedOpts(this._config.oauth) + ? this._config.userPoolWebClientId + : this._config.oauth.clientID; + /*Note: Invenstigate automatically adding trailing slash */ + const redirect_uri = isCognitoHostedOpts(this._config.oauth) + ? this._config.oauth.redirectSignIn + : this._config.oauth.redirectUri; + + this._oAuthHandler.oauthSignIn( + this._config.oauth.responseType, + this._config.oauth.domain, + redirect_uri, + client_id, + provider, + customState + ); + } + } else { + const provider = providerOrOptions; + // To check if the user is already logged in + try { + const loggedInUser = JSON.stringify( + JSON.parse(this._storage.getItem('aws-amplify-federatedInfo')).user + ); + if (loggedInUser) { + logger.warn(`There is already a signed in user: ${loggedInUser} in your app. + You should not call Auth.federatedSignIn method again as it may cause unexpected behavior.`); + } + } catch (e) {} + + const { token, identity_id, expires_at } = response; + // Because this.Credentials.set would update the user info with identity id + // So we need to retrieve the user again. + const credentials = await this.Credentials.set( + { provider, token, identity_id, user, expires_at }, + 'federation' + ); + const currentUser = await this.currentAuthenticatedUser(); + dispatchAuthEvent( + 'signIn', + currentUser, + `A user ${currentUser.username} has been signed in` + ); + logger.debug('federated sign in credentials', credentials); + return credentials; + } + } + + /** + * Used to complete the OAuth flow with or without the Cognito Hosted UI + * @param {String} URL - optional parameter for customers to pass in the response URL + */ + private async _handleAuthResponse(URL?: string) { + if (this.oAuthFlowInProgress) { + logger.debug(`Skipping URL ${URL} current flow in progress`); + return; + } + + try { + this.oAuthFlowInProgress = true; + if (!this._config.userPoolId) { + throw new Error( + `OAuth responses require a User Pool defined in config` + ); + } + + dispatchAuthEvent( + 'parsingCallbackUrl', + { url: URL }, + `The callback url is being parsed` + ); + + const currentUrl = + URL || (browserOrNode().isBrowser ? window.location.href : ''); + + const hasCodeOrError = !!(parse(currentUrl).query || '') + .split('&') + .map(entry => entry.split('=')) + .find(([k]) => k === 'code' || k === 'error'); + + const hasTokenOrError = !!(parse(currentUrl).hash || '#') + .substr(1) + .split('&') + .map(entry => entry.split('=')) + .find(([k]) => k === 'access_token' || k === 'error'); + + if (hasCodeOrError || hasTokenOrError) { + this._storage.setItem('amplify-redirected-from-hosted-ui', 'true'); + try { + const { accessToken, idToken, refreshToken, state } = + await this._oAuthHandler.handleAuthResponse(currentUrl); + const session = new CognitoUserSession({ + IdToken: new CognitoIdToken({ IdToken: idToken }), + RefreshToken: new CognitoRefreshToken({ + RefreshToken: refreshToken, + }), + AccessToken: new CognitoAccessToken({ + AccessToken: accessToken, + }), + }); + + let credentials; + // Get AWS Credentials & store if Identity Pool is defined + if (this._config.identityPoolId) { + credentials = await this.Credentials.set(session, 'session'); + logger.debug('AWS credentials', credentials); + } + + /* + Prior to the request we do sign the custom state along with the state we set. This check will verify + if there is a dash indicated when setting custom state from the request. If a dash is contained + then there is custom state present on the state string. + */ + const isCustomStateIncluded = /-/.test(state); + + /* + The following is to create a user for the Cognito Identity SDK to store the tokens + When we remove this SDK later that logic will have to be centralized in our new version + */ + //#region + const currentUser = this.createCognitoUser( + session.getIdToken().decodePayload()['cognito:username'] + ); + + // This calls cacheTokens() in Cognito SDK + currentUser.setSignInUserSession(session); + + if (window && typeof window.history !== 'undefined') { + window.history.replaceState( + {}, + null, + (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn + ); + } + + dispatchAuthEvent( + 'signIn', + currentUser, + `A user ${currentUser.getUsername()} has been signed in` + ); + dispatchAuthEvent( + 'cognitoHostedUI', + currentUser, + `A user ${currentUser.getUsername()} has been signed in via Cognito Hosted UI` + ); + + if (isCustomStateIncluded) { + const customState = state.split('-').splice(1).join('-'); + + dispatchAuthEvent( + 'customOAuthState', + urlSafeDecode(customState), + `State for user ${currentUser.getUsername()}` + ); + } + //#endregion + + return credentials; + } catch (err) { + logger.debug('Error in cognito hosted auth response', err); + + // Just like a successful handling of `?code`, replace the window history to "dispose" of the `code`. + // Otherwise, reloading the page will throw errors as the `code` has already been spent. + if (window && typeof window.history !== 'undefined') { + window.history.replaceState( + {}, + null, + (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn + ); + } + + dispatchAuthEvent( + 'signIn_failure', + err, + `The OAuth response flow failed` + ); + dispatchAuthEvent( + 'cognitoHostedUI_failure', + err, + `A failure occurred when returning to the Cognito Hosted UI` + ); + dispatchAuthEvent( + 'customState_failure', + err, + `A failure occurred when returning state` + ); + } + } + } finally { + this.oAuthFlowInProgress = false; + } } /** @@ -452,21 +2622,185 @@ export class AuthClass extends InternalAuthClass { * @return {Object} - Credentials */ public essentialCredentials(credentials): ICredentials { - return super.essentialCredentials(credentials); + return { + accessKeyId: credentials.accessKeyId, + sessionToken: credentials.sessionToken, + secretAccessKey: credentials.secretAccessKey, + identityId: credentials.identityId, + authenticated: credentials.authenticated, + }; + } + + private attributesToObject(attributes) { + const obj = {}; + if (attributes) { + attributes.map(attribute => { + if ( + attribute.Name === 'email_verified' || + attribute.Name === 'phone_number_verified' + ) { + obj[attribute.Name] = + this.isTruthyString(attribute.Value) || attribute.Value === true; + } else { + obj[attribute.Name] = attribute.Value; + } + }); + } + return obj; + } + + private isTruthyString(value: any): boolean { + return ( + typeof value.toLowerCase === 'function' && value.toLowerCase() === 'true' + ); + } + + private createCognitoUser(username: string): CognitoUser { + const userData: ICognitoUserData = { + Username: username, + Pool: this.userPool, + }; + userData.Storage = this._storage; + + const { authenticationFlowType } = this._config; + + const user = new CognitoUser(userData); + if (authenticationFlowType) { + user.setAuthenticationFlowType(authenticationFlowType); + } + return user; + } + + private _isValidAuthStorage(obj) { + // We need to check if the obj has the functions of Storage + return ( + !!obj && + typeof obj.getItem === 'function' && + typeof obj.setItem === 'function' && + typeof obj.removeItem === 'function' && + typeof obj.clear === 'function' + ); + } + + private noUserPoolErrorHandler(config: AuthOptions): AuthErrorTypes { + if (config) { + if (!config.userPoolId || !config.identityPoolId) { + return AuthErrorTypes.MissingAuthConfig; + } + } + return AuthErrorTypes.NoConfig; } - public rememberDevice(): Promise { - return super.rememberDevice(); + private rejectAuthError(type: AuthErrorTypes): Promise { + return Promise.reject(new AuthError(type)); } - public forgetDevice(): Promise { - return super.forgetDevice(); + private rejectNoUserPool(): Promise { + const type = this.noUserPoolErrorHandler(this._config); + return Promise.reject(new NoUserPoolError(type)); } - public fetchDevices(): Promise { - return super.fetchDevices(); + public async rememberDevice(): Promise { + let currUser; + + try { + currUser = await this.currentUserPoolUser(); + } catch (error) { + logger.debug('The user is not authenticated by the error', error); + return Promise.reject('The user is not authenticated'); + } + + currUser.getCachedDeviceKeyAndPassword(); + return new Promise((res, rej) => { + currUser.setDeviceStatusRemembered({ + onSuccess: data => { + res(data); + }, + onFailure: err => { + if (err.code === 'InvalidParameterException') { + rej(new AuthError(AuthErrorTypes.DeviceConfig)); + } else if (err.code === 'NetworkError') { + rej(new AuthError(AuthErrorTypes.NetworkError)); + } else { + rej(err); + } + }, + }); + }); + } + + public async forgetDevice(): Promise { + let currUser; + + try { + currUser = await this.currentUserPoolUser(); + } catch (error) { + logger.debug('The user is not authenticated by the error', error); + return Promise.reject('The user is not authenticated'); + } + + currUser.getCachedDeviceKeyAndPassword(); + return new Promise((res, rej) => { + currUser.forgetDevice({ + onSuccess: data => { + res(data); + }, + onFailure: err => { + if (err.code === 'InvalidParameterException') { + rej(new AuthError(AuthErrorTypes.DeviceConfig)); + } else if (err.code === 'NetworkError') { + rej(new AuthError(AuthErrorTypes.NetworkError)); + } else { + rej(err); + } + }, + }); + }); + } + + public async fetchDevices(): Promise { + let currUser; + + try { + currUser = await this.currentUserPoolUser(); + } catch (error) { + logger.debug('The user is not authenticated by the error', error); + throw new Error('The user is not authenticated'); + } + + currUser.getCachedDeviceKeyAndPassword(); + return new Promise((res, rej) => { + const cb = { + onSuccess(data) { + const deviceList: IAuthDevice[] = data.Devices.map(device => { + const deviceName = + device.DeviceAttributes.find( + ({ Name }) => Name === 'device_name' + ) || {}; + + const deviceInfo: IAuthDevice = { + id: device.DeviceKey, + name: deviceName.Value, + }; + return deviceInfo; + }); + res(deviceList); + }, + onFailure: err => { + if (err.code === 'InvalidParameterException') { + rej(new AuthError(AuthErrorTypes.DeviceConfig)); + } else if (err.code === 'NetworkError') { + rej(new AuthError(AuthErrorTypes.NetworkError)); + } else { + rej(err); + } + }, + }; + currUser.listDevices(MAX_DEVICES, null, cb); + }); } } export const Auth = new AuthClass(null); + Amplify.register(Auth); diff --git a/packages/auth/src/internals/InternalAuth.ts b/packages/auth/src/internals/InternalAuth.ts deleted file mode 100644 index b25bfc6a90e..00000000000 --- a/packages/auth/src/internals/InternalAuth.ts +++ /dev/null @@ -1,2901 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - AuthOptions, - FederatedResponse, - SignUpParams, - FederatedUser, - ConfirmSignUpOptions, - SignOutOpts, - CurrentUserOpts, - GetPreferredMFAOpts, - SignInOpts, - isUsernamePasswordOpts, - isCognitoHostedOpts, - isFederatedSignInOptions, - isFederatedSignInOptionsCustom, - hasCustomState, - FederatedSignInOptionsCustom, - LegacyProvider, - FederatedSignInOptions, - AwsCognitoOAuthOpts, - ClientMetaData, -} from '../types'; - -import { - Amplify, - ConsoleLogger as Logger, - Credentials, - CustomUserAgentDetails, - Hub, - StorageHelper, - ICredentials, - Platform, - browserOrNode, - parseAWSExports, - UniversalStorage, - urlSafeDecode, - HubCallback, -} from '@aws-amplify/core'; -import { - CookieStorage, - CognitoUserPool, - AuthenticationDetails, - ICognitoUserPoolData, - ICognitoUserData, - ISignUpResult, - CognitoUser, - MFAOption, - CognitoUserSession, - IAuthenticationCallback, - ICognitoUserAttributeData, - CognitoUserAttribute, - CognitoIdToken, - CognitoRefreshToken, - CognitoAccessToken, - NodeCallback, - CodeDeliveryDetails, -} from 'amazon-cognito-identity-js'; -import { - addAuthCategoryToCognitoUserAgent, - addFrameworkToCognitoUserAgent, -} from 'amazon-cognito-identity-js/internals'; - -import { parse } from 'url'; -import OAuth from '../OAuth/OAuth'; -import { default as urlListener } from '../urlListener'; -import { AuthError, NoUserPoolError } from '../Errors'; -import { - AuthErrorTypes, - AutoSignInOptions, - CognitoHostedUIIdentityProvider, - IAuthDevice, -} from '../types/Auth'; - -const logger = new Logger('AuthClass'); -const USER_ADMIN_SCOPE = 'aws.cognito.signin.user.admin'; - -// 10 sec, following this guide https://www.nngroup.com/articles/response-times-3-important-limits/ -const OAUTH_FLOW_MS_TIMEOUT = 10 * 1000; - -const AMPLIFY_SYMBOL = ( - typeof Symbol !== 'undefined' && typeof Symbol.for === 'function' - ? Symbol.for('amplify_default') - : '@@amplify_default' -) as Symbol; - -const dispatchAuthEvent = (event: string, data: any, message: string) => { - Hub.dispatch('auth', { event, data, message }, 'Auth', AMPLIFY_SYMBOL); -}; - -// Cognito Documentation for max device -// tslint:disable-next-line:max-line-length -// https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListDevices.html#API_ListDevices_RequestSyntax -const MAX_DEVICES = 60; - -const MAX_AUTOSIGNIN_POLLING_MS = 3 * 60 * 1000; - -/** - * Provide authentication steps - */ -export class InternalAuthClass { - private _config: AuthOptions; - private userPool: CognitoUserPool = null; - private user: any = null; - private _oAuthHandler: OAuth; - private _storage; - private _storageSync; - private oAuthFlowInProgress: boolean = false; - private pendingSignIn: ReturnType< - InternalAuthClass['signInWithPassword'] - > | null; - private autoSignInInitiated: boolean = false; - private inflightSessionPromise: Promise | null = null; - private inflightSessionPromiseCounter: number = 0; - Credentials = Credentials; - - /** - * Initialize Auth with AWS configurations - * @param {Object} config - Configuration of the Auth - */ - constructor(config: AuthOptions) { - this.configure(config); - this.currentCredentials = this.currentCredentials.bind(this); - this.currentUserCredentials = this.currentUserCredentials.bind(this); - - Hub.listen('auth', ({ payload }) => { - const { event } = payload; - switch (event) { - case 'verify': - case 'signIn': - this._storage.setItem('amplify-signin-with-hostedUI', 'false'); - break; - case 'signOut': - this._storage.removeItem('amplify-signin-with-hostedUI'); - break; - case 'cognitoHostedUI': - this._storage.setItem('amplify-signin-with-hostedUI', 'true'); - break; - } - }); - - addAuthCategoryToCognitoUserAgent(); - addFrameworkToCognitoUserAgent(Platform.framework); - Platform.observeFrameworkChanges(() => { - addFrameworkToCognitoUserAgent(Platform.framework); - }); - } - - public getModuleName() { - return 'InternalAuth'; - } - - configure(config?) { - if (!config) return this._config || {}; - logger.debug('configure Auth'); - const conf = Object.assign( - {}, - this._config, - parseAWSExports(config).Auth, - config - ); - this._config = conf; - const { - userPoolId, - userPoolWebClientId, - cookieStorage, - oauth, - region, - identityPoolId, - mandatorySignIn, - refreshHandlers, - identityPoolRegion, - clientMetadata, - endpoint, - storage, - } = this._config; - - if (!storage) { - // backward compatability - if (cookieStorage) this._storage = new CookieStorage(cookieStorage); - else { - this._storage = config.ssr - ? new UniversalStorage() - : new StorageHelper().getStorage(); - } - } else { - if (!this._isValidAuthStorage(storage)) { - logger.error('The storage in the Auth config is not valid!'); - throw new Error('Empty storage object'); - } - this._storage = storage; - } - - this._storageSync = Promise.resolve(); - if (typeof this._storage['sync'] === 'function') { - this._storageSync = this._storage['sync'](); - } - - if (userPoolId) { - const userPoolData: ICognitoUserPoolData = { - UserPoolId: userPoolId, - ClientId: userPoolWebClientId, - endpoint, - }; - userPoolData.Storage = this._storage; - - this.userPool = new CognitoUserPool( - userPoolData, - this.wrapRefreshSessionCallback - ); - } - - this.Credentials.configure({ - mandatorySignIn, - region, - userPoolId, - identityPoolId, - refreshHandlers, - storage: this._storage, - identityPoolRegion, - }); - - // initialize cognitoauth client if hosted ui options provided - // to keep backward compatibility: - const cognitoHostedUIConfig = oauth - ? isCognitoHostedOpts(this._config.oauth) - ? oauth - : (oauth).awsCognito - : undefined; - - if (cognitoHostedUIConfig) { - const cognitoAuthParams = Object.assign( - { - cognitoClientId: userPoolWebClientId, - UserPoolId: userPoolId, - domain: cognitoHostedUIConfig['domain'], - scopes: cognitoHostedUIConfig['scope'], - redirectSignIn: cognitoHostedUIConfig['redirectSignIn'], - redirectSignOut: cognitoHostedUIConfig['redirectSignOut'], - responseType: cognitoHostedUIConfig['responseType'], - Storage: this._storage, - urlOpener: cognitoHostedUIConfig['urlOpener'], - clientMetadata, - }, - cognitoHostedUIConfig['options'] - ); - - this._oAuthHandler = new OAuth({ - scopes: cognitoAuthParams.scopes, - config: cognitoAuthParams, - cognitoClientId: cognitoAuthParams.cognitoClientId, - }); - - // **NOTE** - Remove this in a future major release as it is a breaking change - // Prevents _handleAuthResponse from being called multiple times in Expo - // See https://github.com/aws-amplify/amplify-js/issues/4388 - const usedResponseUrls = {}; - urlListener(({ url }) => { - if (usedResponseUrls[url]) { - return; - } - - usedResponseUrls[url] = true; - this._handleAuthResponse(url); - }); - } - - dispatchAuthEvent( - 'configured', - null, - `The Auth category has been configured successfully` - ); - - if ( - !this.autoSignInInitiated && - typeof this._storage['getItem'] === 'function' - ) { - const pollingInitiated = this.isTrueStorageValue( - 'amplify-polling-started' - ); - if (pollingInitiated) { - dispatchAuthEvent( - 'autoSignIn_failure', - null, - AuthErrorTypes.AutoSignInError - ); - this._storage.removeItem('amplify-auto-sign-in'); - } - this._storage.removeItem('amplify-polling-started'); - } - return this._config; - } - - wrapRefreshSessionCallback = (callback: NodeCallback.Any) => { - const wrapped: NodeCallback.Any = (error, data) => { - if (data) { - dispatchAuthEvent('tokenRefresh', undefined, `New token retrieved`); - } else { - dispatchAuthEvent( - 'tokenRefresh_failure', - error, - `Failed to retrieve new token` - ); - } - return callback(error, data); - }; - return wrapped; - } // prettier-ignore - - /** - * Sign up with username, password and other attributes like phone, email - * @param {String | object} params - The user attributes used for signin - * @param {String[]} restOfAttrs - for the backward compatability - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves callback data if success - */ - public signUp( - params: string | SignUpParams, - ...restOfAttrs: any - ): Promise; - public signUp( - params: string | SignUpParams, - restOfAttrs?: string[], - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - - let username: string = null; - let password: string = null; - const attributes: CognitoUserAttribute[] = []; - let validationData: CognitoUserAttribute[] = null; - let clientMetadata; - let autoSignIn: AutoSignInOptions = { enabled: false }; - let autoSignInValidationData = {}; - let autoSignInClientMetaData: ClientMetaData = {}; - - if (params && typeof params === 'string') { - username = params; - password = restOfAttrs ? restOfAttrs[0] : null; - const email: string = restOfAttrs ? restOfAttrs[1] : null; - const phone_number: string = restOfAttrs ? restOfAttrs[2] : null; - - if (email) - attributes.push( - new CognitoUserAttribute({ Name: 'email', Value: email }) - ); - - if (phone_number) - attributes.push( - new CognitoUserAttribute({ - Name: 'phone_number', - Value: phone_number, - }) - ); - } else if (params && typeof params === 'object') { - username = params['username']; - password = params['password']; - - if (params && params.clientMetadata) { - clientMetadata = params.clientMetadata; - } else if (this._config.clientMetadata) { - clientMetadata = this._config.clientMetadata; - } - - const attrs = params['attributes']; - if (attrs) { - Object.keys(attrs).map(key => { - attributes.push( - new CognitoUserAttribute({ Name: key, Value: attrs[key] }) - ); - }); - } - - const validationDataObject = params['validationData']; - if (validationDataObject) { - validationData = []; - Object.keys(validationDataObject).map(key => { - validationData.push( - new CognitoUserAttribute({ - Name: key, - Value: validationDataObject[key], - }) - ); - }); - } - - autoSignIn = params.autoSignIn ?? { enabled: false }; - if (autoSignIn.enabled) { - this._storage.setItem('amplify-auto-sign-in', 'true'); - autoSignInValidationData = autoSignIn.validationData ?? {}; - autoSignInClientMetaData = autoSignIn.clientMetaData ?? {}; - } - } else { - return this.rejectAuthError(AuthErrorTypes.SignUpError); - } - - if (!username) { - return this.rejectAuthError(AuthErrorTypes.EmptyUsername); - } - if (!password) { - return this.rejectAuthError(AuthErrorTypes.EmptyPassword); - } - - logger.debug('signUp attrs:', attributes); - logger.debug('signUp validation data:', validationData); - - return new Promise((resolve, reject) => { - this.userPool.signUp( - username, - password, - attributes, - validationData, - (err, data) => { - if (err) { - dispatchAuthEvent( - 'signUp_failure', - err, - `${username} failed to signup` - ); - reject(err); - } else { - dispatchAuthEvent( - 'signUp', - data, - `${username} has signed up successfully` - ); - if (autoSignIn.enabled) { - this.handleAutoSignIn( - username, - password, - autoSignInValidationData, - autoSignInClientMetaData, - data - ); - } - resolve(data); - } - }, - clientMetadata - ); - }); - } - - private handleAutoSignIn( - username: string, - password: string, - validationData: {}, - clientMetadata: any, - data: any - ) { - this.autoSignInInitiated = true; - const authDetails = new AuthenticationDetails({ - Username: username, - Password: password, - ValidationData: validationData, - ClientMetadata: clientMetadata, - }); - if (data.userConfirmed) { - this.signInAfterUserConfirmed(authDetails); - } else if (this._config.signUpVerificationMethod === 'link') { - this.handleLinkAutoSignIn(authDetails); - } else { - this.handleCodeAutoSignIn(authDetails); - } - } - - private handleCodeAutoSignIn(authDetails: AuthenticationDetails) { - const listenEvent = ({ payload }) => { - if (payload.event === 'confirmSignUp') { - this.signInAfterUserConfirmed(authDetails, listenEvent); - } - }; - Hub.listen('auth', listenEvent); - } - - private handleLinkAutoSignIn(authDetails: AuthenticationDetails) { - this._storage.setItem('amplify-polling-started', 'true'); - const start = Date.now(); - const autoSignInPollingIntervalId = setInterval(() => { - if (Date.now() - start > MAX_AUTOSIGNIN_POLLING_MS) { - clearInterval(autoSignInPollingIntervalId); - dispatchAuthEvent( - 'autoSignIn_failure', - null, - 'Please confirm your account and use your credentials to sign in.' - ); - this._storage.removeItem('amplify-auto-sign-in'); - } else { - this.signInAfterUserConfirmed( - authDetails, - null, - autoSignInPollingIntervalId - ); - } - }, 5000); - } - - private async signInAfterUserConfirmed( - authDetails: AuthenticationDetails, - listenEvent?: HubCallback, - autoSignInPollingIntervalId?: ReturnType - ) { - const user = this.createCognitoUser(authDetails.getUsername()); - try { - await user.authenticateUser( - authDetails, - this.authCallbacks( - user, - value => { - dispatchAuthEvent( - 'autoSignIn', - value, - `${authDetails.getUsername()} has signed in successfully` - ); - if (listenEvent) { - Hub.remove('auth', listenEvent); - } - if (autoSignInPollingIntervalId) { - clearInterval(autoSignInPollingIntervalId); - this._storage.removeItem('amplify-polling-started'); - } - this._storage.removeItem('amplify-auto-sign-in'); - }, - error => { - logger.error(error); - this._storage.removeItem('amplify-auto-sign-in'); - } - ) - ); - } catch (error) { - logger.error(error); - } - } - - /** - * Send the verification code to confirm sign up - * @param {String} username - The username to be confirmed - * @param {String} code - The verification code - * @param {ConfirmSignUpOptions} options - other options for confirm signup - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves callback data if success - */ - public confirmSignUp( - username: string, - code: string, - options?: ConfirmSignUpOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - if (!username) { - return this.rejectAuthError(AuthErrorTypes.EmptyUsername); - } - if (!code) { - return this.rejectAuthError(AuthErrorTypes.EmptyCode); - } - - const user = this.createCognitoUser(username); - const forceAliasCreation = - options && typeof options.forceAliasCreation === 'boolean' - ? options.forceAliasCreation - : true; - - let clientMetadata; - if (options && options.clientMetadata) { - clientMetadata = options.clientMetadata; - } else if (this._config.clientMetadata) { - clientMetadata = this._config.clientMetadata; - } - return new Promise((resolve, reject) => { - user.confirmRegistration( - code, - forceAliasCreation, - (err, data) => { - if (err) { - reject(err); - } else { - dispatchAuthEvent( - 'confirmSignUp', - data, - `${username} has been confirmed successfully` - ); - const autoSignIn = this.isTrueStorageValue('amplify-auto-sign-in'); - if (autoSignIn && !this.autoSignInInitiated) { - dispatchAuthEvent( - 'autoSignIn_failure', - null, - AuthErrorTypes.AutoSignInError - ); - this._storage.removeItem('amplify-auto-sign-in'); - } - resolve(data); - } - }, - clientMetadata - ); - }); - } - - private isTrueStorageValue(value: string) { - const item = this._storage.getItem(value); - return item ? item === 'true' : false; - } - - /** - * Resend the verification code - * @param {String} username - The username to be confirmed - * @param {ClientMetadata} clientMetadata - Metadata to be passed to Cognito Lambda triggers - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves code delivery details if successful - */ - public resendSignUp( - username: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - if (!username) { - return this.rejectAuthError(AuthErrorTypes.EmptyUsername); - } - - const user = this.createCognitoUser(username); - return new Promise((resolve, reject) => { - user.resendConfirmationCode((err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }, clientMetadata); - }); - } - - /** - * Sign in - * @param {String | SignInOpts} usernameOrSignInOpts - The username to be signed in or the sign in options - * @param {String} pw - The password of the username - * @param {ClientMetaData} clientMetadata - Client metadata for custom workflows - * @return - A promise resolves the CognitoUser - */ - public signIn( - usernameOrSignInOpts: string | SignInOpts, - pw?: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - - let username = null; - let password = null; - let validationData = {}; - - // for backward compatibility - if (typeof usernameOrSignInOpts === 'string') { - username = usernameOrSignInOpts; - password = pw; - } else if (isUsernamePasswordOpts(usernameOrSignInOpts)) { - if (typeof pw !== 'undefined') { - logger.warn( - 'The password should be defined under the first parameter object!' - ); - } - username = usernameOrSignInOpts.username; - password = usernameOrSignInOpts.password; - validationData = usernameOrSignInOpts.validationData; - } else { - return this.rejectAuthError(AuthErrorTypes.InvalidUsername); - } - if (!username) { - return this.rejectAuthError(AuthErrorTypes.EmptyUsername); - } - const authDetails = new AuthenticationDetails({ - Username: username, - Password: password, - ValidationData: validationData, - ClientMetadata: clientMetadata, - }); - if (password) { - return this.signInWithPassword(authDetails); - } else { - return this.signInWithoutPassword(authDetails); - } - } - - /** - * Return an object with the authentication callbacks - * @param {CognitoUser} user - the cognito user object - * @param {} resolve - function called when resolving the current step - * @param {} reject - function called when rejecting the current step - * @return - an object with the callback methods for user authentication - */ - private authCallbacks( - user: CognitoUser, - resolve: (value?: CognitoUser | any) => void, - reject: (value?: any) => void - ): IAuthenticationCallback { - const that = this; - return { - onSuccess: async session => { - logger.debug(session); - delete user['challengeName']; - delete user['challengeParam']; - try { - await this.Credentials.clear(); - const cred = await this.Credentials.set(session, 'session'); - logger.debug('succeed to get cognito credentials', cred); - } catch (e) { - logger.debug('cannot get cognito credentials', e); - } finally { - try { - // In order to get user attributes and MFA methods - // We need to trigger currentUserPoolUser again - const currentUser = await this.currentUserPoolUser(); - that.user = currentUser; - dispatchAuthEvent( - 'signIn', - currentUser, - `A user ${user.getUsername()} has been signed in` - ); - resolve(currentUser); - } catch (e) { - logger.error('Failed to get the signed in user', e); - reject(e); - } - } - }, - onFailure: err => { - logger.debug('signIn failure', err); - dispatchAuthEvent( - 'signIn_failure', - err, - `${user.getUsername()} failed to signin` - ); - reject(err); - }, - customChallenge: challengeParam => { - logger.debug('signIn custom challenge answer required'); - user['challengeName'] = 'CUSTOM_CHALLENGE'; - user['challengeParam'] = challengeParam; - resolve(user); - }, - mfaRequired: (challengeName, challengeParam) => { - logger.debug('signIn MFA required'); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - mfaSetup: (challengeName, challengeParam) => { - logger.debug('signIn mfa setup', challengeName); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - newPasswordRequired: (userAttributes, requiredAttributes) => { - logger.debug('signIn new password'); - user['challengeName'] = 'NEW_PASSWORD_REQUIRED'; - user['challengeParam'] = { - userAttributes, - requiredAttributes, - }; - resolve(user); - }, - totpRequired: (challengeName, challengeParam) => { - logger.debug('signIn totpRequired'); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - selectMFAType: (challengeName, challengeParam) => { - logger.debug('signIn selectMFAType', challengeName); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - }; - } - - /** - * Sign in with a password - * @private - * @param {AuthenticationDetails} authDetails - the user sign in data - * @return - A promise resolves the CognitoUser object if success or mfa required - */ - private signInWithPassword( - authDetails: AuthenticationDetails - ): Promise { - if (this.pendingSignIn) { - throw new Error('Pending sign-in attempt already in progress'); - } - - const user = this.createCognitoUser(authDetails.getUsername()); - - this.pendingSignIn = new Promise((resolve, reject) => { - user.authenticateUser( - authDetails, - this.authCallbacks( - user, - value => { - this.pendingSignIn = null; - resolve(value); - }, - error => { - this.pendingSignIn = null; - reject(error); - } - ) - ); - }); - - return this.pendingSignIn; - } - - /** - * Sign in without a password - * @private - * @param {AuthenticationDetails} authDetails - the user sign in data - * @return - A promise resolves the CognitoUser object if success or mfa required - */ - private signInWithoutPassword( - authDetails: AuthenticationDetails - ): Promise { - const user = this.createCognitoUser(authDetails.getUsername()); - user.setAuthenticationFlowType('CUSTOM_AUTH'); - - return new Promise((resolve, reject) => { - user.initiateAuth(authDetails, this.authCallbacks(user, resolve, reject)); - }); - } - - /** - * This was previously used by an authenticated user to get MFAOptions, - * but no longer returns a meaningful response. Refer to the documentation for - * how to setup and use MFA: https://docs.amplify.aws/lib/auth/mfa/q/platform/js - * @deprecated - * @param {CognitoUser} user - the current user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves the current preferred mfa option if success - */ - public getMFAOptions( - user: CognitoUser | any, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return new Promise((res, rej) => { - user.getMFAOptions((err, mfaOptions) => { - if (err) { - logger.debug('get MFA Options failed', err); - rej(err); - return; - } - logger.debug('get MFA options success', mfaOptions); - res(mfaOptions); - return; - }); - }); - } - - /** - * get preferred mfa method - * @param {CognitoUser} user - the current cognito user - * @param {GetPreferredMFAOpts} params - options for getting the current user preferred MFA - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - */ - public getPreferredMFA( - user: CognitoUser | any, - params?: GetPreferredMFAOpts, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const that = this; - return new Promise((res, rej) => { - const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn - - const bypassCache = params ? params.bypassCache : false; - user.getUserData( - async (err, data) => { - if (err) { - logger.debug('getting preferred mfa failed', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - } - rej(err); - return; - } - - const mfaType = that._getMfaTypeFromUserData(data); - if (!mfaType) { - rej('invalid MFA Type'); - return; - } else { - res(mfaType); - return; - } - }, - { bypassCache, clientMetadata } - ); - }); - } - - private _getMfaTypeFromUserData(data) { - let ret = null; - const preferredMFA = data.PreferredMfaSetting; - // if the user has used Auth.setPreferredMFA() to setup the mfa type - // then the "PreferredMfaSetting" would exist in the response - if (preferredMFA) { - ret = preferredMFA; - } else { - // if mfaList exists but empty, then its noMFA - const mfaList = data.UserMFASettingList; - if (!mfaList) { - // if SMS was enabled by using Auth.enableSMS(), - // the response would contain MFAOptions - // as for now Cognito only supports for SMS, so we will say it is 'SMS_MFA' - // if it does not exist, then it should be NOMFA - const MFAOptions = data.MFAOptions; - if (MFAOptions) { - ret = 'SMS_MFA'; - } else { - ret = 'NOMFA'; - } - } else if (mfaList.length === 0) { - ret = 'NOMFA'; - } else { - logger.debug('invalid case for getPreferredMFA', data); - } - } - return ret; - } - - private _getUserData(user, params) { - return new Promise((res, rej) => { - user.getUserData(async (err, data) => { - if (err) { - logger.debug('getting user data failed', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - } - rej(err); - return; - } else { - res(data); - } - }, params); - }); - } - - /** - * set preferred MFA method - * @param {CognitoUser} user - the current Cognito user - * @param {string} mfaMethod - preferred mfa method - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolve if success - */ - public async setPreferredMFA( - user: CognitoUser | any, - mfaMethod: 'TOTP' | 'SMS' | 'NOMFA' | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA', - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn - - const userData = await this._getUserData(user, { - bypassCache: true, - clientMetadata, - }); - let smsMfaSettings = null; - let totpMfaSettings = null; - - switch (mfaMethod) { - case 'TOTP': - case 'SOFTWARE_TOKEN_MFA': - totpMfaSettings = { - PreferredMfa: true, - Enabled: true, - }; - break; - case 'SMS': - case 'SMS_MFA': - smsMfaSettings = { - PreferredMfa: true, - Enabled: true, - }; - break; - case 'NOMFA': - const mfaList = userData['UserMFASettingList']; - const currentMFAType = await this._getMfaTypeFromUserData(userData); - if (currentMFAType === 'NOMFA') { - return Promise.resolve('No change for mfa type'); - } else if (currentMFAType === 'SMS_MFA') { - smsMfaSettings = { - PreferredMfa: false, - Enabled: false, - }; - } else if (currentMFAType === 'SOFTWARE_TOKEN_MFA') { - totpMfaSettings = { - PreferredMfa: false, - Enabled: false, - }; - } else { - return this.rejectAuthError(AuthErrorTypes.InvalidMFA); - } - // if there is a UserMFASettingList in the response - // we need to disable every mfa type in that list - if (mfaList && mfaList.length !== 0) { - // to disable SMS or TOTP if exists in that list - mfaList.forEach(mfaType => { - if (mfaType === 'SMS_MFA') { - smsMfaSettings = { - PreferredMfa: false, - Enabled: false, - }; - } else if (mfaType === 'SOFTWARE_TOKEN_MFA') { - totpMfaSettings = { - PreferredMfa: false, - Enabled: false, - }; - } - }); - } - break; - default: - logger.debug('no validmfa method provided'); - return this.rejectAuthError(AuthErrorTypes.NoMFA); - } - - const that = this; - return new Promise((res, rej) => { - user.setUserMfaPreference( - smsMfaSettings, - totpMfaSettings, - (err, result) => { - if (err) { - logger.debug('Set user mfa preference error', err); - return rej(err); - } - logger.debug('Set user mfa success', result); - logger.debug('Caching the latest user data into local'); - // cache the latest result into user data - user.getUserData( - async (err, data) => { - if (err) { - logger.debug('getting user data failed', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - } - return rej(err); - } else { - return res(result); - } - }, - { - bypassCache: true, - clientMetadata, - } - ); - } - ); - }); - } - - /** - * disable SMS - * @deprecated - * @param {CognitoUser} user - the current user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves is success - */ - public disableSMS( - user: CognitoUser, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return new Promise((res, rej) => { - user.disableMFA((err, data) => { - if (err) { - logger.debug('disable mfa failed', err); - rej(err); - return; - } - logger.debug('disable mfa succeed', data); - res(data); - return; - }); - }); - } - - /** - * enable SMS - * @deprecated - * @param {CognitoUser} user - the current user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves is success - */ - public enableSMS( - user: CognitoUser, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return new Promise((res, rej) => { - user.enableMFA((err, data) => { - if (err) { - logger.debug('enable mfa failed', err); - rej(err); - return; - } - logger.debug('enable mfa succeed', data); - res(data); - return; - }); - }); - } - - /** - * Setup TOTP - * @param {CognitoUser} user - the current user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves with the secret code if success - */ - public setupTOTP( - user: CognitoUser | any, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return new Promise((res, rej) => { - user.associateSoftwareToken({ - onFailure: err => { - logger.debug('associateSoftwareToken failed', err); - rej(err); - return; - }, - associateSecretCode: secretCode => { - logger.debug('associateSoftwareToken success', secretCode); - res(secretCode); - return; - }, - }); - }); - } - - /** - * verify TOTP setup - * @param {CognitoUser} user - the current user - * @param {string} challengeAnswer - challenge answer - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves is success - */ - public verifyTotpToken( - user: CognitoUser | any, - challengeAnswer: string, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - logger.debug('verification totp token', user, challengeAnswer); - - let signInUserSession; - if (user && typeof user.getSignInUserSession === 'function') { - signInUserSession = (user as CognitoUser).getSignInUserSession(); - } - const isLoggedIn = signInUserSession?.isValid(); - - return new Promise((res, rej) => { - user.verifySoftwareToken(challengeAnswer, 'My TOTP device', { - onFailure: err => { - logger.debug('verifyTotpToken failed', err); - rej(err); - return; - }, - onSuccess: data => { - if (!isLoggedIn) { - dispatchAuthEvent( - 'signIn', - user, - `A user ${user.getUsername()} has been signed in` - ); - } - dispatchAuthEvent( - 'verify', - user, - `A user ${user.getUsername()} has been verified` - ); - logger.debug('verifyTotpToken success', data); - res(data); - return; - }, - }); - }); - } - - /** - * Send MFA code to confirm sign in - * @param {Object} user - The CognitoUser object - * @param {String} code - The confirmation code - * @param {string} mfaType - optional mfaType: 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' - * @param {ClientMetaData} clientMetadata - optional client metadata defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - */ - public confirmSignIn( - user: CognitoUser | any, - code: string, - mfaType?: 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' | null, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!code) { - return this.rejectAuthError(AuthErrorTypes.EmptyCode); - } - - const that = this; - return new Promise((resolve, reject) => { - user.sendMFACode( - code, - { - onSuccess: async session => { - logger.debug(session); - try { - await this.Credentials.clear(); - const cred = await this.Credentials.set(session, 'session'); - logger.debug('succeed to get cognito credentials', cred); - } catch (e) { - logger.debug('cannot get cognito credentials', e); - } finally { - that.user = user; - try { - const currentUser = await this.currentUserPoolUser(); - user.attributes = currentUser.attributes; - } catch (e) { - logger.debug('cannot get updated Cognito User', e); - } - dispatchAuthEvent( - 'signIn', - user, - `A user ${user.getUsername()} has been signed in` - ); - resolve(user); - } - }, - onFailure: err => { - logger.debug('confirm signIn failure', err); - reject(err); - }, - }, - mfaType, - clientMetadata - ); - }); - } - - public completeNewPassword( - user: CognitoUser | any, - password: string, - requiredAttributes: any = {}, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!password) { - return this.rejectAuthError(AuthErrorTypes.EmptyPassword); - } - - const that = this; - return new Promise((resolve, reject) => { - user.completeNewPasswordChallenge( - password, - requiredAttributes, - { - onSuccess: async session => { - logger.debug(session); - try { - await this.Credentials.clear(); - const cred = await this.Credentials.set(session, 'session'); - logger.debug('succeed to get cognito credentials', cred); - } catch (e) { - logger.debug('cannot get cognito credentials', e); - } finally { - that.user = user; - dispatchAuthEvent( - 'signIn', - user, - `A user ${user.getUsername()} has been signed in` - ); - resolve(user); - } - }, - onFailure: err => { - logger.debug('completeNewPassword failure', err); - dispatchAuthEvent( - 'completeNewPassword_failure', - err, - `${this.user} failed to complete the new password flow` - ); - reject(err); - }, - mfaRequired: (challengeName, challengeParam) => { - logger.debug('signIn MFA required'); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - mfaSetup: (challengeName, challengeParam) => { - logger.debug('signIn mfa setup', challengeName); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - totpRequired: (challengeName, challengeParam) => { - logger.debug('signIn mfa setup', challengeName); - user['challengeName'] = challengeName; - user['challengeParam'] = challengeParam; - resolve(user); - }, - }, - clientMetadata - ); - }); - } - - /** - * Send the answer to a custom challenge - * @param {CognitoUser} user - The CognitoUser object - * @param {String} challengeResponses - The confirmation code - * @param {ClientMetaData} clientMetadata - optional client metadata defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - */ - public sendCustomChallengeAnswer( - user: CognitoUser | any, - challengeResponses: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - if (!challengeResponses) { - return this.rejectAuthError(AuthErrorTypes.EmptyChallengeResponse); - } - - const that = this; - return new Promise((resolve, reject) => { - user.sendCustomChallengeAnswer( - challengeResponses, - this.authCallbacks(user, resolve, reject), - clientMetadata - ); - }); - } - - /** - * Delete an authenticated users' attributes - * @param {CognitoUser} user - The currently logged in user object - * @param {string[]} attributeNames - Attributes to delete - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return {Promise} - **/ - public deleteUserAttributes( - user: CognitoUser | any, - attributeNames: string[], - customUserAgentDetails?: CustomUserAgentDetails - ) { - const that = this; - return new Promise((resolve, reject) => { - that.userSession(user).then(session => { - user.deleteAttributes(attributeNames, (err, result) => { - if (err) { - return reject(err); - } else { - return resolve(result); - } - }); - }); - }); - } - - /** - * Delete the current authenticated user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return {Promise} - **/ - // TODO: Check return type void - public async deleteUser( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - try { - await this._storageSync; - } catch (e) { - logger.debug('Failed to sync cache info into memory', e); - throw new Error(e); - } - - const isSignedInHostedUI = - this._oAuthHandler && - this._storage.getItem('amplify-signin-with-hostedUI') === 'true'; - - return new Promise(async (res, rej) => { - if (this.userPool) { - const user = this.userPool.getCurrentUser(); - - if (!user) { - logger.debug('Failed to get user from user pool'); - return rej(new Error('No current user.')); - } else { - user.getSession(async (err, session) => { - if (err) { - logger.debug('Failed to get the user session', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - } - return rej(err); - } else { - user.deleteUser((err, result: string) => { - if (err) { - rej(err); - } else { - dispatchAuthEvent( - 'userDeleted', - result, - 'The authenticated user has been deleted.' - ); - user.signOut(); - this.user = null; - try { - this.cleanCachedItems(); // clean aws credentials - } catch (e) { - // TODO: change to rejects in refactor - logger.debug('failed to clear cached items'); - } - - if (isSignedInHostedUI) { - this.oAuthSignOutRedirect(res, rej); - } else { - dispatchAuthEvent( - 'signOut', - this.user, - `A user has been signed out` - ); - res(result); - } - } - }); - } - }); - } - } else { - logger.debug('no Congito User pool'); - rej(new Error('Cognito User pool does not exist')); - } - }); - } - - /** - * Update an authenticated users' attributes - * @param {CognitoUser} user - The currently logged in user object - * @param {object} attributes - attributes to update - * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return {Promise} - **/ - public updateUserAttributes( - user: CognitoUser | any, - attributes: object, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const attributeList: ICognitoUserAttributeData[] = []; - const that = this; - return new Promise((resolve, reject) => { - that.userSession(user).then(session => { - for (const key in attributes) { - if (key !== 'sub' && key.indexOf('_verified') < 0) { - const attr: ICognitoUserAttributeData = { - Name: key, - Value: attributes[key], - }; - attributeList.push(attr); - } - } - user.updateAttributes( - attributeList, - (err, result, details) => { - if (err) { - dispatchAuthEvent( - 'updateUserAttributes_failure', - err, - 'Failed to update attributes' - ); - return reject(err); - } else { - const attrs = this.createUpdateAttributesResultList( - attributes as Record, - details?.CodeDeliveryDetailsList - ); - dispatchAuthEvent( - 'updateUserAttributes', - attrs, - 'Attributes successfully updated' - ); - return resolve(result); - } - }, - clientMetadata - ); - }); - }); - } - - private createUpdateAttributesResultList( - attributes: Record, - codeDeliveryDetailsList?: CodeDeliveryDetails[] - ): Record { - const attrs = {}; - Object.keys(attributes).forEach(key => { - attrs[key] = { - isUpdated: true, - }; - const codeDeliveryDetails = codeDeliveryDetailsList?.find( - value => value.AttributeName === key - ); - if (codeDeliveryDetails) { - attrs[key].isUpdated = false; - attrs[key].codeDeliveryDetails = codeDeliveryDetails; - } - }); - return attrs; - } - - /** - * Return user attributes - * @param {Object} user - The CognitoUser object - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to user attributes if success - */ - public userAttributes( - user: CognitoUser | any, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return new Promise((resolve, reject) => { - this.userSession(user).then(session => { - user.getUserAttributes((err, attributes) => { - if (err) { - reject(err); - } else { - resolve(attributes); - } - }); - }); - }); - } - - public verifiedContact( - user: CognitoUser | any, - customUserAgentDetails?: CustomUserAgentDetails - ) { - const that = this; - return this.userAttributes(user).then(attributes => { - const attrs = that.attributesToObject(attributes); - const unverified = {}; - const verified = {}; - if (attrs['email']) { - if (attrs['email_verified']) { - verified['email'] = attrs['email']; - } else { - unverified['email'] = attrs['email']; - } - } - if (attrs['phone_number']) { - if (attrs['phone_number_verified']) { - verified['phone_number'] = attrs['phone_number']; - } else { - unverified['phone_number'] = attrs['phone_number']; - } - } - return { - verified, - unverified, - }; - }); - } - - private isErrorWithMessage(err: any): err is { message: string } { - return ( - typeof err === 'object' && - Object.prototype.hasOwnProperty.call(err, 'message') - ); - } - - // Session revoked by another app - private isTokenRevokedError( - err: any - ): err is { message: 'Access Token has been revoked' } { - return ( - this.isErrorWithMessage(err) && - err.message === 'Access Token has been revoked' - ); - } - - private isRefreshTokenRevokedError( - err: any - ): err is { message: 'Refresh Token has been revoked' } { - return ( - this.isErrorWithMessage(err) && - err.message === 'Refresh Token has been revoked' - ); - } - - private isUserDisabledError( - err: any - ): err is { message: 'User is disabled.' } { - return this.isErrorWithMessage(err) && err.message === 'User is disabled.'; - } - - private isUserDoesNotExistError( - err: any - ): err is { message: 'User does not exist.' } { - return ( - this.isErrorWithMessage(err) && err.message === 'User does not exist.' - ); - } - - private isRefreshTokenExpiredError( - err: any - ): err is { message: 'Refresh Token has expired' } { - return ( - this.isErrorWithMessage(err) && - err.message === 'Refresh Token has expired' - ); - } - - private isPasswordResetRequiredError( - err: any - ): err is { message: 'Password reset required for the user' } { - return ( - this.isErrorWithMessage(err) && - err.message === 'Password reset required for the user' - ); - } - - private isSignedInHostedUI() { - return ( - this._oAuthHandler && - this._storage.getItem('amplify-signin-with-hostedUI') === 'true' - ); - } - - private isSessionInvalid(err: any) { - return ( - this.isUserDisabledError(err) || - this.isUserDoesNotExistError(err) || - this.isTokenRevokedError(err) || - this.isRefreshTokenRevokedError(err) || - this.isRefreshTokenExpiredError(err) || - this.isPasswordResetRequiredError(err) - ); - } - - private async cleanUpInvalidSession(user: CognitoUser) { - user.signOut(); - this.user = null; - try { - await this.cleanCachedItems(); // clean aws credentials - } catch (e) { - logger.debug('failed to clear cached items'); - } - if (this.isSignedInHostedUI()) { - return new Promise((res, rej) => { - this.oAuthSignOutRedirect(res, rej); - }); - } else { - dispatchAuthEvent('signOut', this.user, `A user has been signed out`); - } - } - - /** - * Get current authenticated user - * @param {CurrentUserOpts} params - options for getting the current user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to current authenticated CognitoUser if success - */ - public currentUserPoolUser( - params?: CurrentUserOpts, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - - return new Promise((res, rej) => { - this._storageSync - .then(async () => { - if (this.isOAuthInProgress()) { - logger.debug('OAuth signIn in progress, waiting for resolution...'); - - await new Promise(res => { - const timeoutId = setTimeout(() => { - logger.debug('OAuth signIn in progress timeout'); - - Hub.remove('auth', hostedUISignCallback); - - res(); - }, OAUTH_FLOW_MS_TIMEOUT); - - Hub.listen('auth', hostedUISignCallback); - - function hostedUISignCallback({ payload }) { - const { event } = payload; - - if ( - event === 'cognitoHostedUI' || - event === 'cognitoHostedUI_failure' - ) { - logger.debug(`OAuth signIn resolved: ${event}`); - clearTimeout(timeoutId); - - Hub.remove('auth', hostedUISignCallback); - - res(); - } - } - }); - } - - const user = this.userPool.getCurrentUser(); - - if (!user) { - logger.debug('Failed to get user from user pool'); - rej('No current user'); - return; - } - - // refresh the session if the session expired. - try { - const session = await this._userSession(user); - - // get user data from Cognito - const bypassCache = params ? params.bypassCache : false; - - if (bypassCache) { - await this.Credentials.clear(); - } - - const clientMetadata = this._config.clientMetadata; - - // validate the token's scope first before calling this function - const { scope = '' } = session.getAccessToken().decodePayload(); - if (scope.split(' ').includes(USER_ADMIN_SCOPE)) { - user.getUserData( - async (err, data) => { - if (err) { - logger.debug('getting user data failed', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - rej(err); - } else { - res(user); - } - return; - } - const preferredMFA = data.PreferredMfaSetting || 'NOMFA'; - const attributeList = []; - - for (let i = 0; i < data.UserAttributes.length; i++) { - const attribute = { - Name: data.UserAttributes[i].Name, - Value: data.UserAttributes[i].Value, - }; - const userAttribute = new CognitoUserAttribute(attribute); - attributeList.push(userAttribute); - } - - const attributes = this.attributesToObject(attributeList); - Object.assign(user, { attributes, preferredMFA }); - return res(user); - }, - { bypassCache, clientMetadata } - ); - } else { - logger.debug( - `Unable to get the user data because the ${USER_ADMIN_SCOPE} ` + - `is not in the scopes of the access token` - ); - return res(user); - } - } catch (err) { - rej(err); - } - }) - .catch(e => { - logger.debug('Failed to sync cache info into memory', e); - return rej(e); - }); - }); - } - - private isOAuthInProgress(): boolean { - return this.oAuthFlowInProgress; - } - - /** - * Get current authenticated user - * @param {CurrentUserOpts} - options for getting the current user - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to current authenticated CognitoUser if success - */ - public async currentAuthenticatedUser( - params?: CurrentUserOpts, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - logger.debug('getting current authenticated user'); - let federatedUser = null; - try { - await this._storageSync; - } catch (e) { - logger.debug('Failed to sync cache info into memory', e); - throw e; - } - - try { - const federatedInfo = JSON.parse( - this._storage.getItem('aws-amplify-federatedInfo') - ); - if (federatedInfo) { - federatedUser = { - ...federatedInfo.user, - token: federatedInfo.token, - }; - } - } catch (e) { - logger.debug('cannot load federated user from auth storage'); - } - - if (federatedUser) { - this.user = federatedUser; - logger.debug('get current authenticated federated user', this.user); - return this.user; - } else { - logger.debug('get current authenticated userpool user'); - let user = null; - try { - user = await this.currentUserPoolUser(params); - } catch (e) { - if (e === 'No userPool') { - logger.error( - 'Cannot get the current user because the user pool is missing. ' + - 'Please make sure the Auth module is configured with a valid Cognito User Pool ID' - ); - } - logger.debug('The user is not authenticated by the error', e); - return Promise.reject('The user is not authenticated'); - } - this.user = user; - return this.user; - } - } - - /** - * Get current user's session - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to session object if success - */ - public currentSession( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const that = this; - logger.debug('Getting current session'); - // Purposely not calling the reject method here because we don't need a console error - if (!this.userPool) { - return Promise.reject(new Error('No User Pool in the configuration.')); - } - - return new Promise((res, rej) => { - that - .currentUserPoolUser() - .then(user => { - that - .userSession(user) - .then(session => { - res(session); - return; - }) - .catch(e => { - logger.debug('Failed to get the current session', e); - rej(e); - return; - }); - }) - .catch(e => { - logger.debug('Failed to get the current user', e); - rej(e); - return; - }); - }); - } - - private async _userSession(user?: CognitoUser): Promise { - if (!user) { - logger.debug('the user is null'); - return this.rejectAuthError(AuthErrorTypes.NoUserSession); - } - const clientMetadata = this._config.clientMetadata; - // Debouncing the concurrent userSession calls by caching the promise. - // This solution assumes users will always call this function with the same CognitoUser instance. - if (this.inflightSessionPromiseCounter === 0) { - this.inflightSessionPromise = new Promise( - (res, rej) => { - user.getSession( - async (err, session) => { - if (err) { - logger.debug('Failed to get the session from user', user); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - } - rej(err); - return; - } else { - logger.debug('Succeed to get the user session', session); - res(session); - return; - } - }, - { clientMetadata } - ); - } - ); - } - this.inflightSessionPromiseCounter++; - - try { - const userSession = await this.inflightSessionPromise; - // Set private member. Avoid user.setSignInUserSession() to prevent excessive localstorage refresh. - // @ts-ignore - user.signInUserSession = userSession; - return userSession!; - } finally { - this.inflightSessionPromiseCounter--; - } - } - - /** - * Get the corresponding user session - * @param {Object} user - The CognitoUser object - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to the session - */ - public userSession( - user, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return this._userSession(user); - } - - /** - * Get authenticated credentials of current user. - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to be current user's credentials - */ - public async currentUserCredentials( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - logger.debug('Getting current user credentials'); - - try { - await this._storageSync; - } catch (e) { - logger.debug('Failed to sync cache info into memory', e); - throw e; - } - - // first to check whether there is federation info in the auth storage - let federatedInfo = null; - try { - federatedInfo = JSON.parse( - this._storage.getItem('aws-amplify-federatedInfo') - ); - } catch (e) { - logger.debug('failed to get or parse item aws-amplify-federatedInfo', e); - } - - if (federatedInfo) { - // refresh the jwt token here if necessary - return this.Credentials.refreshFederatedToken(federatedInfo); - } else { - return this.currentSession() - .then(session => { - logger.debug('getting session success', session); - return this.Credentials.set(session, 'session'); - }) - .catch(() => { - logger.debug('getting guest credentials'); - return this.Credentials.set(null, 'guest'); - }); - } - } - - public currentCredentials( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - logger.debug('getting current credentials'); - return this.Credentials.get(); - } - - /** - * Initiate an attribute confirmation request - * @param {Object} user - The CognitoUser - * @param {Object} attr - The attributes to be verified - * @return - A promise resolves to callback data if success - */ - public verifyUserAttribute( - user: CognitoUser | any, - attr: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return new Promise((resolve, reject) => { - user.getAttributeVerificationCode( - attr, - { - onSuccess(success) { - return resolve(success); - }, - onFailure(err) { - return reject(err); - }, - }, - clientMetadata - ); - }); - } - - /** - * Confirm an attribute using a confirmation code - * @param {Object} user - The CognitoUser - * @param {Object} attr - The attribute to be verified - * @param {String} code - The confirmation code - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to callback data if success - */ - public verifyUserAttributeSubmit( - user: CognitoUser | any, - attr: string, - code: string, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!code) { - return this.rejectAuthError(AuthErrorTypes.EmptyCode); - } - - return new Promise((resolve, reject) => { - user.verifyAttribute(attr, code, { - onSuccess(data) { - resolve(data); - return; - }, - onFailure(err) { - reject(err); - return; - }, - }); - }); - } - - public verifyCurrentUserAttribute( - attr: string, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const that = this; - return that - .currentUserPoolUser() - .then(user => that.verifyUserAttribute(user, attr)); - } - - /** - * Confirm current user's attribute using a confirmation code - * @param {Object} attr - The attribute to be verified - * @param {String} code - The confirmation code - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves to callback data if success - */ - verifyCurrentUserAttributeSubmit( - attr: string, - code: string, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const that = this; - return that - .currentUserPoolUser() - .then(user => that.verifyUserAttributeSubmit(user, attr, code)); - } - - private async cognitoIdentitySignOut( - opts: SignOutOpts, - user: CognitoUser | any - ) { - try { - await this._storageSync; - } catch (e) { - logger.debug('Failed to sync cache info into memory', e); - throw e; - } - - const isSignedInHostedUI = - this._oAuthHandler && - this._storage.getItem('amplify-signin-with-hostedUI') === 'true'; - - return new Promise((res, rej) => { - if (opts && opts.global) { - logger.debug('user global sign out', user); - // in order to use global signout - // we must validate the user as an authenticated user by using getSession - const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn - - user.getSession( - async (err, result) => { - if (err) { - logger.debug('failed to get the user session', err); - if (this.isSessionInvalid(err)) { - try { - await this.cleanUpInvalidSession(user); - } catch (cleanUpError) { - rej( - new Error( - `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}` - ) - ); - return; - } - } - return rej(err); - } - user.globalSignOut({ - onSuccess: data => { - logger.debug('global sign out success'); - if (isSignedInHostedUI) { - this.oAuthSignOutRedirect(res, rej); - } else { - return res(); - } - }, - onFailure: err => { - logger.debug('global sign out failed', err); - return rej(err); - }, - }); - }, - { clientMetadata } - ); - } else { - logger.debug('user sign out', user); - user.signOut(() => { - if (isSignedInHostedUI) { - this.oAuthSignOutRedirect(res, rej); - } else { - return res(); - } - }); - } - }); - } - - private oAuthSignOutRedirect( - resolve: () => void, - reject: (reason?: any) => void - ) { - const { isBrowser } = browserOrNode(); - - if (isBrowser) { - this.oAuthSignOutRedirectOrReject(reject); - } else { - this.oAuthSignOutAndResolve(resolve); - } - } - - private oAuthSignOutAndResolve(resolve: () => void) { - this._oAuthHandler.signOut(); - resolve(); - } - - private oAuthSignOutRedirectOrReject(reject: (reason?: any) => void) { - this._oAuthHandler.signOut(); // this method redirects url - - // App should be redirected to another url otherwise it will reject - setTimeout(() => reject(Error('Signout timeout fail')), 3000); - } - - /** - * Sign out method - * @param {SignOutOpts} opts - options for sign out - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolved if success - */ - public async signOut( - opts?: SignOutOpts, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - try { - await this.cleanCachedItems(); - } catch (e) { - logger.debug('failed to clear cached items'); - } - - if (this.userPool) { - const user = this.userPool.getCurrentUser(); - if (user) { - await this.cognitoIdentitySignOut(opts, user); - } else { - logger.debug('no current Cognito user'); - } - } else { - logger.debug('no Cognito User pool'); - } - - /** - * Note for future refactor - no reliable way to get username with - * Cognito User Pools vs Identity when federating with Social Providers - * This is why we need a well structured session object that can be inspected - * and information passed back in the message below for Hub dispatch - */ - dispatchAuthEvent('signOut', this.user, `A user has been signed out`); - this.user = null; - } - - private async cleanCachedItems() { - // clear cognito cached item - await this.Credentials.clear(); - } - - /** - * Change a password for an authenticated user - * @param {Object} user - The CognitoUser object - * @param {String} oldPassword - the current password - * @param {String} newPassword - the requested new password - * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves if success - */ - public changePassword( - user: CognitoUser | any, - oldPassword: string, - newPassword: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise<'SUCCESS'> { - return new Promise((resolve, reject) => { - this.userSession(user).then(session => { - user.changePassword( - oldPassword, - newPassword, - (err, data) => { - if (err) { - logger.debug('change password failure', err); - return reject(err); - } else { - return resolve(data); - } - }, - clientMetadata - ); - }); - }); - } - - /** - * Initiate a forgot password request - * @param {String} username - the username to change password - * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise resolves if success - */ - public forgotPassword( - username: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - if (!username) { - return this.rejectAuthError(AuthErrorTypes.EmptyUsername); - } - - const user = this.createCognitoUser(username); - return new Promise((resolve, reject) => { - user.forgotPassword( - { - onSuccess: () => { - resolve(); - return; - }, - onFailure: err => { - logger.debug('forgot password failure', err); - dispatchAuthEvent( - 'forgotPassword_failure', - err, - `${username} forgotPassword failed` - ); - reject(err); - return; - }, - inputVerificationCode: data => { - dispatchAuthEvent( - 'forgotPassword', - user, - `${username} has initiated forgot password flow` - ); - resolve(data); - return; - }, - }, - clientMetadata - ); - }); - } - - /** - * Confirm a new password using a confirmation Code - * @param {String} username - The username - * @param {String} code - The confirmation code - * @param {String} password - The new password - * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return - A promise that resolves if success - */ - public forgotPasswordSubmit( - username: string, - code: string, - password: string, - clientMetadata: ClientMetaData = this._config.clientMetadata, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this.userPool) { - return this.rejectNoUserPool(); - } - if (!username) { - return this.rejectAuthError(AuthErrorTypes.EmptyUsername); - } - if (!code) { - return this.rejectAuthError(AuthErrorTypes.EmptyCode); - } - if (!password) { - return this.rejectAuthError(AuthErrorTypes.EmptyPassword); - } - - const user = this.createCognitoUser(username); - return new Promise((resolve, reject) => { - user.confirmPassword( - code, - password, - { - onSuccess: success => { - dispatchAuthEvent( - 'forgotPasswordSubmit', - user, - `${username} forgotPasswordSubmit successful` - ); - resolve(success); - return; - }, - onFailure: err => { - dispatchAuthEvent( - 'forgotPasswordSubmit_failure', - err, - `${username} forgotPasswordSubmit failed` - ); - reject(err); - return; - }, - }, - clientMetadata - ); - }); - } - - /** - * Get user information - * @async - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @return {Object }- current User's information - */ - public async currentUserInfo( - customUserAgentDetails?: CustomUserAgentDetails - ) { - const source = this.Credentials.getCredSource(); - - if (!source || source === 'aws' || source === 'userPool') { - const user = await this.currentUserPoolUser().catch(err => - logger.error(err) - ); - if (!user) { - return null; - } - - try { - const attributes = await this.userAttributes(user); - const userAttrs: object = this.attributesToObject(attributes); - let credentials = null; - try { - credentials = await this.currentCredentials(); - } catch (e) { - logger.debug( - 'Failed to retrieve credentials while getting current user info', - e - ); - } - - const info = { - id: credentials ? credentials.identityId : undefined, - username: user.getUsername(), - attributes: userAttrs, - }; - return info; - } catch (err) { - logger.error('currentUserInfo error', err); - return {}; - } - } - - if (source === 'federated') { - const user = this.user; - return user ? user : {}; - } - } - - public async federatedSignIn( - providerOrOptions: - | LegacyProvider - | FederatedSignInOptions - | FederatedSignInOptionsCustom, - response?: FederatedResponse, - user?: FederatedUser, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - if (!this._config.identityPoolId && !this._config.userPoolId) { - throw new Error( - `Federation requires either a User Pool or Identity Pool in config` - ); - } - - // Ensure backwards compatability - if (typeof providerOrOptions === 'undefined') { - if (this._config.identityPoolId && !this._config.userPoolId) { - throw new Error( - `Federation with Identity Pools requires tokens passed as arguments` - ); - } - } - - if ( - isFederatedSignInOptions(providerOrOptions) || - isFederatedSignInOptionsCustom(providerOrOptions) || - hasCustomState(providerOrOptions) || - typeof providerOrOptions === 'undefined' - ) { - const options = providerOrOptions || { - provider: CognitoHostedUIIdentityProvider.Cognito, - }; - const provider = isFederatedSignInOptions(options) - ? options.provider - : (options as FederatedSignInOptionsCustom).customProvider; - - const customState = isFederatedSignInOptions(options) - ? options.customState - : (options as FederatedSignInOptionsCustom).customState; - - if (this._config.userPoolId) { - const client_id = isCognitoHostedOpts(this._config.oauth) - ? this._config.userPoolWebClientId - : this._config.oauth.clientID; - /*Note: Invenstigate automatically adding trailing slash */ - const redirect_uri = isCognitoHostedOpts(this._config.oauth) - ? this._config.oauth.redirectSignIn - : this._config.oauth.redirectUri; - - this._oAuthHandler.oauthSignIn( - this._config.oauth.responseType, - this._config.oauth.domain, - redirect_uri, - client_id, - provider, - customState - ); - } - } else { - const provider = providerOrOptions; - // To check if the user is already logged in - try { - const loggedInUser = JSON.stringify( - JSON.parse(this._storage.getItem('aws-amplify-federatedInfo')).user - ); - if (loggedInUser) { - logger.warn(`There is already a signed in user: ${loggedInUser} in your app. - You should not call Auth.federatedSignIn method again as it may cause unexpected behavior.`); - } - } catch (e) {} - - const { token, identity_id, expires_at } = response; - // Because this.Credentials.set would update the user info with identity id - // So we need to retrieve the user again. - const credentials = await this.Credentials.set( - { provider, token, identity_id, user, expires_at }, - 'federation' - ); - const currentUser = await this.currentAuthenticatedUser(); - dispatchAuthEvent( - 'signIn', - currentUser, - `A user ${currentUser.username} has been signed in` - ); - logger.debug('federated sign in credentials', credentials); - return credentials; - } - } - - /** - * Used to complete the OAuth flow with or without the Cognito Hosted UI - * @param {String} URL - optional parameter for customers to pass in the response URL - */ - private async _handleAuthResponse(URL?: string) { - if (this.oAuthFlowInProgress) { - logger.debug(`Skipping URL ${URL} current flow in progress`); - return; - } - - try { - this.oAuthFlowInProgress = true; - if (!this._config.userPoolId) { - throw new Error( - `OAuth responses require a User Pool defined in config` - ); - } - - dispatchAuthEvent( - 'parsingCallbackUrl', - { url: URL }, - `The callback url is being parsed` - ); - - const currentUrl = - URL || (browserOrNode().isBrowser ? window.location.href : ''); - - const hasCodeOrError = !!(parse(currentUrl).query || '') - .split('&') - .map(entry => entry.split('=')) - .find(([k]) => k === 'code' || k === 'error'); - - const hasTokenOrError = !!(parse(currentUrl).hash || '#') - .substr(1) - .split('&') - .map(entry => entry.split('=')) - .find(([k]) => k === 'access_token' || k === 'error'); - - if (hasCodeOrError || hasTokenOrError) { - this._storage.setItem('amplify-redirected-from-hosted-ui', 'true'); - try { - const { accessToken, idToken, refreshToken, state } = - await this._oAuthHandler.handleAuthResponse(currentUrl); - const session = new CognitoUserSession({ - IdToken: new CognitoIdToken({ IdToken: idToken }), - RefreshToken: new CognitoRefreshToken({ - RefreshToken: refreshToken, - }), - AccessToken: new CognitoAccessToken({ - AccessToken: accessToken, - }), - }); - - let credentials; - // Get AWS Credentials & store if Identity Pool is defined - if (this._config.identityPoolId) { - credentials = await this.Credentials.set(session, 'session'); - logger.debug('AWS credentials', credentials); - } - - /* - Prior to the request we do sign the custom state along with the state we set. This check will verify - if there is a dash indicated when setting custom state from the request. If a dash is contained - then there is custom state present on the state string. - */ - const isCustomStateIncluded = /-/.test(state); - - /* - The following is to create a user for the Cognito Identity SDK to store the tokens - When we remove this SDK later that logic will have to be centralized in our new version - */ - //#region - const currentUser = this.createCognitoUser( - session.getIdToken().decodePayload()['cognito:username'] - ); - - // This calls cacheTokens() in Cognito SDK - currentUser.setSignInUserSession(session); - - if (window && typeof window.history !== 'undefined') { - window.history.replaceState( - {}, - null, - (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn - ); - } - - dispatchAuthEvent( - 'signIn', - currentUser, - `A user ${currentUser.getUsername()} has been signed in` - ); - dispatchAuthEvent( - 'cognitoHostedUI', - currentUser, - `A user ${currentUser.getUsername()} has been signed in via Cognito Hosted UI` - ); - - if (isCustomStateIncluded) { - const customState = state.split('-').splice(1).join('-'); - - dispatchAuthEvent( - 'customOAuthState', - urlSafeDecode(customState), - `State for user ${currentUser.getUsername()}` - ); - } - //#endregion - - return credentials; - } catch (err) { - logger.debug('Error in cognito hosted auth response', err); - - // Just like a successful handling of `?code`, replace the window history to "dispose" of the `code`. - // Otherwise, reloading the page will throw errors as the `code` has already been spent. - if (window && typeof window.history !== 'undefined') { - window.history.replaceState( - {}, - null, - (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn - ); - } - - dispatchAuthEvent( - 'signIn_failure', - err, - `The OAuth response flow failed` - ); - dispatchAuthEvent( - 'cognitoHostedUI_failure', - err, - `A failure occurred when returning to the Cognito Hosted UI` - ); - dispatchAuthEvent( - 'customState_failure', - err, - `A failure occurred when returning state` - ); - } - } - } finally { - this.oAuthFlowInProgress = false; - } - } - - /** - * Compact version of credentials - * @param {Object} credentials - * @return {Object} - Credentials - */ - public essentialCredentials(credentials): ICredentials { - return { - accessKeyId: credentials.accessKeyId, - sessionToken: credentials.sessionToken, - secretAccessKey: credentials.secretAccessKey, - identityId: credentials.identityId, - authenticated: credentials.authenticated, - }; - } - - private attributesToObject(attributes) { - const obj = {}; - if (attributes) { - attributes.map(attribute => { - if ( - attribute.Name === 'email_verified' || - attribute.Name === 'phone_number_verified' - ) { - obj[attribute.Name] = - this.isTruthyString(attribute.Value) || attribute.Value === true; - } else { - obj[attribute.Name] = attribute.Value; - } - }); - } - return obj; - } - - private isTruthyString(value: any): boolean { - return ( - typeof value.toLowerCase === 'function' && value.toLowerCase() === 'true' - ); - } - - private createCognitoUser(username: string): CognitoUser { - const userData: ICognitoUserData = { - Username: username, - Pool: this.userPool, - }; - userData.Storage = this._storage; - - const { authenticationFlowType } = this._config; - - const user = new CognitoUser(userData); - if (authenticationFlowType) { - user.setAuthenticationFlowType(authenticationFlowType); - } - return user; - } - - private _isValidAuthStorage(obj) { - // We need to check if the obj has the functions of Storage - return ( - !!obj && - typeof obj.getItem === 'function' && - typeof obj.setItem === 'function' && - typeof obj.removeItem === 'function' && - typeof obj.clear === 'function' - ); - } - - private noUserPoolErrorHandler(config: AuthOptions): AuthErrorTypes { - if (config) { - if (!config.userPoolId || !config.identityPoolId) { - return AuthErrorTypes.MissingAuthConfig; - } - } - return AuthErrorTypes.NoConfig; - } - - private rejectAuthError(type: AuthErrorTypes): Promise { - return Promise.reject(new AuthError(type)); - } - - private rejectNoUserPool(): Promise { - const type = this.noUserPoolErrorHandler(this._config); - return Promise.reject(new NoUserPoolError(type)); - } - - public async rememberDevice( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - let currUser; - - try { - currUser = await this.currentUserPoolUser(); - } catch (error) { - logger.debug('The user is not authenticated by the error', error); - return Promise.reject('The user is not authenticated'); - } - - currUser.getCachedDeviceKeyAndPassword(); - return new Promise((res, rej) => { - currUser.setDeviceStatusRemembered({ - onSuccess: data => { - res(data); - }, - onFailure: err => { - if (err.code === 'InvalidParameterException') { - rej(new AuthError(AuthErrorTypes.DeviceConfig)); - } else if (err.code === 'NetworkError') { - rej(new AuthError(AuthErrorTypes.NetworkError)); - } else { - rej(err); - } - }, - }); - }); - } - - public async forgetDevice( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - let currUser; - - try { - currUser = await this.currentUserPoolUser(); - } catch (error) { - logger.debug('The user is not authenticated by the error', error); - return Promise.reject('The user is not authenticated'); - } - - currUser.getCachedDeviceKeyAndPassword(); - return new Promise((res, rej) => { - currUser.forgetDevice({ - onSuccess: data => { - res(data); - }, - onFailure: err => { - if (err.code === 'InvalidParameterException') { - rej(new AuthError(AuthErrorTypes.DeviceConfig)); - } else if (err.code === 'NetworkError') { - rej(new AuthError(AuthErrorTypes.NetworkError)); - } else { - rej(err); - } - }, - }); - }); - } - - public async fetchDevices( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - let currUser; - - try { - currUser = await this.currentUserPoolUser(); - } catch (error) { - logger.debug('The user is not authenticated by the error', error); - throw new Error('The user is not authenticated'); - } - - currUser.getCachedDeviceKeyAndPassword(); - return new Promise((res, rej) => { - const cb = { - onSuccess(data) { - const deviceList: IAuthDevice[] = data.Devices.map(device => { - const deviceName = - device.DeviceAttributes.find( - ({ Name }) => Name === 'device_name' - ) || {}; - - const deviceInfo: IAuthDevice = { - id: device.DeviceKey, - name: deviceName.Value, - }; - return deviceInfo; - }); - res(deviceList); - }, - onFailure: err => { - if (err.code === 'InvalidParameterException') { - rej(new AuthError(AuthErrorTypes.DeviceConfig)); - } else if (err.code === 'NetworkError') { - rej(new AuthError(AuthErrorTypes.NetworkError)); - } else { - rej(err); - } - }, - }; - currUser.listDevices(MAX_DEVICES, null, cb); - }); - } -} - -export const InternalAuth = new InternalAuthClass(null); -Amplify.register(InternalAuth); diff --git a/packages/auth/src/internals/index.ts b/packages/auth/src/internals/index.ts deleted file mode 100644 index b153c1d1d92..00000000000 --- a/packages/auth/src/internals/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -export { InternalAuth } from './InternalAuth'; diff --git a/packages/auth/tslint.json b/packages/auth/tslint.json index 8eafab1d2b4..1bb9e144d24 100644 --- a/packages/auth/tslint.json +++ b/packages/auth/tslint.json @@ -39,12 +39,7 @@ "allow-snake-case", "allow-leading-underscore" ], - "semicolon": [ - true, - "always", - "ignore-interfaces", - "ignore-bound-class-methods" - ] + "semicolon": [true, "always", "ignore-interfaces"] }, "rulesDirectory": [] } diff --git a/packages/datastore/package.json b/packages/datastore/package.json index 3519ccac09b..20b142357d9 100644 --- a/packages/datastore/package.json +++ b/packages/datastore/package.json @@ -73,7 +73,7 @@ "name": "DataStore (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, DataStore }", - "limit": "138.27 kB" + "limit": "137.68 kB" } ], "jest": { From 12a232f122d1ee4c875948e72ff3ec41fbd94ba8 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:44:54 -0500 Subject: [PATCH 10/16] Revert "feat: custom user agent InternalCognitoUser (#11709)" This reverts commit 411529560d225e789af220c554da8cd733b1563c. --- .../__tests__/CognitoUser.test.js | 22 +- .../__tests__/internalsIndex.test.js | 1 - .../amazon-cognito-identity-js/index.d.ts | 15 +- .../internals/index.d.ts | 266 -- .../amazon-cognito-identity-js/src/Client.js | 50 +- .../src/CognitoUser.js | 1809 +++++++++++- .../src/internals/InternalCognitoUser.js | 2428 ----------------- .../src/internals/index.js | 2 - packages/auth/__tests__/auth-unit-test.ts | 108 +- packages/auth/package.json | 2 +- packages/datastore/package.json | 2 +- 11 files changed, 1846 insertions(+), 2859 deletions(-) delete mode 100644 packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js diff --git a/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js b/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js index c9c86c439d4..23a015b5c4c 100644 --- a/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js +++ b/packages/amazon-cognito-identity-js/__tests__/CognitoUser.test.js @@ -203,7 +203,7 @@ describe('authenticateUser()', () => { user.setAuthenticationFlowType('USER_PASSWORD_AUTH'); user.authenticateUser(authDetails, callback); - expect(spyon).toHaveBeenCalledWith(authDetails, callback, undefined); + expect(spyon).toHaveBeenCalledWith(authDetails, callback); }); test('USER_SRP_AUTH and CUSTOM_AUTH flow types', () => { @@ -212,12 +212,12 @@ describe('authenticateUser()', () => { user.setAuthenticationFlowType('USER_SRP_AUTH'); user.authenticateUser(authDetails, callback); - expect(spyon).toHaveBeenCalledWith(authDetails, callback, undefined); + expect(spyon).toHaveBeenCalledWith(authDetails, callback); user.setAuthenticationFlowType('CUSTOM_AUTH'); user.authenticateUser(authDetails, callback); - expect(spyon).toHaveBeenCalledWith(authDetails, callback, undefined); + expect(spyon).toHaveBeenCalledWith(authDetails, callback); }); test('throws error for invalid Authentication flow type', () => { @@ -310,8 +310,7 @@ describe('authenticateUserPlainUsernamePassword()', () => { expect(userSpy3).toBeCalledWith( 'test auth result', userSpy3.mock.calls[0][1], - callback, - undefined + callback ); expect(userSpy3.mock.results[0].value).toBe('test return value'); }); @@ -756,8 +755,7 @@ describe('sendCustomChallengeAnswer()', () => { expect(spyon3).toBeCalledWith( vCognitoUserSession, expect.any(AuthenticationHelper), - callback, - undefined + callback ); }); @@ -1178,33 +1176,33 @@ describe('confirmPassword() and forgotPassword()', () => { jest.clearAllMocks(); }); - test('confirmPassword happy path should callback onSuccess', () => { + test('happy path should callback onSuccess', () => { netRequestMockSuccess(true); cognitoUser.confirmPassword(...confirmPasswordDefaults); expect(callback.onSuccess).toHaveBeenCalledWith('SUCCESS'); }); - test('confirmPassword client request throws an error', () => { + test('client request throws an error', () => { netRequestMockSuccess(false); cognitoUser.confirmPassword(...confirmPasswordDefaults); expect(callback.onFailure.mock.calls.length).toEqual(1); }); - test('forgotPassword happy path should callback onSuccess', () => { + test('happy path should callback onSuccess', () => { callback.inputVerificationCode = null; netRequestMockSuccess(true); cognitoUser.forgotPassword(...forgotPasswordDefaults); expect(callback.onSuccess.mock.calls.length).toEqual(1); }); - test('forgotPassword inputVerification code is a function should callback inputVerificationCode', () => { + test('inputVerification code is a function should callback inputVerificationCode', () => { callback.inputVerificationCode = jest.fn(); netRequestMockSuccess(true); cognitoUser.forgotPassword(...forgotPasswordDefaults); expect(callback.inputVerificationCode.mock.calls.length).toEqual(1); }); - test('forgotPassword client returning an error should call onFailure', () => { + test('client returning an error should call onFailure', () => { netRequestMockSuccess(false); cognitoUser.forgotPassword(...forgotPasswordDefaults); expect(callback.onFailure.mock.calls.length).toEqual(1); diff --git a/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js b/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js index 12b4ffa9676..4b448a3e8a1 100644 --- a/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js +++ b/packages/amazon-cognito-identity-js/__tests__/internalsIndex.test.js @@ -6,7 +6,6 @@ describe('import * keys', () => { Array [ "addAuthCategoryToCognitoUserAgent", "addFrameworkToCognitoUserAgent", - "InternalCognitoUser", ] `); }); diff --git a/packages/amazon-cognito-identity-js/index.d.ts b/packages/amazon-cognito-identity-js/index.d.ts index 2c526568121..0a32f2c9fc5 100644 --- a/packages/amazon-cognito-identity-js/index.d.ts +++ b/packages/amazon-cognito-identity-js/index.d.ts @@ -1,4 +1,3 @@ -import { InternalCognitoUser } from './internals'; declare module 'amazon-cognito-identity-js' { //import * as AWS from "aws-sdk"; @@ -90,7 +89,19 @@ declare module 'amazon-cognito-identity-js' { | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA'; - export class CognitoUser extends InternalCognitoUser { + export class CognitoUser { + constructor(data: ICognitoUserData); + + challengeName?: ChallengeName; + + public setSignInUserSession(signInUserSession: CognitoUserSession): void; + public getSignInUserSession(): CognitoUserSession | null; + public getUsername(): string; + + public getAuthenticationFlowType(): string; + public setAuthenticationFlowType(authenticationFlowType: string): string; + public getCachedDeviceKeyAndPassword(): void; + public getSession( callback: | ((error: Error, session: null) => void) diff --git a/packages/amazon-cognito-identity-js/internals/index.d.ts b/packages/amazon-cognito-identity-js/internals/index.d.ts index f1160c9563d..c7ecb6928c7 100644 --- a/packages/amazon-cognito-identity-js/internals/index.d.ts +++ b/packages/amazon-cognito-identity-js/internals/index.d.ts @@ -1,268 +1,2 @@ -import { - NodeCallback, - UpdateAttributesNodeCallback, - ClientMetadata, - IAuthenticationCallback, - IMfaSettings, - AuthenticationDetails, - ICognitoUserData, - GetSessionOptions, - ChallengeName, - CognitoUserSession, - CognitoRefreshToken, - CognitoUserAttribute, - ICognitoUserAttributeData, - MFAOption, - UserData, -} from 'amazon-cognito-identity-js'; - export const addAuthCategoryToCognitoUserAgent: () => void; export const addFrameworkToCognitoUserAgent: (content: string) => void; - -export class InternalCognitoUser { - constructor(data: ICognitoUserData); - - challengeName?: ChallengeName; - - public setSignInUserSession(signInUserSession: CognitoUserSession): void; - public getSignInUserSession(): CognitoUserSession | null; - public getUsername(): string; - - public getAuthenticationFlowType(): string; - public setAuthenticationFlowType(authenticationFlowType: string): string; - public getCachedDeviceKeyAndPassword(): void; - - public getSession( - callback: - | ((error: Error, session: null) => void) - | ((error: null, session: CognitoUserSession) => void), - options?: GetSessionOptions, - userAgentValue?: string - ): void; - public refreshSession( - refreshToken: CognitoRefreshToken, - callback: NodeCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public authenticateUser( - authenticationDetails: AuthenticationDetails, - callbacks: IAuthenticationCallback, - userAgentValue?: string - ): void; - public initiateAuth( - authenticationDetails: AuthenticationDetails, - callbacks: IAuthenticationCallback, - userAgentValue?: string - ): void; - public confirmRegistration( - code: string, - forceAliasCreation: boolean, - callback: NodeCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public sendCustomChallengeAnswer( - answerChallenge: any, - callback: IAuthenticationCallback, - clientMetaData?: ClientMetadata, - userAgentValue?: string - ): void; - public resendConfirmationCode( - callback: NodeCallback, - clientMetaData?: ClientMetadata, - userAgentValue?: string - ): void; - public changePassword( - oldPassword: string, - newPassword: string, - callback: NodeCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public forgotPassword( - callbacks: { - onSuccess: (data: any) => void; - onFailure: (err: Error) => void; - inputVerificationCode?: (data: any) => void; - }, - clientMetaData?: ClientMetadata, - userAgentValue?: string - ): void; - public confirmPassword( - verificationCode: string, - newPassword: string, - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: Error) => void; - }, - clientMetaData?: ClientMetadata, - userAgentValue?: string - ): void; - public setDeviceStatusRemembered( - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: any) => void; - }, - userAgentValue?: string - ): void; - public setDeviceStatusNotRemembered( - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: any) => void; - }, - userAgentValue?: string - ): void; - public getDevice( - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): any; - public forgetDevice( - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): void; - public forgetSpecificDevice( - deviceKey: string, - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): void; - public sendMFACode( - confirmationCode: string, - callbacks: { - onSuccess: ( - session: CognitoUserSession, - userConfirmationNecessary?: boolean - ) => void; - onFailure: (err: any) => void; - }, - mfaType?: string, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public listDevices( - limit: number, - paginationToken: string | null, - callbacks: { - onSuccess: (data: any) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): void; - public completeNewPasswordChallenge( - newPassword: string, - requiredAttributeData: any, - callbacks: IAuthenticationCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public signOut(callback?: () => void, userAgentValue?: string): void; - public globalSignOut( - callbacks: { - onSuccess: (msg: string) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): void; - public verifyAttribute( - attributeName: string, - confirmationCode: string, - callbacks: { - onSuccess: (success: string) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): void; - public getUserAttributes( - callback: NodeCallback, - userAgentValue?: string - ): void; - public updateAttributes( - attributes: (CognitoUserAttribute | ICognitoUserAttributeData)[], - callback: UpdateAttributesNodeCallback, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public deleteAttributes( - attributeList: string[], - callback: NodeCallback, - userAgentValue?: string - ): void; - public getAttributeVerificationCode( - name: string, - callback: { - onSuccess: (success: string) => void; - onFailure: (err: Error) => void; - inputVerificationCode?: (data: string) => void | null; - }, - clientMetadata?: ClientMetadata, - userAgentValue?: string - ): void; - public deleteUser( - callback: NodeCallback, - userAgentValue?: string - ): void; - public enableMFA( - callback: NodeCallback, - userAgentValue?: string - ): void; - public disableMFA( - callback: NodeCallback, - userAgentValue?: string - ): void; - public getMFAOptions( - callback: NodeCallback, - userAgentValue?: string - ): void; - public getUserData( - callback: NodeCallback, - params?: any, - userAgentValue?: string - ): void; - public associateSoftwareToken( - callbacks: { - associateSecretCode: (secretCode: string) => void; - onFailure: (err: any) => void; - }, - userAgentValue?: string - ): void; - public verifySoftwareToken( - totpCode: string, - friendlyDeviceName: string, - callbacks: { - onSuccess: (session: CognitoUserSession) => void; - onFailure: (err: Error) => void; - }, - userAgentValue?: string - ): void; - public setUserMfaPreference( - smsMfaSettings: IMfaSettings | null, - softwareTokenMfaSettings: IMfaSettings | null, - callback: NodeCallback, - userAgentValue?: string - ): void; - public sendMFASelectionAnswer( - answerChallenge: string, - callbacks: { - onSuccess: (session: CognitoUserSession) => void; - onFailure: (err: any) => void; - mfaRequired?: ( - challengeName: ChallengeName, - challengeParameters: any - ) => void; - totpRequired?: ( - challengeName: ChallengeName, - challengeParameters: any - ) => void; - }, - userAgentValue?: string - ): void; -} diff --git a/packages/amazon-cognito-identity-js/src/Client.js b/packages/amazon-cognito-identity-js/src/Client.js index 199432eb44a..1ee3f8b4dbc 100644 --- a/packages/amazon-cognito-identity-js/src/Client.js +++ b/packages/amazon-cognito-identity-js/src/Client.js @@ -32,43 +32,33 @@ export default class Client { * @param {object} params Input parameters * @returns Promise */ - promisifyRequest(operation, params, userAgentValue) { + promisifyRequest(operation, params) { return new Promise((resolve, reject) => { - this.request( - operation, - params, - (err, data) => { - if (err) { - reject( - new CognitoError(err.message, err.code, err.name, err.statusCode) - ); - } else { - resolve(data); - } - }, - userAgentValue - ); + this.request(operation, params, (err, data) => { + if (err) { + reject( + new CognitoError(err.message, err.code, err.name, err.statusCode) + ); + } else { + resolve(data); + } + }); }); } - requestWithRetry(operation, params, callback, userAgentValue) { + requestWithRetry(operation, params, callback) { const MAX_DELAY_IN_MILLIS = 5 * 1000; jitteredExponentialRetry( p => new Promise((res, rej) => { - this.request( - operation, - p, - (error, result) => { - if (error) { - rej(error); - } else { - res(result); - } - }, - userAgentValue - ); + this.request(operation, p, (error, result) => { + if (error) { + rej(error); + } else { + res(result); + } + }); }), [params], MAX_DELAY_IN_MILLIS @@ -85,11 +75,11 @@ export default class Client { * @param {function} callback Callback called when a response is returned * @returns {void} */ - request(operation, params, callback, userAgentValue) { + request(operation, params, callback) { const headers = { 'Content-Type': 'application/x-amz-json-1.1', 'X-Amz-Target': `AWSCognitoIdentityProviderService.${operation}`, - 'X-Amz-User-Agent': userAgentValue || getAmplifyUserAgent(), + 'X-Amz-User-Agent': getAmplifyUserAgent(), 'Cache-Control': 'no-store', }; diff --git a/packages/amazon-cognito-identity-js/src/CognitoUser.js b/packages/amazon-cognito-identity-js/src/CognitoUser.js index 345e8bf88c0..9dc61d9e228 100644 --- a/packages/amazon-cognito-identity-js/src/CognitoUser.js +++ b/packages/amazon-cognito-identity-js/src/CognitoUser.js @@ -3,10 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { Buffer } from 'buffer'; +import { Sha256 } from '@aws-crypto/sha256-js'; +import { Platform } from './Platform'; + +import BigInteger from './BigInteger'; +import AuthenticationHelper from './AuthenticationHelper'; +import CognitoAccessToken from './CognitoAccessToken'; +import CognitoIdToken from './CognitoIdToken'; import CognitoRefreshToken from './CognitoRefreshToken'; import CognitoUserSession from './CognitoUserSession'; +import DateHelper from './DateHelper'; import CognitoUserAttribute from './CognitoUserAttribute'; -import { InternalCognitoUser } from './internals'; +import StorageHelper from './StorageHelper'; /** * @callback nodeCallback @@ -47,8 +56,83 @@ import { InternalCognitoUser } from './internals'; * @param {bool=} userConfirmationNecessary User must be confirmed. */ +const isNavigatorAvailable = typeof navigator !== 'undefined'; +const userAgent = isNavigatorAvailable + ? Platform.isReactNative + ? 'react-native' + : navigator.userAgent + : 'nodejs'; + /** @class */ -export default class CognitoUser extends InternalCognitoUser { +export default class CognitoUser { + /** + * Constructs a new CognitoUser object + * @param {object} data Creation options + * @param {string} data.Username The user's username. + * @param {CognitoUserPool} data.Pool Pool containing the user. + * @param {object} data.Storage Optional storage object. + */ + constructor(data) { + if (data == null || data.Username == null || data.Pool == null) { + throw new Error('Username and Pool information are required.'); + } + + this.username = data.Username || ''; + this.pool = data.Pool; + this.Session = null; + + this.client = data.Pool.client; + + this.signInUserSession = null; + this.authenticationFlowType = 'USER_SRP_AUTH'; + + this.storage = data.Storage || new StorageHelper().getStorage(); + + this.keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; + this.userDataKey = `${this.keyPrefix}.${this.username}.userData`; + } + + /** + * Sets the session for this user + * @param {CognitoUserSession} signInUserSession the session + * @returns {void} + */ + setSignInUserSession(signInUserSession) { + this.clearCachedUserData(); + this.signInUserSession = signInUserSession; + this.cacheTokens(); + } + + /** + * @returns {CognitoUserSession} the current session for this user + */ + getSignInUserSession() { + return this.signInUserSession; + } + + /** + * @returns {string} the user's username + */ + getUsername() { + return this.username; + } + + /** + * @returns {String} the authentication flow type + */ + getAuthenticationFlowType() { + return this.authenticationFlowType; + } + + /** + * sets authentication flow type + * @param {string} authenticationFlowType New value. + * @returns {void} + */ + setAuthenticationFlowType(authenticationFlowType) { + this.authenticationFlowType = authenticationFlowType; + } + /** * This is used for authenticating the user through the custom authentication flow. * @param {AuthenticationDetails} authDetails Contains the authentication data @@ -60,7 +144,41 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ initiateAuth(authDetails, callback) { - super.initiateAuth(authDetails, callback); + const authParameters = authDetails.getAuthParameters(); + authParameters.USERNAME = this.username; + + const clientMetaData = + Object.keys(authDetails.getValidationData()).length !== 0 + ? authDetails.getValidationData() + : authDetails.getClientMetadata(); + + const jsonReq = { + AuthFlow: 'CUSTOM_AUTH', + ClientId: this.pool.getClientId(), + AuthParameters: authParameters, + ClientMetadata: clientMetaData, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + + this.client.request('InitiateAuth', jsonReq, (err, data) => { + if (err) { + return callback.onFailure(err); + } + const challengeName = data.ChallengeName; + const challengeParameters = data.ChallengeParameters; + + if (challengeName === 'CUSTOM_CHALLENGE') { + this.Session = data.Session; + return callback.customChallenge(challengeParameters); + } + this.signInUserSession = this.getCognitoUserSession( + data.AuthenticationResult + ); + this.cacheTokens(); + return callback.onSuccess(this.signInUserSession); + }); } /** @@ -79,7 +197,375 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ authenticateUser(authDetails, callback) { - super.authenticateUser(authDetails, callback); + if (this.authenticationFlowType === 'USER_PASSWORD_AUTH') { + return this.authenticateUserPlainUsernamePassword(authDetails, callback); + } else if ( + this.authenticationFlowType === 'USER_SRP_AUTH' || + this.authenticationFlowType === 'CUSTOM_AUTH' + ) { + return this.authenticateUserDefaultAuth(authDetails, callback); + } + return callback.onFailure( + new Error('Authentication flow type is invalid.') + ); + } + + /** + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + * It calls the AuthenticationHelper for SRP related + * stuff + * @param {AuthenticationDetails} authDetails Contains the authentication data + * @param {object} callback Result callback map. + * @param {onFailure} callback.onFailure Called on any error. + * @param {newPasswordRequired} callback.newPasswordRequired new + * password and any required attributes are required to continue + * @param {mfaRequired} callback.mfaRequired MFA code + * required to continue. + * @param {customChallenge} callback.customChallenge Custom challenge + * response required to continue. + * @param {authSuccess} callback.onSuccess Called on success with the new session. + * @returns {void} + */ + authenticateUserDefaultAuth(authDetails, callback) { + const authenticationHelper = new AuthenticationHelper( + this.pool.getUserPoolName() + ); + const dateHelper = new DateHelper(); + + let serverBValue; + let salt; + const authParameters = {}; + + if (this.deviceKey != null) { + authParameters.DEVICE_KEY = this.deviceKey; + } + + authParameters.USERNAME = this.username; + authenticationHelper.getLargeAValue((errOnAValue, aValue) => { + // getLargeAValue callback start + if (errOnAValue) { + callback.onFailure(errOnAValue); + } + + authParameters.SRP_A = aValue.toString(16); + + if (this.authenticationFlowType === 'CUSTOM_AUTH') { + authParameters.CHALLENGE_NAME = 'SRP_A'; + } + + const clientMetaData = + Object.keys(authDetails.getValidationData()).length !== 0 + ? authDetails.getValidationData() + : authDetails.getClientMetadata(); + + const jsonReq = { + AuthFlow: this.authenticationFlowType, + ClientId: this.pool.getClientId(), + AuthParameters: authParameters, + ClientMetadata: clientMetaData, + }; + if (this.getUserContextData(this.username)) { + jsonReq.UserContextData = this.getUserContextData(this.username); + } + + this.client.request('InitiateAuth', jsonReq, (err, data) => { + if (err) { + return callback.onFailure(err); + } + + const challengeParameters = data.ChallengeParameters; + + this.username = challengeParameters.USER_ID_FOR_SRP; + this.userDataKey = `${this.keyPrefix}.${this.username}.userData`; + serverBValue = new BigInteger(challengeParameters.SRP_B, 16); + salt = new BigInteger(challengeParameters.SALT, 16); + this.getCachedDeviceKeyAndPassword(); + + authenticationHelper.getPasswordAuthenticationKey( + this.username, + authDetails.getPassword(), + serverBValue, + salt, + (errOnHkdf, hkdf) => { + // getPasswordAuthenticationKey callback start + if (errOnHkdf) { + callback.onFailure(errOnHkdf); + } + + const dateNow = dateHelper.getNowString(); + + const concatBuffer = Buffer.concat([ + Buffer.from(this.pool.getUserPoolName(), 'utf8'), + Buffer.from(this.username, 'utf8'), + Buffer.from(challengeParameters.SECRET_BLOCK, 'base64'), + Buffer.from(dateNow, 'utf8'), + ]); + + const awsCryptoHash = new Sha256(hkdf); + awsCryptoHash.update(concatBuffer); + + const resultFromAWSCrypto = awsCryptoHash.digestSync(); + const signatureString = + Buffer.from(resultFromAWSCrypto).toString('base64'); + + const challengeResponses = {}; + + challengeResponses.USERNAME = this.username; + challengeResponses.PASSWORD_CLAIM_SECRET_BLOCK = + challengeParameters.SECRET_BLOCK; + challengeResponses.TIMESTAMP = dateNow; + challengeResponses.PASSWORD_CLAIM_SIGNATURE = signatureString; + + if (this.deviceKey != null) { + challengeResponses.DEVICE_KEY = this.deviceKey; + } + + const respondToAuthChallenge = (challenge, challengeCallback) => + this.client.request( + 'RespondToAuthChallenge', + challenge, + (errChallenge, dataChallenge) => { + if ( + errChallenge && + errChallenge.code === 'ResourceNotFoundException' && + errChallenge.message.toLowerCase().indexOf('device') !== -1 + ) { + challengeResponses.DEVICE_KEY = null; + this.deviceKey = null; + this.randomPassword = null; + this.deviceGroupKey = null; + this.clearCachedDeviceKeyAndPassword(); + return respondToAuthChallenge(challenge, challengeCallback); + } + return challengeCallback(errChallenge, dataChallenge); + } + ); + + const jsonReqResp = { + ChallengeName: 'PASSWORD_VERIFIER', + ClientId: this.pool.getClientId(), + ChallengeResponses: challengeResponses, + Session: data.Session, + ClientMetadata: clientMetaData, + }; + if (this.getUserContextData()) { + jsonReqResp.UserContextData = this.getUserContextData(); + } + respondToAuthChallenge( + jsonReqResp, + (errAuthenticate, dataAuthenticate) => { + if (errAuthenticate) { + return callback.onFailure(errAuthenticate); + } + + return this.authenticateUserInternal( + dataAuthenticate, + authenticationHelper, + callback + ); + } + ); + return undefined; + // getPasswordAuthenticationKey callback end + } + ); + return undefined; + }); + // getLargeAValue callback end + }); + } + + /** + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + * @param {AuthenticationDetails} authDetails Contains the authentication data. + * @param {object} callback Result callback map. + * @param {onFailure} callback.onFailure Called on any error. + * @param {mfaRequired} callback.mfaRequired MFA code + * required to continue. + * @param {authSuccess} callback.onSuccess Called on success with the new session. + * @returns {void} + */ + authenticateUserPlainUsernamePassword(authDetails, callback) { + const authParameters = {}; + authParameters.USERNAME = this.username; + authParameters.PASSWORD = authDetails.getPassword(); + if (!authParameters.PASSWORD) { + callback.onFailure(new Error('PASSWORD parameter is required')); + return; + } + const authenticationHelper = new AuthenticationHelper( + this.pool.getUserPoolName() + ); + this.getCachedDeviceKeyAndPassword(); + if (this.deviceKey != null) { + authParameters.DEVICE_KEY = this.deviceKey; + } + + const clientMetaData = + Object.keys(authDetails.getValidationData()).length !== 0 + ? authDetails.getValidationData() + : authDetails.getClientMetadata(); + + const jsonReq = { + AuthFlow: 'USER_PASSWORD_AUTH', + ClientId: this.pool.getClientId(), + AuthParameters: authParameters, + ClientMetadata: clientMetaData, + }; + if (this.getUserContextData(this.username)) { + jsonReq.UserContextData = this.getUserContextData(this.username); + } + // USER_PASSWORD_AUTH happens in a single round-trip: client sends userName and password, + // Cognito UserPools verifies password and returns tokens. + this.client.request('InitiateAuth', jsonReq, (err, authResult) => { + if (err) { + return callback.onFailure(err); + } + return this.authenticateUserInternal( + authResult, + authenticationHelper, + callback + ); + }); + } + + /** + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + * @param {object} dataAuthenticate authentication data + * @param {object} authenticationHelper helper created + * @param {callback} callback passed on from caller + * @returns {void} + */ + authenticateUserInternal(dataAuthenticate, authenticationHelper, callback) { + const challengeName = dataAuthenticate.ChallengeName; + const challengeParameters = dataAuthenticate.ChallengeParameters; + + if (challengeName === 'SMS_MFA') { + this.Session = dataAuthenticate.Session; + return callback.mfaRequired(challengeName, challengeParameters); + } + + if (challengeName === 'SELECT_MFA_TYPE') { + this.Session = dataAuthenticate.Session; + return callback.selectMFAType(challengeName, challengeParameters); + } + + if (challengeName === 'MFA_SETUP') { + this.Session = dataAuthenticate.Session; + return callback.mfaSetup(challengeName, challengeParameters); + } + + if (challengeName === 'SOFTWARE_TOKEN_MFA') { + this.Session = dataAuthenticate.Session; + return callback.totpRequired(challengeName, challengeParameters); + } + + if (challengeName === 'CUSTOM_CHALLENGE') { + this.Session = dataAuthenticate.Session; + return callback.customChallenge(challengeParameters); + } + + if (challengeName === 'NEW_PASSWORD_REQUIRED') { + this.Session = dataAuthenticate.Session; + + let userAttributes = null; + let rawRequiredAttributes = null; + const requiredAttributes = []; + const userAttributesPrefix = + authenticationHelper.getNewPasswordRequiredChallengeUserAttributePrefix(); + + if (challengeParameters) { + userAttributes = JSON.parse( + dataAuthenticate.ChallengeParameters.userAttributes + ); + rawRequiredAttributes = JSON.parse( + dataAuthenticate.ChallengeParameters.requiredAttributes + ); + } + + if (rawRequiredAttributes) { + for (let i = 0; i < rawRequiredAttributes.length; i++) { + requiredAttributes[i] = rawRequiredAttributes[i].substr( + userAttributesPrefix.length + ); + } + } + return callback.newPasswordRequired(userAttributes, requiredAttributes); + } + + if (challengeName === 'DEVICE_SRP_AUTH') { + this.Session = dataAuthenticate.Session; + this.getDeviceResponse(callback); + return undefined; + } + + this.signInUserSession = this.getCognitoUserSession( + dataAuthenticate.AuthenticationResult + ); + this.challengeName = challengeName; + this.cacheTokens(); + + const newDeviceMetadata = + dataAuthenticate.AuthenticationResult.NewDeviceMetadata; + if (newDeviceMetadata == null) { + return callback.onSuccess(this.signInUserSession); + } + + authenticationHelper.generateHashDevice( + dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey, + dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey, + errGenHash => { + if (errGenHash) { + return callback.onFailure(errGenHash); + } + + const deviceSecretVerifierConfig = { + Salt: Buffer.from( + authenticationHelper.getSaltDevices(), + 'hex' + ).toString('base64'), + PasswordVerifier: Buffer.from( + authenticationHelper.getVerifierDevices(), + 'hex' + ).toString('base64'), + }; + + this.verifierDevices = deviceSecretVerifierConfig.PasswordVerifier; + this.deviceGroupKey = newDeviceMetadata.DeviceGroupKey; + this.randomPassword = authenticationHelper.getRandomPassword(); + + this.client.request( + 'ConfirmDevice', + { + DeviceKey: newDeviceMetadata.DeviceKey, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + DeviceSecretVerifierConfig: deviceSecretVerifierConfig, + DeviceName: userAgent, + }, + (errConfirm, dataConfirm) => { + if (errConfirm) { + return callback.onFailure(errConfirm); + } + + this.deviceKey = + dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey; + this.cacheDeviceKeyAndPassword(); + if (dataConfirm.UserConfirmationNecessary === true) { + return callback.onSuccess( + this.signInUserSession, + dataConfirm.UserConfirmationNecessary + ); + } + return callback.onSuccess(this.signInUserSession); + } + ); + return undefined; + } + ); + return undefined; } /** @@ -103,12 +589,51 @@ export default class CognitoUser extends InternalCognitoUser { callback, clientMetadata ) { - super.completeNewPasswordChallenge( - newPassword, - requiredAttributeData, - callback, - clientMetadata + if (!newPassword) { + return callback.onFailure(new Error('New password is required.')); + } + const authenticationHelper = new AuthenticationHelper( + this.pool.getUserPoolName() + ); + const userAttributesPrefix = + authenticationHelper.getNewPasswordRequiredChallengeUserAttributePrefix(); + + const finalUserAttributes = {}; + if (requiredAttributeData) { + Object.keys(requiredAttributeData).forEach(key => { + finalUserAttributes[userAttributesPrefix + key] = + requiredAttributeData[key]; + }); + } + + finalUserAttributes.NEW_PASSWORD = newPassword; + finalUserAttributes.USERNAME = this.username; + const jsonReq = { + ChallengeName: 'NEW_PASSWORD_REQUIRED', + ClientId: this.pool.getClientId(), + ChallengeResponses: finalUserAttributes, + Session: this.Session, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + + this.client.request( + 'RespondToAuthChallenge', + jsonReq, + (errAuthenticate, dataAuthenticate) => { + if (errAuthenticate) { + return callback.onFailure(errAuthenticate); + } + return this.authenticateUserInternal( + dataAuthenticate, + authenticationHelper, + callback + ); + } ); + return undefined; } /** @@ -123,7 +648,111 @@ export default class CognitoUser extends InternalCognitoUser { * @private */ getDeviceResponse(callback, clientMetadata) { - super.getDeviceResponse(callback, clientMetadata); + const authenticationHelper = new AuthenticationHelper(this.deviceGroupKey); + const dateHelper = new DateHelper(); + + const authParameters = {}; + + authParameters.USERNAME = this.username; + authParameters.DEVICE_KEY = this.deviceKey; + authenticationHelper.getLargeAValue((errAValue, aValue) => { + // getLargeAValue callback start + if (errAValue) { + callback.onFailure(errAValue); + } + + authParameters.SRP_A = aValue.toString(16); + + const jsonReq = { + ChallengeName: 'DEVICE_SRP_AUTH', + ClientId: this.pool.getClientId(), + ChallengeResponses: authParameters, + ClientMetadata: clientMetadata, + Session: this.Session, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => { + if (err) { + return callback.onFailure(err); + } + + const challengeParameters = data.ChallengeParameters; + + const serverBValue = new BigInteger(challengeParameters.SRP_B, 16); + const salt = new BigInteger(challengeParameters.SALT, 16); + + authenticationHelper.getPasswordAuthenticationKey( + this.deviceKey, + this.randomPassword, + serverBValue, + salt, + (errHkdf, hkdf) => { + // getPasswordAuthenticationKey callback start + if (errHkdf) { + return callback.onFailure(errHkdf); + } + + const dateNow = dateHelper.getNowString(); + + const concatBuffer = Buffer.concat([ + Buffer.from(this.deviceGroupKey, 'utf8'), + Buffer.from(this.deviceKey, 'utf8'), + Buffer.from(challengeParameters.SECRET_BLOCK, 'base64'), + Buffer.from(dateNow, 'utf8'), + ]); + + const awsCryptoHash = new Sha256(hkdf); + awsCryptoHash.update(concatBuffer); + + const resultFromAWSCrypto = awsCryptoHash.digestSync(); + const signatureString = + Buffer.from(resultFromAWSCrypto).toString('base64'); + + const challengeResponses = {}; + + challengeResponses.USERNAME = this.username; + challengeResponses.PASSWORD_CLAIM_SECRET_BLOCK = + challengeParameters.SECRET_BLOCK; + challengeResponses.TIMESTAMP = dateNow; + challengeResponses.PASSWORD_CLAIM_SIGNATURE = signatureString; + challengeResponses.DEVICE_KEY = this.deviceKey; + + const jsonReqResp = { + ChallengeName: 'DEVICE_PASSWORD_VERIFIER', + ClientId: this.pool.getClientId(), + ChallengeResponses: challengeResponses, + Session: data.Session, + }; + if (this.getUserContextData()) { + jsonReqResp.UserContextData = this.getUserContextData(); + } + + this.client.request( + 'RespondToAuthChallenge', + jsonReqResp, + (errAuthenticate, dataAuthenticate) => { + if (errAuthenticate) { + return callback.onFailure(errAuthenticate); + } + + this.signInUserSession = this.getCognitoUserSession( + dataAuthenticate.AuthenticationResult + ); + this.cacheTokens(); + + return callback.onSuccess(this.signInUserSession); + } + ); + return undefined; + // getPasswordAuthenticationKey callback end + } + ); + return undefined; + }); + // getLargeAValue callback end + }); } /** @@ -140,12 +769,22 @@ export default class CognitoUser extends InternalCognitoUser { callback, clientMetadata ) { - super.confirmRegistration( - confirmationCode, - forceAliasCreation, - callback, - clientMetadata - ); + const jsonReq = { + ClientId: this.pool.getClientId(), + ConfirmationCode: confirmationCode, + Username: this.username, + ForceAliasCreation: forceAliasCreation, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('ConfirmSignUp', jsonReq, err => { + if (err) { + return callback(err, null); + } + return callback(null, 'SUCCESS'); + }); } /** @@ -160,7 +799,39 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ sendCustomChallengeAnswer(answerChallenge, callback, clientMetadata) { - super.sendCustomChallengeAnswer(answerChallenge, callback, clientMetadata); + const challengeResponses = {}; + challengeResponses.USERNAME = this.username; + challengeResponses.ANSWER = answerChallenge; + + const authenticationHelper = new AuthenticationHelper( + this.pool.getUserPoolName() + ); + this.getCachedDeviceKeyAndPassword(); + if (this.deviceKey != null) { + challengeResponses.DEVICE_KEY = this.deviceKey; + } + + const jsonReq = { + ChallengeName: 'CUSTOM_CHALLENGE', + ChallengeResponses: challengeResponses, + ClientId: this.pool.getClientId(), + Session: this.Session, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => { + if (err) { + return callback.onFailure(err); + } + + return this.authenticateUserInternal( + data, + authenticationHelper, + callback + ); + }); } /** @@ -174,7 +845,116 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ sendMFACode(confirmationCode, callback, mfaType, clientMetadata) { - super.sendMFACode(confirmationCode, callback, mfaType, clientMetadata); + const challengeResponses = {}; + challengeResponses.USERNAME = this.username; + challengeResponses.SMS_MFA_CODE = confirmationCode; + const mfaTypeSelection = mfaType || 'SMS_MFA'; + if (mfaTypeSelection === 'SOFTWARE_TOKEN_MFA') { + challengeResponses.SOFTWARE_TOKEN_MFA_CODE = confirmationCode; + } + + if (this.deviceKey != null) { + challengeResponses.DEVICE_KEY = this.deviceKey; + } + + const jsonReq = { + ChallengeName: mfaTypeSelection, + ChallengeResponses: challengeResponses, + ClientId: this.pool.getClientId(), + Session: this.Session, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + + this.client.request( + 'RespondToAuthChallenge', + jsonReq, + (err, dataAuthenticate) => { + if (err) { + return callback.onFailure(err); + } + + const challengeName = dataAuthenticate.ChallengeName; + + if (challengeName === 'DEVICE_SRP_AUTH') { + this.getDeviceResponse(callback); + return undefined; + } + + this.signInUserSession = this.getCognitoUserSession( + dataAuthenticate.AuthenticationResult + ); + this.cacheTokens(); + + if (dataAuthenticate.AuthenticationResult.NewDeviceMetadata == null) { + return callback.onSuccess(this.signInUserSession); + } + + const authenticationHelper = new AuthenticationHelper( + this.pool.getUserPoolName() + ); + authenticationHelper.generateHashDevice( + dataAuthenticate.AuthenticationResult.NewDeviceMetadata + .DeviceGroupKey, + dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey, + errGenHash => { + if (errGenHash) { + return callback.onFailure(errGenHash); + } + + const deviceSecretVerifierConfig = { + Salt: Buffer.from( + authenticationHelper.getSaltDevices(), + 'hex' + ).toString('base64'), + PasswordVerifier: Buffer.from( + authenticationHelper.getVerifierDevices(), + 'hex' + ).toString('base64'), + }; + + this.verifierDevices = deviceSecretVerifierConfig.PasswordVerifier; + this.deviceGroupKey = + dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey; + this.randomPassword = authenticationHelper.getRandomPassword(); + + this.client.request( + 'ConfirmDevice', + { + DeviceKey: + dataAuthenticate.AuthenticationResult.NewDeviceMetadata + .DeviceKey, + AccessToken: this.signInUserSession + .getAccessToken() + .getJwtToken(), + DeviceSecretVerifierConfig: deviceSecretVerifierConfig, + DeviceName: userAgent, + }, + (errConfirm, dataConfirm) => { + if (errConfirm) { + return callback.onFailure(errConfirm); + } + + this.deviceKey = + dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey; + this.cacheDeviceKeyAndPassword(); + if (dataConfirm.UserConfirmationNecessary === true) { + return callback.onSuccess( + this.signInUserSession, + dataConfirm.UserConfirmationNecessary + ); + } + return callback.onSuccess(this.signInUserSession); + } + ); + return undefined; + } + ); + return undefined; + } + ); } /** @@ -186,12 +966,26 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ changePassword(oldUserPassword, newUserPassword, callback, clientMetadata) { - super.changePassword( - oldUserPassword, - newUserPassword, - callback, - clientMetadata + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'ChangePassword', + { + PreviousPassword: oldUserPassword, + ProposedPassword: newUserPassword, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + ClientMetadata: clientMetadata, + }, + err => { + if (err) { + return callback(err, null); + } + return callback(null, 'SUCCESS'); + } ); + return undefined; } /** @@ -201,7 +995,31 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ enableMFA(callback) { - super.enableMFA(callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback(new Error('User is not authenticated'), null); + } + + const mfaOptions = []; + const mfaEnabled = { + DeliveryMedium: 'SMS', + AttributeName: 'phone_number', + }; + mfaOptions.push(mfaEnabled); + + this.client.request( + 'SetUserSettings', + { + MFAOptions: mfaOptions, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + err => { + if (err) { + return callback(err, null); + } + return callback(null, 'SUCCESS'); + } + ); + return undefined; } /** @@ -212,11 +1030,25 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ setUserMfaPreference(smsMfaSettings, softwareTokenMfaSettings, callback) { - super.setUserMfaPreference( - smsMfaSettings, - softwareTokenMfaSettings, - callback + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'SetUserMFAPreference', + { + SMSMfaSettings: smsMfaSettings, + SoftwareTokenMfaSettings: softwareTokenMfaSettings, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + err => { + if (err) { + return callback(err, null); + } + return callback(null, 'SUCCESS'); + } ); + return undefined; } /** @@ -226,7 +1058,26 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ disableMFA(callback) { - super.disableMFA(callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback(new Error('User is not authenticated'), null); + } + + const mfaOptions = []; + + this.client.request( + 'SetUserSettings', + { + MFAOptions: mfaOptions, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + err => { + if (err) { + return callback(err, null); + } + return callback(null, 'SUCCESS'); + } + ); + return undefined; } /** @@ -236,7 +1087,25 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ deleteUser(callback, clientMetadata) { - super.deleteUser(callback, clientMetadata); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'DeleteUser', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + ClientMetadata: clientMetadata, + }, + err => { + if (err) { + return callback(err, null); + } + this.clearCachedUser(); + return callback(null, 'SUCCESS'); + } + ); + return undefined; } /** @@ -250,7 +1119,29 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ updateAttributes(attributes, callback, clientMetadata) { - super.updateAttributes(attributes, callback, clientMetadata); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'UpdateUserAttributes', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + UserAttributes: attributes, + ClientMetadata: clientMetadata, + }, + (err,result) => { + if (err) { + return callback(err, null); + } + + // update cached user + return this.getUserData(() => callback(null, 'SUCCESS', result), { + bypassCache: true, + }); + } + ); + return undefined; } /** @@ -259,7 +1150,35 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ getUserAttributes(callback) { - super.getUserAttributes(callback); + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'GetUser', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + (err, userData) => { + if (err) { + return callback(err, null); + } + + const attributeList = []; + + for (let i = 0; i < userData.UserAttributes.length; i++) { + const attribute = { + Name: userData.UserAttributes[i].Name, + Value: userData.UserAttributes[i].Value, + }; + const userAttribute = new CognitoUserAttribute(attribute); + attributeList.push(userAttribute); + } + + return callback(null, attributeList); + } + ); + return undefined; } /** @@ -271,7 +1190,50 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ getMFAOptions(callback) { - super.getMFAOptions(callback); + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'GetUser', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + (err, userData) => { + if (err) { + return callback(err, null); + } + + return callback(null, userData.MFAOptions); + } + ); + return undefined; + } + + /** + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + */ + createGetUserRequest() { + return this.client.promisifyRequest('GetUser', { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }); + } + + /** + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + */ + refreshSessionIfPossible(options = {}) { + // best effort, if not possible + return new Promise(resolve => { + const refresh = this.signInUserSession.getRefreshToken(); + if (refresh && refresh.getToken()) { + this.refreshSession(refresh, resolve, options.clientMetadata); + } else { + resolve(); + } + }); } /** @@ -287,7 +1249,73 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ getUserData(callback, params) { - super.getUserData(callback, params); + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + this.clearCachedUserData(); + return callback(new Error('User is not authenticated'), null); + } + + const userData = this.getUserDataFromCache(); + + if (!userData) { + this.fetchUserData() + .then(data => { + callback(null, data); + }) + .catch(callback); + return; + } + + if (this.isFetchUserDataAndTokenRequired(params)) { + this.fetchUserData() + .then(data => { + return this.refreshSessionIfPossible(params).then(() => data); + }) + .then(data => callback(null, data)) + .catch(callback); + return; + } + + try { + callback(null, JSON.parse(userData)); + return; + } catch (err) { + this.clearCachedUserData(); + callback(err, null); + return; + } + } + + /** + * + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + */ + getUserDataFromCache() { + const userData = this.storage.getItem(this.userDataKey); + + return userData; + } + + /** + * + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + */ + isFetchUserDataAndTokenRequired(params) { + const { bypassCache = false } = params || {}; + + return bypassCache; + } + /** + * + * PRIVATE ONLY: This is an internal only method and should not + * be directly called by the consumers. + */ + fetchUserData() { + return this.createGetUserRequest().then(data => { + this.cacheUserData(data); + return data; + }); } /** @@ -297,7 +1325,28 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ deleteAttributes(attributeList, callback) { - super.deleteAttributes(attributeList, callback); + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + return callback(new Error('User is not authenticated'), null); + } + + this.client.request( + 'DeleteUserAttributes', + { + UserAttributeNames: attributeList, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + err => { + if (err) { + return callback(err, null); + } + + // update cached user + return this.getUserData(() => callback(null, 'SUCCESS'), { + bypassCache: true, + }); + } + ); + return undefined; } /** @@ -307,7 +1356,18 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ resendConfirmationCode(callback, clientMetadata) { - super.resendConfirmationCode(callback, clientMetadata); + const jsonReq = { + ClientId: this.pool.getClientId(), + Username: this.username, + ClientMetadata: clientMetadata, + }; + + this.client.request('ResendConfirmationCode', jsonReq, (err, result) => { + if (err) { + return callback(err, null); + } + return callback(null, result); + }); } /** @@ -324,7 +1384,66 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ getSession(callback, options = {}) { - super.getSession(callback, options); + if (this.username == null) { + return callback( + new Error('Username is null. Cannot retrieve a new session'), + null + ); + } + + if (this.signInUserSession != null && this.signInUserSession.isValid()) { + return callback(null, this.signInUserSession); + } + + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ + this.username + }`; + const idTokenKey = `${keyPrefix}.idToken`; + const accessTokenKey = `${keyPrefix}.accessToken`; + const refreshTokenKey = `${keyPrefix}.refreshToken`; + const clockDriftKey = `${keyPrefix}.clockDrift`; + + if (this.storage.getItem(idTokenKey)) { + const idToken = new CognitoIdToken({ + IdToken: this.storage.getItem(idTokenKey), + }); + const accessToken = new CognitoAccessToken({ + AccessToken: this.storage.getItem(accessTokenKey), + }); + const refreshToken = new CognitoRefreshToken({ + RefreshToken: this.storage.getItem(refreshTokenKey), + }); + const clockDrift = parseInt(this.storage.getItem(clockDriftKey), 0) || 0; + + const sessionData = { + IdToken: idToken, + AccessToken: accessToken, + RefreshToken: refreshToken, + ClockDrift: clockDrift, + }; + const cachedSession = new CognitoUserSession(sessionData); + + if (cachedSession.isValid()) { + this.signInUserSession = cachedSession; + return callback(null, this.signInUserSession); + } + + if (!refreshToken.getToken()) { + return callback( + new Error('Cannot retrieve a new session. Please authenticate.'), + null + ); + } + + this.refreshSession(refreshToken, callback, options.clientMetadata); + } else { + callback( + new Error('Local storage is missing an ID Token, Please authenticate'), + null + ); + } + + return undefined; } /** @@ -335,7 +1454,196 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ refreshSession(refreshToken, callback, clientMetadata) { - super.refreshSession(refreshToken, callback, clientMetadata); + const wrappedCallback = this.pool.wrapRefreshSessionCallback + ? this.pool.wrapRefreshSessionCallback(callback) + : callback; + const authParameters = {}; + authParameters.REFRESH_TOKEN = refreshToken.getToken(); + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; + const lastUserKey = `${keyPrefix}.LastAuthUser`; + + if (this.storage.getItem(lastUserKey)) { + this.username = this.storage.getItem(lastUserKey); + const deviceKeyKey = `${keyPrefix}.${this.username}.deviceKey`; + this.deviceKey = this.storage.getItem(deviceKeyKey); + authParameters.DEVICE_KEY = this.deviceKey; + } + + const jsonReq = { + ClientId: this.pool.getClientId(), + AuthFlow: 'REFRESH_TOKEN_AUTH', + AuthParameters: authParameters, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('InitiateAuth', jsonReq, (err, authResult) => { + if (err) { + if (err.code === 'NotAuthorizedException') { + this.clearCachedUser(); + } + return wrappedCallback(err, null); + } + if (authResult) { + const authenticationResult = authResult.AuthenticationResult; + if ( + !Object.prototype.hasOwnProperty.call( + authenticationResult, + 'RefreshToken' + ) + ) { + authenticationResult.RefreshToken = refreshToken.getToken(); + } + this.signInUserSession = + this.getCognitoUserSession(authenticationResult); + this.cacheTokens(); + return wrappedCallback(null, this.signInUserSession); + } + return undefined; + }); + } + + /** + * This is used to save the session tokens to local storage + * @returns {void} + */ + cacheTokens() { + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; + const idTokenKey = `${keyPrefix}.${this.username}.idToken`; + const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`; + const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`; + const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`; + const lastUserKey = `${keyPrefix}.LastAuthUser`; + + this.storage.setItem( + idTokenKey, + this.signInUserSession.getIdToken().getJwtToken() + ); + this.storage.setItem( + accessTokenKey, + this.signInUserSession.getAccessToken().getJwtToken() + ); + this.storage.setItem( + refreshTokenKey, + this.signInUserSession.getRefreshToken().getToken() + ); + this.storage.setItem( + clockDriftKey, + `${this.signInUserSession.getClockDrift()}` + ); + this.storage.setItem(lastUserKey, this.username); + } + + /** + * This is to cache user data + */ + cacheUserData(userData) { + this.storage.setItem(this.userDataKey, JSON.stringify(userData)); + } + + /** + * This is to remove cached user data + */ + clearCachedUserData() { + this.storage.removeItem(this.userDataKey); + } + + clearCachedUser() { + this.clearCachedTokens(); + this.clearCachedUserData(); + } + + /** + * This is used to cache the device key and device group and device password + * @returns {void} + */ + cacheDeviceKeyAndPassword() { + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ + this.username + }`; + const deviceKeyKey = `${keyPrefix}.deviceKey`; + const randomPasswordKey = `${keyPrefix}.randomPasswordKey`; + const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`; + + this.storage.setItem(deviceKeyKey, this.deviceKey); + this.storage.setItem(randomPasswordKey, this.randomPassword); + this.storage.setItem(deviceGroupKeyKey, this.deviceGroupKey); + } + + /** + * This is used to get current device key and device group and device password + * @returns {void} + */ + getCachedDeviceKeyAndPassword() { + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ + this.username + }`; + const deviceKeyKey = `${keyPrefix}.deviceKey`; + const randomPasswordKey = `${keyPrefix}.randomPasswordKey`; + const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`; + + if (this.storage.getItem(deviceKeyKey)) { + this.deviceKey = this.storage.getItem(deviceKeyKey); + this.randomPassword = this.storage.getItem(randomPasswordKey); + this.deviceGroupKey = this.storage.getItem(deviceGroupKeyKey); + } + } + + /** + * This is used to clear the device key info from local storage + * @returns {void} + */ + clearCachedDeviceKeyAndPassword() { + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ + this.username + }`; + const deviceKeyKey = `${keyPrefix}.deviceKey`; + const randomPasswordKey = `${keyPrefix}.randomPasswordKey`; + const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`; + + this.storage.removeItem(deviceKeyKey); + this.storage.removeItem(randomPasswordKey); + this.storage.removeItem(deviceGroupKeyKey); + } + + /** + * This is used to clear the session tokens from local storage + * @returns {void} + */ + clearCachedTokens() { + const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; + const idTokenKey = `${keyPrefix}.${this.username}.idToken`; + const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`; + const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`; + const lastUserKey = `${keyPrefix}.LastAuthUser`; + const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`; + + this.storage.removeItem(idTokenKey); + this.storage.removeItem(accessTokenKey); + this.storage.removeItem(refreshTokenKey); + this.storage.removeItem(lastUserKey); + this.storage.removeItem(clockDriftKey); + } + + /** + * This is used to build a user session from tokens retrieved in the authentication result + * @param {object} authResult Successful auth response from server. + * @returns {CognitoUserSession} The new user session. + * @private + */ + getCognitoUserSession(authResult) { + const idToken = new CognitoIdToken(authResult); + const accessToken = new CognitoAccessToken(authResult); + const refreshToken = new CognitoRefreshToken(authResult); + + const sessionData = { + IdToken: idToken, + AccessToken: accessToken, + RefreshToken: refreshToken, + }; + + return new CognitoUserSession(sessionData); } /** @@ -349,7 +1657,23 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ forgotPassword(callback, clientMetadata) { - super.forgotPassword(callback, clientMetadata); + const jsonReq = { + ClientId: this.pool.getClientId(), + Username: this.username, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('ForgotPassword', jsonReq, (err, data) => { + if (err) { + return callback.onFailure(err); + } + if (typeof callback.inputVerificationCode === 'function') { + return callback.inputVerificationCode(data); + } + return callback.onSuccess(data); + }); } /** @@ -363,12 +1687,22 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ confirmPassword(confirmationCode, newPassword, callback, clientMetadata) { - super.confirmPassword( - confirmationCode, - newPassword, - callback, - clientMetadata - ); + const jsonReq = { + ClientId: this.pool.getClientId(), + Username: this.username, + ConfirmationCode: confirmationCode, + Password: newPassword, + ClientMetadata: clientMetadata, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('ConfirmForgotPassword', jsonReq, err => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess('SUCCESS'); + }); } /** @@ -381,7 +1715,28 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ getAttributeVerificationCode(attributeName, callback, clientMetadata) { - super.getAttributeVerificationCode(attributeName, callback, clientMetadata); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'GetUserAttributeVerificationCode', + { + AttributeName: attributeName, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + ClientMetadata: clientMetadata, + }, + (err, data) => { + if (err) { + return callback.onFailure(err); + } + if (typeof callback.inputVerificationCode === 'function') { + return callback.inputVerificationCode(data); + } + return callback.onSuccess('SUCCESS'); + } + ); + return undefined; } /** @@ -394,7 +1749,25 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ verifyAttribute(attributeName, confirmationCode, callback) { - super.verifyAttribute(attributeName, confirmationCode, callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'VerifyUserAttribute', + { + AttributeName: attributeName, + Code: confirmationCode, + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + err => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess('SUCCESS'); + } + ); + return undefined; } /** @@ -405,7 +1778,24 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ getDevice(callback) { - super.getDevice(callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'GetDevice', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + DeviceKey: this.deviceKey, + }, + (err, data) => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess(data); + } + ); + return undefined; } /** @@ -417,7 +1807,24 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ forgetSpecificDevice(deviceKey, callback) { - super.forgetSpecificDevice(deviceKey, callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'ForgetDevice', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + DeviceKey: deviceKey, + }, + err => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess('SUCCESS'); + } + ); + return undefined; } /** @@ -428,7 +1835,16 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ forgetDevice(callback) { - super.forgetDevice(callback); + this.forgetSpecificDevice(this.deviceKey, { + onFailure: callback.onFailure, + onSuccess: result => { + this.deviceKey = null; + this.deviceGroupKey = null; + this.randomPassword = null; + this.clearCachedDeviceKeyAndPassword(); + return callback.onSuccess(result); + }, + }); } /** @@ -439,7 +1855,25 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ setDeviceStatusRemembered(callback) { - super.setDeviceStatusRemembered(callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'UpdateDeviceStatus', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + DeviceKey: this.deviceKey, + DeviceRememberedStatus: 'remembered', + }, + err => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess('SUCCESS'); + } + ); + return undefined; } /** @@ -450,7 +1884,25 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ setDeviceStatusNotRemembered(callback) { - super.setDeviceStatusNotRemembered(callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'UpdateDeviceStatus', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + DeviceKey: this.deviceKey, + DeviceRememberedStatus: 'not_remembered', + }, + err => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess('SUCCESS'); + } + ); + return undefined; } /** @@ -464,7 +1916,25 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ listDevices(limit, paginationToken, callback) { - super.listDevices(limit, paginationToken, callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + const requestParams = { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + Limit: limit, + }; + + if (paginationToken) { + requestParams.PaginationToken = paginationToken; + } + + this.client.request('ListDevices', requestParams, (err, data) => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess(data); + }); + return undefined; } /** @@ -475,7 +1945,24 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ globalSignOut(callback) { - super.globalSignOut(callback); + if (this.signInUserSession == null || !this.signInUserSession.isValid()) { + return callback.onFailure(new Error('User is not authenticated')); + } + + this.client.request( + 'GlobalSignOut', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + err => { + if (err) { + return callback.onFailure(err); + } + this.clearCachedUser(); + return callback.onSuccess('SUCCESS'); + } + ); + return undefined; } /** @@ -483,15 +1970,92 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ signOut(revokeTokenCallback) { - super.signOut(revokeTokenCallback); + // If tokens won't be revoked, we just clean the client data. + if (!revokeTokenCallback || typeof revokeTokenCallback !== 'function') { + this.cleanClientData(); + + return; + } + + this.getSession((error, _session) => { + if (error) { + return revokeTokenCallback(error); + } + + this.revokeTokens(err => { + this.cleanClientData(); + + revokeTokenCallback(err); + }); + }); } - revokeTokens(revokeTokenCallback = () => { }) { - super.revokeTokens(revokeTokenCallback); + revokeTokens(revokeTokenCallback = () => {}) { + if (typeof revokeTokenCallback !== 'function') { + throw new Error('Invalid revokeTokenCallback. It should be a function.'); + } + + const tokensToBeRevoked = []; + + if (!this.signInUserSession) { + const error = new Error('User is not authenticated'); + + return revokeTokenCallback(error); + } + + if (!this.signInUserSession.getAccessToken()) { + const error = new Error('No Access token available'); + + return revokeTokenCallback(error); + } + + const refreshToken = this.signInUserSession.getRefreshToken().getToken(); + const accessToken = this.signInUserSession.getAccessToken(); + + if (this.isSessionRevocable(accessToken)) { + if (refreshToken) { + return this.revokeToken({ + token: refreshToken, + callback: revokeTokenCallback, + }); + } + } + revokeTokenCallback(); + } + + isSessionRevocable(token) { + if (token && typeof token.decodePayload === 'function') { + try { + const { origin_jti } = token.decodePayload(); + return !!origin_jti; + } catch (err) { + // Nothing to do, token doesnt have origin_jti claim + } + } + + return false; + } + + cleanClientData() { + this.signInUserSession = null; + this.clearCachedUser(); } revokeToken({ token, callback }) { - super.revokeToken({ token, callback }); + this.client.requestWithRetry( + 'RevokeToken', + { + Token: token, + ClientId: this.pool.getClientId(), + }, + err => { + if (err) { + return callback(err); + } + + callback(); + } + ); } /** @@ -501,7 +2065,47 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ sendMFASelectionAnswer(answerChallenge, callback) { - super.sendMFASelectionAnswer(answerChallenge, callback); + const challengeResponses = {}; + challengeResponses.USERNAME = this.username; + challengeResponses.ANSWER = answerChallenge; + + const jsonReq = { + ChallengeName: 'SELECT_MFA_TYPE', + ChallengeResponses: challengeResponses, + ClientId: this.pool.getClientId(), + Session: this.Session, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => { + if (err) { + return callback.onFailure(err); + } + this.Session = data.Session; + if (answerChallenge === 'SMS_MFA') { + return callback.mfaRequired( + data.ChallengeName, + data.ChallengeParameters + ); + } + if (answerChallenge === 'SOFTWARE_TOKEN_MFA') { + return callback.totpRequired( + data.ChallengeName, + data.ChallengeParameters + ); + } + return undefined; + }); + } + + /** + * This returns the user context data for advanced security feature. + * @returns {string} the user context data from CognitoUserPool + */ + getUserContextData() { + const pool = this.pool; + return pool.getUserContextData(this.username); } /** @@ -510,7 +2114,34 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ associateSoftwareToken(callback) { - super.associateSoftwareToken(callback); + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + this.client.request( + 'AssociateSoftwareToken', + { + Session: this.Session, + }, + (err, data) => { + if (err) { + return callback.onFailure(err); + } + this.Session = data.Session; + return callback.associateSecretCode(data.SecretCode); + } + ); + } else { + this.client.request( + 'AssociateSoftwareToken', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + }, + (err, data) => { + if (err) { + return callback.onFailure(err); + } + return callback.associateSecretCode(data.SecretCode); + } + ); + } } /** @@ -521,6 +2152,62 @@ export default class CognitoUser extends InternalCognitoUser { * @returns {void} */ verifySoftwareToken(totpCode, friendlyDeviceName, callback) { - super.verifySoftwareToken(totpCode, friendlyDeviceName, callback); + if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { + this.client.request( + 'VerifySoftwareToken', + { + Session: this.Session, + UserCode: totpCode, + FriendlyDeviceName: friendlyDeviceName, + }, + (err, data) => { + if (err) { + return callback.onFailure(err); + } + this.Session = data.Session; + const challengeResponses = {}; + challengeResponses.USERNAME = this.username; + const jsonReq = { + ChallengeName: 'MFA_SETUP', + ClientId: this.pool.getClientId(), + ChallengeResponses: challengeResponses, + Session: this.Session, + }; + if (this.getUserContextData()) { + jsonReq.UserContextData = this.getUserContextData(); + } + this.client.request( + 'RespondToAuthChallenge', + jsonReq, + (errRespond, dataRespond) => { + if (errRespond) { + return callback.onFailure(errRespond); + } + this.signInUserSession = this.getCognitoUserSession( + dataRespond.AuthenticationResult + ); + this.cacheTokens(); + return callback.onSuccess(this.signInUserSession); + } + ); + return undefined; + } + ); + } else { + this.client.request( + 'VerifySoftwareToken', + { + AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), + UserCode: totpCode, + FriendlyDeviceName: friendlyDeviceName, + }, + (err, data) => { + if (err) { + return callback.onFailure(err); + } + return callback.onSuccess(data); + } + ); + } } } diff --git a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js b/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js deleted file mode 100644 index 19f0e87b79e..00000000000 --- a/packages/amazon-cognito-identity-js/src/internals/InternalCognitoUser.js +++ /dev/null @@ -1,2428 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Buffer } from 'buffer'; -import { Sha256 } from '@aws-crypto/sha256-js'; -import { Platform } from '../Platform'; - -import BigInteger from '../BigInteger'; -import AuthenticationHelper from '../AuthenticationHelper'; -import CognitoAccessToken from '../CognitoAccessToken'; -import CognitoIdToken from '../CognitoIdToken'; -import CognitoRefreshToken from '../CognitoRefreshToken'; -import CognitoUserSession from '../CognitoUserSession'; -import DateHelper from '../DateHelper'; -import CognitoUserAttribute from '../CognitoUserAttribute'; -import StorageHelper from '../StorageHelper'; - -/** - * @callback nodeCallback - * @template T result - * @param {*} err The operation failure reason, or null. - * @param {T} result The operation result. - */ - -/** - * @callback onFailure - * @param {*} err Failure reason. - */ - -/** - * @callback onSuccess - * @template T result - * @param {T} result The operation result. - */ - -/** - * @callback mfaRequired - * @param {*} details MFA challenge details. - */ - -/** - * @callback customChallenge - * @param {*} details Custom challenge details. - */ - -/** - * @callback inputVerificationCode - * @param {*} data Server response. - */ - -/** - * @callback authSuccess - * @param {CognitoUserSession} session The new session. - * @param {bool=} userConfirmationNecessary User must be confirmed. - */ - -const isNavigatorAvailable = typeof navigator !== 'undefined'; -const userAgent = isNavigatorAvailable - ? Platform.isReactNative - ? 'react-native' - : navigator.userAgent - : 'nodejs'; - -/** @class */ -export class InternalCognitoUser { - /** - * Constructs a new CognitoUser object - * @param {object} data Creation options - * @param {string} data.Username The user's username. - * @param {CognitoUserPool} data.Pool Pool containing the user. - * @param {object} data.Storage Optional storage object. - */ - constructor(data) { - if (data == null || data.Username == null || data.Pool == null) { - throw new Error('Username and Pool information are required.'); - } - - this.username = data.Username || ''; - this.pool = data.Pool; - this.Session = null; - - this.client = data.Pool.client; - - this.signInUserSession = null; - this.authenticationFlowType = 'USER_SRP_AUTH'; - - this.storage = data.Storage || new StorageHelper().getStorage(); - - this.keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; - this.userDataKey = `${this.keyPrefix}.${this.username}.userData`; - } - - /** - * Sets the session for this user - * @param {CognitoUserSession} signInUserSession the session - * @returns {void} - */ - setSignInUserSession(signInUserSession) { - this.clearCachedUserData(); - this.signInUserSession = signInUserSession; - this.cacheTokens(); - } - - /** - * @returns {CognitoUserSession} the current session for this user - */ - getSignInUserSession() { - return this.signInUserSession; - } - - /** - * @returns {string} the user's username - */ - getUsername() { - return this.username; - } - - /** - * @returns {String} the authentication flow type - */ - getAuthenticationFlowType() { - return this.authenticationFlowType; - } - - /** - * sets authentication flow type - * @param {string} authenticationFlowType New value. - * @returns {void} - */ - setAuthenticationFlowType(authenticationFlowType) { - this.authenticationFlowType = authenticationFlowType; - } - - /** - * This is used for authenticating the user through the custom authentication flow. - * @param {AuthenticationDetails} authDetails Contains the authentication data - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {customChallenge} callback.customChallenge Custom challenge - * response required to continue. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - initiateAuth(authDetails, callback, userAgentValue) { - const authParameters = authDetails.getAuthParameters(); - authParameters.USERNAME = this.username; - - const clientMetaData = - Object.keys(authDetails.getValidationData()).length !== 0 - ? authDetails.getValidationData() - : authDetails.getClientMetadata(); - - const jsonReq = { - AuthFlow: 'CUSTOM_AUTH', - ClientId: this.pool.getClientId(), - AuthParameters: authParameters, - ClientMetadata: clientMetaData, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - - this.client.request( - 'InitiateAuth', - jsonReq, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - const challengeName = data.ChallengeName; - const challengeParameters = data.ChallengeParameters; - - if (challengeName === 'CUSTOM_CHALLENGE') { - this.Session = data.Session; - return callback.customChallenge(challengeParameters); - } - this.signInUserSession = this.getCognitoUserSession( - data.AuthenticationResult - ); - this.cacheTokens(); - return callback.onSuccess(this.signInUserSession); - }, - userAgentValue - ); - } - - /** - * This is used for authenticating the user. - * stuff - * @param {AuthenticationDetails} authDetails Contains the authentication data - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {newPasswordRequired} callback.newPasswordRequired new - * password and any required attributes are required to continue - * @param {mfaRequired} callback.mfaRequired MFA code - * required to continue. - * @param {customChallenge} callback.customChallenge Custom challenge - * response required to continue. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - authenticateUser(authDetails, callback, userAgentValue) { - if (this.authenticationFlowType === 'USER_PASSWORD_AUTH') { - return this.authenticateUserPlainUsernamePassword( - authDetails, - callback, - userAgentValue - ); - } else if ( - this.authenticationFlowType === 'USER_SRP_AUTH' || - this.authenticationFlowType === 'CUSTOM_AUTH' - ) { - return this.authenticateUserDefaultAuth( - authDetails, - callback, - userAgentValue - ); - } - return callback.onFailure( - new Error('Authentication flow type is invalid.') - ); - } - - /** - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - * It calls the AuthenticationHelper for SRP related - * stuff - * @param {AuthenticationDetails} authDetails Contains the authentication data - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {newPasswordRequired} callback.newPasswordRequired new - * password and any required attributes are required to continue - * @param {mfaRequired} callback.mfaRequired MFA code - * required to continue. - * @param {customChallenge} callback.customChallenge Custom challenge - * response required to continue. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - authenticateUserDefaultAuth(authDetails, callback, userAgentValue) { - const authenticationHelper = new AuthenticationHelper( - this.pool.getUserPoolName() - ); - const dateHelper = new DateHelper(); - - let serverBValue; - let salt; - const authParameters = {}; - - if (this.deviceKey != null) { - authParameters.DEVICE_KEY = this.deviceKey; - } - - authParameters.USERNAME = this.username; - authenticationHelper.getLargeAValue((errOnAValue, aValue) => { - // getLargeAValue callback start - if (errOnAValue) { - callback.onFailure(errOnAValue); - } - - authParameters.SRP_A = aValue.toString(16); - - if (this.authenticationFlowType === 'CUSTOM_AUTH') { - authParameters.CHALLENGE_NAME = 'SRP_A'; - } - - const clientMetaData = - Object.keys(authDetails.getValidationData()).length !== 0 - ? authDetails.getValidationData() - : authDetails.getClientMetadata(); - - const jsonReq = { - AuthFlow: this.authenticationFlowType, - ClientId: this.pool.getClientId(), - AuthParameters: authParameters, - ClientMetadata: clientMetaData, - }; - if (this.getUserContextData(this.username)) { - jsonReq.UserContextData = this.getUserContextData(this.username); - } - - this.client.request( - 'InitiateAuth', - jsonReq, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - - const challengeParameters = data.ChallengeParameters; - - this.username = challengeParameters.USER_ID_FOR_SRP; - this.userDataKey = `${this.keyPrefix}.${this.username}.userData`; - serverBValue = new BigInteger(challengeParameters.SRP_B, 16); - salt = new BigInteger(challengeParameters.SALT, 16); - this.getCachedDeviceKeyAndPassword(); - - authenticationHelper.getPasswordAuthenticationKey( - this.username, - authDetails.getPassword(), - serverBValue, - salt, - (errOnHkdf, hkdf) => { - // getPasswordAuthenticationKey callback start - if (errOnHkdf) { - callback.onFailure(errOnHkdf); - } - - const dateNow = dateHelper.getNowString(); - - const concatBuffer = Buffer.concat([ - Buffer.from(this.pool.getUserPoolName(), 'utf8'), - Buffer.from(this.username, 'utf8'), - Buffer.from(challengeParameters.SECRET_BLOCK, 'base64'), - Buffer.from(dateNow, 'utf8'), - ]); - - const awsCryptoHash = new Sha256(hkdf); - awsCryptoHash.update(concatBuffer); - - const resultFromAWSCrypto = awsCryptoHash.digestSync(); - const signatureString = - Buffer.from(resultFromAWSCrypto).toString('base64'); - - const challengeResponses = {}; - - challengeResponses.USERNAME = this.username; - challengeResponses.PASSWORD_CLAIM_SECRET_BLOCK = - challengeParameters.SECRET_BLOCK; - challengeResponses.TIMESTAMP = dateNow; - challengeResponses.PASSWORD_CLAIM_SIGNATURE = signatureString; - - if (this.deviceKey != null) { - challengeResponses.DEVICE_KEY = this.deviceKey; - } - - const respondToAuthChallenge = (challenge, challengeCallback) => - this.client.request( - 'RespondToAuthChallenge', - challenge, - (errChallenge, dataChallenge) => { - if ( - errChallenge && - errChallenge.code === 'ResourceNotFoundException' && - errChallenge.message.toLowerCase().indexOf('device') !== - -1 - ) { - challengeResponses.DEVICE_KEY = null; - this.deviceKey = null; - this.randomPassword = null; - this.deviceGroupKey = null; - this.clearCachedDeviceKeyAndPassword(); - return respondToAuthChallenge( - challenge, - challengeCallback - ); - } - return challengeCallback(errChallenge, dataChallenge); - }, - userAgentValue - ); - - const jsonReqResp = { - ChallengeName: 'PASSWORD_VERIFIER', - ClientId: this.pool.getClientId(), - ChallengeResponses: challengeResponses, - Session: data.Session, - ClientMetadata: clientMetaData, - }; - if (this.getUserContextData()) { - jsonReqResp.UserContextData = this.getUserContextData(); - } - respondToAuthChallenge( - jsonReqResp, - (errAuthenticate, dataAuthenticate) => { - if (errAuthenticate) { - return callback.onFailure(errAuthenticate); - } - - return this.authenticateUserInternal( - dataAuthenticate, - authenticationHelper, - callback, - userAgentValue - ); - } - ); - return undefined; - // getPasswordAuthenticationKey callback end - } - ); - return undefined; - }, - userAgentValue - ); - // getLargeAValue callback end - }); - } - - /** - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - * @param {AuthenticationDetails} authDetails Contains the authentication data. - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {mfaRequired} callback.mfaRequired MFA code - * required to continue. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - authenticateUserPlainUsernamePassword(authDetails, callback, userAgentValue) { - const authParameters = {}; - authParameters.USERNAME = this.username; - authParameters.PASSWORD = authDetails.getPassword(); - if (!authParameters.PASSWORD) { - callback.onFailure(new Error('PASSWORD parameter is required')); - return; - } - const authenticationHelper = new AuthenticationHelper( - this.pool.getUserPoolName() - ); - this.getCachedDeviceKeyAndPassword(); - if (this.deviceKey != null) { - authParameters.DEVICE_KEY = this.deviceKey; - } - - const clientMetaData = - Object.keys(authDetails.getValidationData()).length !== 0 - ? authDetails.getValidationData() - : authDetails.getClientMetadata(); - - const jsonReq = { - AuthFlow: 'USER_PASSWORD_AUTH', - ClientId: this.pool.getClientId(), - AuthParameters: authParameters, - ClientMetadata: clientMetaData, - }; - if (this.getUserContextData(this.username)) { - jsonReq.UserContextData = this.getUserContextData(this.username); - } - // USER_PASSWORD_AUTH happens in a single round-trip: client sends userName and password, - // Cognito UserPools verifies password and returns tokens. - this.client.request( - 'InitiateAuth', - jsonReq, - (err, authResult) => { - if (err) { - return callback.onFailure(err); - } - return this.authenticateUserInternal( - authResult, - authenticationHelper, - callback, - userAgentValue - ); - }, - userAgentValue - ); - } - - /** - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - * @param {object} dataAuthenticate authentication data - * @param {object} authenticationHelper helper created - * @param {callback} callback passed on from caller - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - authenticateUserInternal( - dataAuthenticate, - authenticationHelper, - callback, - userAgentValue - ) { - const challengeName = dataAuthenticate.ChallengeName; - const challengeParameters = dataAuthenticate.ChallengeParameters; - - if (challengeName === 'SMS_MFA') { - this.Session = dataAuthenticate.Session; - return callback.mfaRequired(challengeName, challengeParameters); - } - - if (challengeName === 'SELECT_MFA_TYPE') { - this.Session = dataAuthenticate.Session; - return callback.selectMFAType(challengeName, challengeParameters); - } - - if (challengeName === 'MFA_SETUP') { - this.Session = dataAuthenticate.Session; - return callback.mfaSetup(challengeName, challengeParameters); - } - - if (challengeName === 'SOFTWARE_TOKEN_MFA') { - this.Session = dataAuthenticate.Session; - return callback.totpRequired(challengeName, challengeParameters); - } - - if (challengeName === 'CUSTOM_CHALLENGE') { - this.Session = dataAuthenticate.Session; - return callback.customChallenge(challengeParameters); - } - - if (challengeName === 'NEW_PASSWORD_REQUIRED') { - this.Session = dataAuthenticate.Session; - - let userAttributes = null; - let rawRequiredAttributes = null; - const requiredAttributes = []; - const userAttributesPrefix = - authenticationHelper.getNewPasswordRequiredChallengeUserAttributePrefix(); - - if (challengeParameters) { - userAttributes = JSON.parse( - dataAuthenticate.ChallengeParameters.userAttributes - ); - rawRequiredAttributes = JSON.parse( - dataAuthenticate.ChallengeParameters.requiredAttributes - ); - } - - if (rawRequiredAttributes) { - for (let i = 0; i < rawRequiredAttributes.length; i++) { - requiredAttributes[i] = rawRequiredAttributes[i].substr( - userAttributesPrefix.length - ); - } - } - return callback.newPasswordRequired(userAttributes, requiredAttributes); - } - - if (challengeName === 'DEVICE_SRP_AUTH') { - this.Session = dataAuthenticate.Session; - this.getDeviceResponse(callback, undefined, userAgentValue); - return undefined; - } - - this.signInUserSession = this.getCognitoUserSession( - dataAuthenticate.AuthenticationResult - ); - this.challengeName = challengeName; - this.cacheTokens(); - - const newDeviceMetadata = - dataAuthenticate.AuthenticationResult.NewDeviceMetadata; - if (newDeviceMetadata == null) { - return callback.onSuccess(this.signInUserSession); - } - - authenticationHelper.generateHashDevice( - dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey, - dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey, - errGenHash => { - if (errGenHash) { - return callback.onFailure(errGenHash); - } - - const deviceSecretVerifierConfig = { - Salt: Buffer.from( - authenticationHelper.getSaltDevices(), - 'hex' - ).toString('base64'), - PasswordVerifier: Buffer.from( - authenticationHelper.getVerifierDevices(), - 'hex' - ).toString('base64'), - }; - - this.verifierDevices = deviceSecretVerifierConfig.PasswordVerifier; - this.deviceGroupKey = newDeviceMetadata.DeviceGroupKey; - this.randomPassword = authenticationHelper.getRandomPassword(); - - this.client.request( - 'ConfirmDevice', - { - DeviceKey: newDeviceMetadata.DeviceKey, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - DeviceSecretVerifierConfig: deviceSecretVerifierConfig, - DeviceName: userAgent, - }, - (errConfirm, dataConfirm) => { - if (errConfirm) { - return callback.onFailure(errConfirm); - } - - this.deviceKey = - dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey; - this.cacheDeviceKeyAndPassword(); - if (dataConfirm.UserConfirmationNecessary === true) { - return callback.onSuccess( - this.signInUserSession, - dataConfirm.UserConfirmationNecessary - ); - } - return callback.onSuccess(this.signInUserSession); - }, - userAgentValue - ); - return undefined; - } - ); - return undefined; - } - - /** - * This method is user to complete the NEW_PASSWORD_REQUIRED challenge. - * Pass the new password with any new user attributes to be updated. - * User attribute keys must be of format userAttributes.. - * @param {string} newPassword new password for this user - * @param {object} requiredAttributeData map with values for all required attributes - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {mfaRequired} callback.mfaRequired MFA code required to continue. - * @param {customChallenge} callback.customChallenge Custom challenge - * response required to continue. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - completeNewPasswordChallenge( - newPassword, - requiredAttributeData, - callback, - clientMetadata, - userAgentValue - ) { - if (!newPassword) { - return callback.onFailure(new Error('New password is required.')); - } - const authenticationHelper = new AuthenticationHelper( - this.pool.getUserPoolName() - ); - const userAttributesPrefix = - authenticationHelper.getNewPasswordRequiredChallengeUserAttributePrefix(); - - const finalUserAttributes = {}; - if (requiredAttributeData) { - Object.keys(requiredAttributeData).forEach(key => { - finalUserAttributes[userAttributesPrefix + key] = - requiredAttributeData[key]; - }); - } - - finalUserAttributes.NEW_PASSWORD = newPassword; - finalUserAttributes.USERNAME = this.username; - const jsonReq = { - ChallengeName: 'NEW_PASSWORD_REQUIRED', - ClientId: this.pool.getClientId(), - ChallengeResponses: finalUserAttributes, - Session: this.Session, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - - this.client.request( - 'RespondToAuthChallenge', - jsonReq, - (errAuthenticate, dataAuthenticate) => { - if (errAuthenticate) { - return callback.onFailure(errAuthenticate); - } - return this.authenticateUserInternal( - dataAuthenticate, - authenticationHelper, - callback, - userAgentValue - ); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to get a session using device authentication. It is called at the end of user - * authentication - * - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - * @private - */ - getDeviceResponse(callback, clientMetadata, userAgentValue) { - const authenticationHelper = new AuthenticationHelper(this.deviceGroupKey); - const dateHelper = new DateHelper(); - - const authParameters = {}; - - authParameters.USERNAME = this.username; - authParameters.DEVICE_KEY = this.deviceKey; - authenticationHelper.getLargeAValue((errAValue, aValue) => { - // getLargeAValue callback start - if (errAValue) { - callback.onFailure(errAValue); - } - - authParameters.SRP_A = aValue.toString(16); - - const jsonReq = { - ChallengeName: 'DEVICE_SRP_AUTH', - ClientId: this.pool.getClientId(), - ChallengeResponses: authParameters, - ClientMetadata: clientMetadata, - Session: this.Session, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'RespondToAuthChallenge', - jsonReq, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - - const challengeParameters = data.ChallengeParameters; - - const serverBValue = new BigInteger(challengeParameters.SRP_B, 16); - const salt = new BigInteger(challengeParameters.SALT, 16); - - authenticationHelper.getPasswordAuthenticationKey( - this.deviceKey, - this.randomPassword, - serverBValue, - salt, - (errHkdf, hkdf) => { - // getPasswordAuthenticationKey callback start - if (errHkdf) { - return callback.onFailure(errHkdf); - } - - const dateNow = dateHelper.getNowString(); - - const concatBuffer = Buffer.concat([ - Buffer.from(this.deviceGroupKey, 'utf8'), - Buffer.from(this.deviceKey, 'utf8'), - Buffer.from(challengeParameters.SECRET_BLOCK, 'base64'), - Buffer.from(dateNow, 'utf8'), - ]); - - const awsCryptoHash = new Sha256(hkdf); - awsCryptoHash.update(concatBuffer); - - const resultFromAWSCrypto = awsCryptoHash.digestSync(); - const signatureString = - Buffer.from(resultFromAWSCrypto).toString('base64'); - - const challengeResponses = {}; - - challengeResponses.USERNAME = this.username; - challengeResponses.PASSWORD_CLAIM_SECRET_BLOCK = - challengeParameters.SECRET_BLOCK; - challengeResponses.TIMESTAMP = dateNow; - challengeResponses.PASSWORD_CLAIM_SIGNATURE = signatureString; - challengeResponses.DEVICE_KEY = this.deviceKey; - - const jsonReqResp = { - ChallengeName: 'DEVICE_PASSWORD_VERIFIER', - ClientId: this.pool.getClientId(), - ChallengeResponses: challengeResponses, - Session: data.Session, - }; - if (this.getUserContextData()) { - jsonReqResp.UserContextData = this.getUserContextData(); - } - - this.client.request( - 'RespondToAuthChallenge', - jsonReqResp, - (errAuthenticate, dataAuthenticate) => { - if (errAuthenticate) { - return callback.onFailure(errAuthenticate); - } - - this.signInUserSession = this.getCognitoUserSession( - dataAuthenticate.AuthenticationResult - ); - this.cacheTokens(); - - return callback.onSuccess(this.signInUserSession); - }, - userAgentValue - ); - return undefined; - // getPasswordAuthenticationKey callback end - } - ); - return undefined; - }, - userAgentValue - ); - // getLargeAValue callback end - }); - } - - /** - * This is used for a certain user to confirm the registration by using a confirmation code - * @param {string} confirmationCode Code entered by user. - * @param {bool} forceAliasCreation Allow migrating from an existing email / phone number. - * @param {nodeCallback} callback Called on success or error. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - confirmRegistration( - confirmationCode, - forceAliasCreation, - callback, - clientMetadata, - userAgentValue - ) { - const jsonReq = { - ClientId: this.pool.getClientId(), - ConfirmationCode: confirmationCode, - Username: this.username, - ForceAliasCreation: forceAliasCreation, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'ConfirmSignUp', - jsonReq, - err => { - if (err) { - return callback(err, null); - } - return callback(null, 'SUCCESS'); - }, - userAgentValue - ); - } - - /** - * This is used by the user once he has the responses to a custom challenge - * @param {string} answerChallenge The custom challenge answer. - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {customChallenge} callback.customChallenge - * Custom challenge response required to continue. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - sendCustomChallengeAnswer( - answerChallenge, - callback, - clientMetadata, - userAgentValue - ) { - const challengeResponses = {}; - challengeResponses.USERNAME = this.username; - challengeResponses.ANSWER = answerChallenge; - - const authenticationHelper = new AuthenticationHelper( - this.pool.getUserPoolName() - ); - this.getCachedDeviceKeyAndPassword(); - if (this.deviceKey != null) { - challengeResponses.DEVICE_KEY = this.deviceKey; - } - - const jsonReq = { - ChallengeName: 'CUSTOM_CHALLENGE', - ChallengeResponses: challengeResponses, - ClientId: this.pool.getClientId(), - Session: this.Session, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => { - if (err) { - return callback.onFailure(err); - } - - return this.authenticateUserInternal( - data, - authenticationHelper, - callback, - userAgentValue - ); - }); - } - - /** - * This is used by the user once he has an MFA code - * @param {string} confirmationCode The MFA code entered by the user. - * @param {object} callback Result callback map. - * @param {string} mfaType The mfa we are replying to. - * @param {onFailure} callback.onFailure Called on any error. - * @param {authSuccess} callback.onSuccess Called on success with the new session. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - sendMFACode( - confirmationCode, - callback, - mfaType, - clientMetadata, - userAgentValue - ) { - const challengeResponses = {}; - challengeResponses.USERNAME = this.username; - challengeResponses.SMS_MFA_CODE = confirmationCode; - const mfaTypeSelection = mfaType || 'SMS_MFA'; - if (mfaTypeSelection === 'SOFTWARE_TOKEN_MFA') { - challengeResponses.SOFTWARE_TOKEN_MFA_CODE = confirmationCode; - } - - if (this.deviceKey != null) { - challengeResponses.DEVICE_KEY = this.deviceKey; - } - - const jsonReq = { - ChallengeName: mfaTypeSelection, - ChallengeResponses: challengeResponses, - ClientId: this.pool.getClientId(), - Session: this.Session, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - - this.client.request( - 'RespondToAuthChallenge', - jsonReq, - (err, dataAuthenticate) => { - if (err) { - return callback.onFailure(err); - } - - const challengeName = dataAuthenticate.ChallengeName; - - if (challengeName === 'DEVICE_SRP_AUTH') { - this.getDeviceResponse(callback, undefined, userAgentValue); - return undefined; - } - - this.signInUserSession = this.getCognitoUserSession( - dataAuthenticate.AuthenticationResult - ); - this.cacheTokens(); - - if (dataAuthenticate.AuthenticationResult.NewDeviceMetadata == null) { - return callback.onSuccess(this.signInUserSession); - } - - const authenticationHelper = new AuthenticationHelper( - this.pool.getUserPoolName() - ); - authenticationHelper.generateHashDevice( - dataAuthenticate.AuthenticationResult.NewDeviceMetadata - .DeviceGroupKey, - dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey, - errGenHash => { - if (errGenHash) { - return callback.onFailure(errGenHash); - } - - const deviceSecretVerifierConfig = { - Salt: Buffer.from( - authenticationHelper.getSaltDevices(), - 'hex' - ).toString('base64'), - PasswordVerifier: Buffer.from( - authenticationHelper.getVerifierDevices(), - 'hex' - ).toString('base64'), - }; - - this.verifierDevices = deviceSecretVerifierConfig.PasswordVerifier; - this.deviceGroupKey = - dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey; - this.randomPassword = authenticationHelper.getRandomPassword(); - - this.client.request( - 'ConfirmDevice', - { - DeviceKey: - dataAuthenticate.AuthenticationResult.NewDeviceMetadata - .DeviceKey, - AccessToken: this.signInUserSession - .getAccessToken() - .getJwtToken(), - DeviceSecretVerifierConfig: deviceSecretVerifierConfig, - DeviceName: userAgent, - }, - (errConfirm, dataConfirm) => { - if (errConfirm) { - return callback.onFailure(errConfirm); - } - - this.deviceKey = - dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey; - this.cacheDeviceKeyAndPassword(); - if (dataConfirm.UserConfirmationNecessary === true) { - return callback.onSuccess( - this.signInUserSession, - dataConfirm.UserConfirmationNecessary - ); - } - return callback.onSuccess(this.signInUserSession); - }, - userAgentValue - ); - return undefined; - } - ); - return undefined; - }, - userAgentValue - ); - } - - /** - * This is used by an authenticated user to change the current password - * @param {string} oldUserPassword The current password. - * @param {string} newUserPassword The requested new password. - * @param {nodeCallback} callback Called on success or error. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - changePassword( - oldUserPassword, - newUserPassword, - callback, - clientMetadata, - userAgentValue - ) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'ChangePassword', - { - PreviousPassword: oldUserPassword, - ProposedPassword: newUserPassword, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - ClientMetadata: clientMetadata, - }, - err => { - if (err) { - return callback(err, null); - } - return callback(null, 'SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used by an authenticated user to enable MFA for itself - * @deprecated - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - enableMFA(callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback(new Error('User is not authenticated'), null); - } - - const mfaOptions = []; - const mfaEnabled = { - DeliveryMedium: 'SMS', - AttributeName: 'phone_number', - }; - mfaOptions.push(mfaEnabled); - - this.client.request( - 'SetUserSettings', - { - MFAOptions: mfaOptions, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - err => { - if (err) { - return callback(err, null); - } - return callback(null, 'SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used by an authenticated user to enable MFA for itself - * @param {IMfaSettings} smsMfaSettings the sms mfa settings - * @param {IMFASettings} softwareTokenMfaSettings the software token mfa settings - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - setUserMfaPreference( - smsMfaSettings, - softwareTokenMfaSettings, - callback, - userAgentValue - ) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'SetUserMFAPreference', - { - SMSMfaSettings: smsMfaSettings, - SoftwareTokenMfaSettings: softwareTokenMfaSettings, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - err => { - if (err) { - return callback(err, null); - } - return callback(null, 'SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used by an authenticated user to disable MFA for itself - * @deprecated - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - disableMFA(callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback(new Error('User is not authenticated'), null); - } - - const mfaOptions = []; - - this.client.request( - 'SetUserSettings', - { - MFAOptions: mfaOptions, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - err => { - if (err) { - return callback(err, null); - } - return callback(null, 'SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used by an authenticated user to delete itself - * @param {nodeCallback} callback Called on success or error. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - deleteUser(callback, clientMetadata, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'DeleteUser', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - ClientMetadata: clientMetadata, - }, - err => { - if (err) { - return callback(err, null); - } - this.clearCachedUser(); - return callback(null, 'SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * @typedef {CognitoUserAttribute | { Name:string, Value:string }} AttributeArg - */ - /** - * This is used by an authenticated user to change a list of attributes - * @param {AttributeArg[]} attributes A list of the new user attributes. - * @param {nodeCallback} callback Called on success or error. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - updateAttributes(attributes, callback, clientMetadata, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'UpdateUserAttributes', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - UserAttributes: attributes, - ClientMetadata: clientMetadata, - }, - (err, result) => { - if (err) { - return callback(err, null); - } - - // update cached user - return this.getUserData( - () => callback(null, 'SUCCESS', result), - { - bypassCache: true, - }, - userAgentValue - ); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used by an authenticated user to get a list of attributes - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - getUserAttributes(callback, userAgentValue) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'GetUser', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - (err, userData) => { - if (err) { - return callback(err, null); - } - - const attributeList = []; - - for (let i = 0; i < userData.UserAttributes.length; i++) { - const attribute = { - Name: userData.UserAttributes[i].Name, - Value: userData.UserAttributes[i].Value, - }; - const userAttribute = new CognitoUserAttribute(attribute); - attributeList.push(userAttribute); - } - - return callback(null, attributeList); - }, - userAgentValue - ); - return undefined; - } - - /** - * This was previously used by an authenticated user to get MFAOptions, - * but no longer returns a meaningful response. Refer to the documentation for - * how to setup and use MFA: https://docs.amplify.aws/lib/auth/mfa/q/platform/js - * @deprecated - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - getMFAOptions(callback, userAgentValue) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'GetUser', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - (err, userData) => { - if (err) { - return callback(err, null); - } - - return callback(null, userData.MFAOptions); - }, - userAgentValue - ); - return undefined; - } - - /** - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {Promise} - */ - createGetUserRequest(userAgentValue) { - return this.client.promisifyRequest( - 'GetUser', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - userAgentValue - ); - } - - /** - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - * @param {object} options - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {Promise} - */ - refreshSessionIfPossible(options = {}, userAgentValue) { - // best effort, if not possible - return new Promise(resolve => { - const refresh = this.signInUserSession.getRefreshToken(); - if (refresh && refresh.getToken()) { - this.refreshSession( - refresh, - resolve, - options.clientMetadata, - userAgentValue - ); - } else { - resolve(); - } - }); - } - - /** - * @typedef {Object} GetUserDataOptions - * @property {boolean} bypassCache - force getting data from Cognito service - * @property {Record} clientMetadata - clientMetadata for getSession - */ - - /** - * This is used by an authenticated users to get the userData - * @param {nodeCallback} callback Called on success or error. - * @param {GetUserDataOptions} params - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - getUserData(callback, params, userAgentValue) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - this.clearCachedUserData(); - return callback(new Error('User is not authenticated'), null); - } - - const userData = this.getUserDataFromCache(); - - if (!userData) { - this.fetchUserData() - .then(data => { - callback(null, data); - }) - .catch(callback); - return; - } - - if (this.isFetchUserDataAndTokenRequired(params)) { - this.fetchUserData() - .then(data => { - return this.refreshSessionIfPossible(params, userAgentValue).then( - () => data - ); - }) - .then(data => callback(null, data)) - .catch(callback); - return; - } - - try { - callback(null, JSON.parse(userData)); - return; - } catch (err) { - this.clearCachedUserData(); - callback(err, null); - return; - } - } - - /** - * - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - */ - getUserDataFromCache() { - const userData = this.storage.getItem(this.userDataKey); - - return userData; - } - - /** - * - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - */ - isFetchUserDataAndTokenRequired(params) { - const { bypassCache = false } = params || {}; - - return bypassCache; - } - /** - * - * PRIVATE ONLY: This is an internal only method and should not - * be directly called by the consumers. - * @param {string} userAgentValue Optional string containing custom user agent value - */ - fetchUserData(userAgentValue) { - return this.createGetUserRequest().then(data => { - this.cacheUserData(data); - return data; - }, userAgentValue); - } - - /** - * This is used by an authenticated user to delete a list of attributes - * @param {string[]} attributeList Names of the attributes to delete. - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - deleteAttributes(attributeList, callback, userAgentValue) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - return callback(new Error('User is not authenticated'), null); - } - - this.client.request( - 'DeleteUserAttributes', - { - UserAttributeNames: attributeList, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - err => { - if (err) { - return callback(err, null); - } - - // update cached user - return this.getUserData( - () => callback(null, 'SUCCESS'), - { - bypassCache: true, - }, - userAgentValue - ); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used by a user to resend a confirmation code - * @param {nodeCallback} callback Called on success or error. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - resendConfirmationCode(callback, clientMetadata, userAgentValue) { - const jsonReq = { - ClientId: this.pool.getClientId(), - Username: this.username, - ClientMetadata: clientMetadata, - }; - - this.client.request( - 'ResendConfirmationCode', - jsonReq, - (err, result) => { - if (err) { - return callback(err, null); - } - return callback(null, result); - }, - userAgentValue - ); - } - - /** - * @typedef {Object} GetSessionOptions - * @property {Record} clientMetadata - clientMetadata for getSession - */ - - /** - * This is used to get a session, either from the session object - * or from the local storage, or by using a refresh token - * - * @param {nodeCallback} callback Called on success or error. - * @param {GetSessionOptions} options - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - getSession(callback, options = {}, userAgentValue) { - if (this.username == null) { - return callback( - new Error('Username is null. Cannot retrieve a new session'), - null - ); - } - - if (this.signInUserSession != null && this.signInUserSession.isValid()) { - return callback(null, this.signInUserSession); - } - - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ - this.username - }`; - const idTokenKey = `${keyPrefix}.idToken`; - const accessTokenKey = `${keyPrefix}.accessToken`; - const refreshTokenKey = `${keyPrefix}.refreshToken`; - const clockDriftKey = `${keyPrefix}.clockDrift`; - - if (this.storage.getItem(idTokenKey)) { - const idToken = new CognitoIdToken({ - IdToken: this.storage.getItem(idTokenKey), - }); - const accessToken = new CognitoAccessToken({ - AccessToken: this.storage.getItem(accessTokenKey), - }); - const refreshToken = new CognitoRefreshToken({ - RefreshToken: this.storage.getItem(refreshTokenKey), - }); - const clockDrift = parseInt(this.storage.getItem(clockDriftKey), 0) || 0; - - const sessionData = { - IdToken: idToken, - AccessToken: accessToken, - RefreshToken: refreshToken, - ClockDrift: clockDrift, - }; - const cachedSession = new CognitoUserSession(sessionData); - - if (cachedSession.isValid()) { - this.signInUserSession = cachedSession; - return callback(null, this.signInUserSession); - } - - if (!refreshToken.getToken()) { - return callback( - new Error('Cannot retrieve a new session. Please authenticate.'), - null - ); - } - - this.refreshSession( - refreshToken, - callback, - options.clientMetadata, - userAgentValue - ); - } else { - callback( - new Error('Local storage is missing an ID Token, Please authenticate'), - null - ); - } - - return undefined; - } - - /** - * This uses the refreshToken to retrieve a new session - * @param {CognitoRefreshToken} refreshToken A previous session's refresh token. - * @param {nodeCallback} callback Called on success or error. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - refreshSession(refreshToken, callback, clientMetadata, userAgentValue) { - const wrappedCallback = this.pool.wrapRefreshSessionCallback - ? this.pool.wrapRefreshSessionCallback(callback) - : callback; - const authParameters = {}; - authParameters.REFRESH_TOKEN = refreshToken.getToken(); - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; - const lastUserKey = `${keyPrefix}.LastAuthUser`; - - if (this.storage.getItem(lastUserKey)) { - this.username = this.storage.getItem(lastUserKey); - const deviceKeyKey = `${keyPrefix}.${this.username}.deviceKey`; - this.deviceKey = this.storage.getItem(deviceKeyKey); - authParameters.DEVICE_KEY = this.deviceKey; - } - - const jsonReq = { - ClientId: this.pool.getClientId(), - AuthFlow: 'REFRESH_TOKEN_AUTH', - AuthParameters: authParameters, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'InitiateAuth', - jsonReq, - (err, authResult) => { - if (err) { - if (err.code === 'NotAuthorizedException') { - this.clearCachedUser(); - } - return wrappedCallback(err, null); - } - if (authResult) { - const authenticationResult = authResult.AuthenticationResult; - if ( - !Object.prototype.hasOwnProperty.call( - authenticationResult, - 'RefreshToken' - ) - ) { - authenticationResult.RefreshToken = refreshToken.getToken(); - } - this.signInUserSession = - this.getCognitoUserSession(authenticationResult); - this.cacheTokens(); - return wrappedCallback(null, this.signInUserSession); - } - return undefined; - }, - userAgentValue - ); - } - - /** - * This is used to save the session tokens to local storage - * @returns {void} - */ - cacheTokens() { - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; - const idTokenKey = `${keyPrefix}.${this.username}.idToken`; - const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`; - const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`; - const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`; - const lastUserKey = `${keyPrefix}.LastAuthUser`; - - this.storage.setItem( - idTokenKey, - this.signInUserSession.getIdToken().getJwtToken() - ); - this.storage.setItem( - accessTokenKey, - this.signInUserSession.getAccessToken().getJwtToken() - ); - this.storage.setItem( - refreshTokenKey, - this.signInUserSession.getRefreshToken().getToken() - ); - this.storage.setItem( - clockDriftKey, - `${this.signInUserSession.getClockDrift()}` - ); - this.storage.setItem(lastUserKey, this.username); - } - - /** - * This is to cache user data - */ - cacheUserData(userData) { - this.storage.setItem(this.userDataKey, JSON.stringify(userData)); - } - - /** - * This is to remove cached user data - */ - clearCachedUserData() { - this.storage.removeItem(this.userDataKey); - } - - clearCachedUser() { - this.clearCachedTokens(); - this.clearCachedUserData(); - } - - /** - * This is used to cache the device key and device group and device password - * @returns {void} - */ - cacheDeviceKeyAndPassword() { - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ - this.username - }`; - const deviceKeyKey = `${keyPrefix}.deviceKey`; - const randomPasswordKey = `${keyPrefix}.randomPasswordKey`; - const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`; - - this.storage.setItem(deviceKeyKey, this.deviceKey); - this.storage.setItem(randomPasswordKey, this.randomPassword); - this.storage.setItem(deviceGroupKeyKey, this.deviceGroupKey); - } - - /** - * This is used to get current device key and device group and device password - * @returns {void} - */ - getCachedDeviceKeyAndPassword() { - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ - this.username - }`; - const deviceKeyKey = `${keyPrefix}.deviceKey`; - const randomPasswordKey = `${keyPrefix}.randomPasswordKey`; - const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`; - - if (this.storage.getItem(deviceKeyKey)) { - this.deviceKey = this.storage.getItem(deviceKeyKey); - this.randomPassword = this.storage.getItem(randomPasswordKey); - this.deviceGroupKey = this.storage.getItem(deviceGroupKeyKey); - } - } - - /** - * This is used to clear the device key info from local storage - * @returns {void} - */ - clearCachedDeviceKeyAndPassword() { - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${ - this.username - }`; - const deviceKeyKey = `${keyPrefix}.deviceKey`; - const randomPasswordKey = `${keyPrefix}.randomPasswordKey`; - const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`; - - this.storage.removeItem(deviceKeyKey); - this.storage.removeItem(randomPasswordKey); - this.storage.removeItem(deviceGroupKeyKey); - } - - /** - * This is used to clear the session tokens from local storage - * @returns {void} - */ - clearCachedTokens() { - const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`; - const idTokenKey = `${keyPrefix}.${this.username}.idToken`; - const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`; - const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`; - const lastUserKey = `${keyPrefix}.LastAuthUser`; - const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`; - - this.storage.removeItem(idTokenKey); - this.storage.removeItem(accessTokenKey); - this.storage.removeItem(refreshTokenKey); - this.storage.removeItem(lastUserKey); - this.storage.removeItem(clockDriftKey); - } - - /** - * This is used to build a user session from tokens retrieved in the authentication result - * @param {object} authResult Successful auth response from server. - * @returns {CognitoUserSession} The new user session. - * @private - */ - getCognitoUserSession(authResult) { - const idToken = new CognitoIdToken(authResult); - const accessToken = new CognitoAccessToken(authResult); - const refreshToken = new CognitoRefreshToken(authResult); - - const sessionData = { - IdToken: idToken, - AccessToken: accessToken, - RefreshToken: refreshToken, - }; - - return new CognitoUserSession(sessionData); - } - - /** - * This is used to initiate a forgot password request - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {inputVerificationCode?} callback.inputVerificationCode - * Optional callback raised instead of onSuccess with response data. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - forgotPassword(callback, clientMetadata, userAgentValue) { - const jsonReq = { - ClientId: this.pool.getClientId(), - Username: this.username, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'ForgotPassword', - jsonReq, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - if (typeof callback.inputVerificationCode === 'function') { - return callback.inputVerificationCode(data); - } - return callback.onSuccess(data); - }, - userAgentValue - ); - } - - /** - * This is used to confirm a new password using a confirmationCode - * @param {string} confirmationCode Code entered by user. - * @param {string} newPassword Confirm new password. - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - confirmPassword( - confirmationCode, - newPassword, - callback, - clientMetadata, - userAgentValue - ) { - const jsonReq = { - ClientId: this.pool.getClientId(), - Username: this.username, - ConfirmationCode: confirmationCode, - Password: newPassword, - ClientMetadata: clientMetadata, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'ConfirmForgotPassword', - jsonReq, - err => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - } - - /** - * This is used to initiate an attribute confirmation request - * @param {string} attributeName User attribute that needs confirmation. - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {inputVerificationCode} callback.inputVerificationCode Called on success. - * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - getAttributeVerificationCode( - attributeName, - callback, - clientMetadata, - userAgentValue - ) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'GetUserAttributeVerificationCode', - { - AttributeName: attributeName, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - ClientMetadata: clientMetadata, - }, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - if (typeof callback.inputVerificationCode === 'function') { - return callback.inputVerificationCode(data); - } - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to confirm an attribute using a confirmation code - * @param {string} attributeName Attribute being confirmed. - * @param {string} confirmationCode Code entered by user. - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - verifyAttribute(attributeName, confirmationCode, callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'VerifyUserAttribute', - { - AttributeName: attributeName, - Code: confirmationCode, - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - err => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to get the device information using the current device key - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess<*>} callback.onSuccess Called on success with device data. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - getDevice(callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'GetDevice', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - DeviceKey: this.deviceKey, - }, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess(data); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to forget a specific device - * @param {string} deviceKey Device key. - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - forgetSpecificDevice(deviceKey, callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'ForgetDevice', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - DeviceKey: deviceKey, - }, - err => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to forget the current device - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - forgetDevice(callback, userAgentValue) { - this.forgetSpecificDevice( - this.deviceKey, - { - onFailure: callback.onFailure, - onSuccess: result => { - this.deviceKey = null; - this.deviceGroupKey = null; - this.randomPassword = null; - this.clearCachedDeviceKeyAndPassword(); - return callback.onSuccess(result); - }, - }, - userAgentValue - ); - } - - /** - * This is used to set the device status as remembered - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - setDeviceStatusRemembered(callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'UpdateDeviceStatus', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - DeviceKey: this.deviceKey, - DeviceRememberedStatus: 'remembered', - }, - err => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to set the device status as not remembered - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - setDeviceStatusNotRemembered(callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'UpdateDeviceStatus', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - DeviceKey: this.deviceKey, - DeviceRememberedStatus: 'not_remembered', - }, - err => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to list all devices for a user - * - * @param {int} limit the number of devices returned in a call - * @param {string | null} paginationToken the pagination token in case any was returned before - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess<*>} callback.onSuccess Called on success with device list. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - listDevices(limit, paginationToken, callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - const requestParams = { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - Limit: limit, - }; - - if (paginationToken) { - requestParams.PaginationToken = paginationToken; - } - - this.client.request( - 'ListDevices', - requestParams, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess(data); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used to globally revoke all tokens issued to a user - * @param {object} callback Result callback map. - * @param {onFailure} callback.onFailure Called on any error. - * @param {onSuccess} callback.onSuccess Called on success. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - globalSignOut(callback, userAgentValue) { - if (this.signInUserSession == null || !this.signInUserSession.isValid()) { - return callback.onFailure(new Error('User is not authenticated')); - } - - this.client.request( - 'GlobalSignOut', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - err => { - if (err) { - return callback.onFailure(err); - } - this.clearCachedUser(); - return callback.onSuccess('SUCCESS'); - }, - userAgentValue - ); - return undefined; - } - - /** - * This is used for the user to signOut of the application and clear the cached tokens. - * @param revokeTokenCallback - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - signOut(revokeTokenCallback, userAgentValue) { - // If tokens won't be revoked, we just clean the client data. - if (!revokeTokenCallback || typeof revokeTokenCallback !== 'function') { - this.cleanClientData(); - - return; - } - - this.getSession((error, _session) => { - if (error) { - return revokeTokenCallback(error); - } - - this.revokeTokens(err => { - this.cleanClientData(); - - revokeTokenCallback(err); - }, userAgentValue); - }); - } - - revokeTokens(revokeTokenCallback = () => {}, userAgentValue) { - if (typeof revokeTokenCallback !== 'function') { - throw new Error('Invalid revokeTokenCallback. It should be a function.'); - } - - const tokensToBeRevoked = []; - - if (!this.signInUserSession) { - const error = new Error('User is not authenticated'); - - return revokeTokenCallback(error); - } - - if (!this.signInUserSession.getAccessToken()) { - const error = new Error('No Access token available'); - - return revokeTokenCallback(error); - } - - const refreshToken = this.signInUserSession.getRefreshToken().getToken(); - const accessToken = this.signInUserSession.getAccessToken(); - - if (this.isSessionRevocable(accessToken)) { - if (refreshToken) { - return this.revokeToken( - { - token: refreshToken, - callback: revokeTokenCallback, - }, - userAgentValue - ); - } - } - revokeTokenCallback(); - } - - isSessionRevocable(token) { - if (token && typeof token.decodePayload === 'function') { - try { - const { origin_jti } = token.decodePayload(); - return !!origin_jti; - } catch (err) { - // Nothing to do, token doesnt have origin_jti claim - } - } - - return false; - } - - cleanClientData() { - this.signInUserSession = null; - this.clearCachedUser(); - } - - revokeToken({ token, callback }, userAgentValue) { - this.client.requestWithRetry( - 'RevokeToken', - { - Token: token, - ClientId: this.pool.getClientId(), - }, - err => { - if (err) { - return callback(err); - } - - callback(); - }, - userAgentValue - ); - } - - /** - * This is used by a user trying to select a given MFA - * @param {string} answerChallenge the mfa the user wants - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - sendMFASelectionAnswer(answerChallenge, callback, userAgentValue) { - const challengeResponses = {}; - challengeResponses.USERNAME = this.username; - challengeResponses.ANSWER = answerChallenge; - - const jsonReq = { - ChallengeName: 'SELECT_MFA_TYPE', - ChallengeResponses: challengeResponses, - ClientId: this.pool.getClientId(), - Session: this.Session, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'RespondToAuthChallenge', - jsonReq, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - this.Session = data.Session; - if (answerChallenge === 'SMS_MFA') { - return callback.mfaRequired( - data.ChallengeName, - data.ChallengeParameters - ); - } - if (answerChallenge === 'SOFTWARE_TOKEN_MFA') { - return callback.totpRequired( - data.ChallengeName, - data.ChallengeParameters - ); - } - return undefined; - }, - userAgentValue - ); - } - - /** - * This returns the user context data for advanced security feature. - * @returns {string} the user context data from CognitoUserPool - */ - getUserContextData() { - const pool = this.pool; - return pool.getUserContextData(this.username); - } - - /** - * This is used by an authenticated or a user trying to authenticate to associate a TOTP MFA - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - associateSoftwareToken(callback, userAgentValue) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - this.client.request( - 'AssociateSoftwareToken', - { - Session: this.Session, - }, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - this.Session = data.Session; - return callback.associateSecretCode(data.SecretCode); - }, - userAgentValue - ); - } else { - this.client.request( - 'AssociateSoftwareToken', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - }, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - return callback.associateSecretCode(data.SecretCode); - }, - userAgentValue - ); - } - } - - /** - * This is used by an authenticated or a user trying to authenticate to verify a TOTP MFA - * @param {string} totpCode The MFA code entered by the user. - * @param {string} friendlyDeviceName The device name we are assigning to the device. - * @param {nodeCallback} callback Called on success or error. - * @param {string} userAgentValue Optional string containing custom user agent value - * @returns {void} - */ - verifySoftwareToken(totpCode, friendlyDeviceName, callback, userAgentValue) { - if (!(this.signInUserSession != null && this.signInUserSession.isValid())) { - this.client.request( - 'VerifySoftwareToken', - { - Session: this.Session, - UserCode: totpCode, - FriendlyDeviceName: friendlyDeviceName, - }, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - this.Session = data.Session; - const challengeResponses = {}; - challengeResponses.USERNAME = this.username; - const jsonReq = { - ChallengeName: 'MFA_SETUP', - ClientId: this.pool.getClientId(), - ChallengeResponses: challengeResponses, - Session: this.Session, - }; - if (this.getUserContextData()) { - jsonReq.UserContextData = this.getUserContextData(); - } - this.client.request( - 'RespondToAuthChallenge', - jsonReq, - (errRespond, dataRespond) => { - if (errRespond) { - return callback.onFailure(errRespond); - } - this.signInUserSession = this.getCognitoUserSession( - dataRespond.AuthenticationResult - ); - this.cacheTokens(); - return callback.onSuccess(this.signInUserSession); - }, - userAgentValue - ); - return undefined; - }, - userAgentValue - ); - } else { - this.client.request( - 'VerifySoftwareToken', - { - AccessToken: this.signInUserSession.getAccessToken().getJwtToken(), - UserCode: totpCode, - FriendlyDeviceName: friendlyDeviceName, - }, - (err, data) => { - if (err) { - return callback.onFailure(err); - } - return callback.onSuccess(data); - }, - userAgentValue - ); - } - } -} diff --git a/packages/amazon-cognito-identity-js/src/internals/index.js b/packages/amazon-cognito-identity-js/src/internals/index.js index 5de97fc3cc3..42084ebe8da 100644 --- a/packages/amazon-cognito-identity-js/src/internals/index.js +++ b/packages/amazon-cognito-identity-js/src/internals/index.js @@ -2,5 +2,3 @@ export { addAuthCategoryToCognitoUserAgent, addFrameworkToCognitoUserAgent, } from '../UserAgent'; - -export { InternalCognitoUser } from './InternalCognitoUser'; diff --git a/packages/auth/__tests__/auth-unit-test.ts b/packages/auth/__tests__/auth-unit-test.ts index 3a03f0f98a3..ecafed17013 100644 --- a/packages/auth/__tests__/auth-unit-test.ts +++ b/packages/auth/__tests__/auth-unit-test.ts @@ -1407,38 +1407,38 @@ describe('auth unit test', () => { spyon.mockClear(); }); - test('currentUserPoolUser fails but hub event still dispatches', async () => { - const auth = new Auth(authOptions); - const spyon = jest - .spyOn(CognitoUser.prototype, 'sendMFACode') - .mockImplementationOnce((code, callback) => { - callback.onSuccess(session); - }); - - const spyon2 = jest - .spyOn(auth, 'currentUserPoolUser') - .mockImplementationOnce(() => { - return Promise.reject('Could not get current user.'); - }); - const hubSpy = jest.spyOn(Hub, 'dispatch'); - const user = new CognitoUser({ - Username: 'username', - Pool: userPool, - }); - const result = await auth.confirmSignIn(user, 'code', null); - expect(result).toEqual(user); - expect(hubSpy).toHaveBeenCalledWith( - 'auth', - { - data: user, - event: 'signIn', - message: 'A user username has been signed in', - }, - 'Auth', - Symbol.for('amplify_default') - ); - spyon.mockClear(); - }); + test('currentUserPoolUser fails but hub event still dispatches', async () => { + const auth = new Auth(authOptions); + const spyon = jest + .spyOn(CognitoUser.prototype, 'sendMFACode') + .mockImplementationOnce((code, callback) => { + callback.onSuccess(session); + }); + + const spyon2 = jest + .spyOn(auth, 'currentUserPoolUser') + .mockImplementationOnce(() => { + return Promise.reject('Could not get current user.'); + }); + const hubSpy = jest.spyOn(Hub, 'dispatch'); + const user = new CognitoUser({ + Username: 'username', + Pool: userPool, + }); + const result = await auth.confirmSignIn(user, 'code', null); + expect(result).toEqual(user); + expect(hubSpy).toHaveBeenCalledWith( + 'auth', + { + data: user, + event: 'signIn', + message: 'A user username has been signed in', + }, + 'Auth', + Symbol.for('amplify_default') + ); + spyon.mockClear(); + }); test('onFailure', async () => { const spyon = jest @@ -3058,13 +3058,12 @@ describe('auth unit test', () => { spyon.mockClear(); }); - test('error hub event', async done => { + test('error hub event', async (done) => { expect.assertions(3); - const spyon = jest - .spyOn(CognitoUser.prototype, 'updateAttributes') + const spyon = jest.spyOn(CognitoUser.prototype, 'updateAttributes') .mockImplementationOnce((attrs, callback: any) => { callback(new Error('Error'), null, null); - }); + }); const auth = new Auth(authOptions); @@ -3098,20 +3097,19 @@ describe('auth unit test', () => { spyon.mockClear(); }); - test('happy case code delivery details hub event', async done => { + test('happy case code delivery details hub event', async (done) => { expect.assertions(2); - + const codeDeliverDetailsResult: any = { - CodeDeliveryDetailsList: [ - { - AttributeName: 'email', - DeliveryMedium: 'EMAIL', - Destination: 'e***@e***', - }, - ], + 'CodeDeliveryDetailsList': [ + { + 'AttributeName': 'email', + 'DeliveryMedium': 'EMAIL', + 'Destination': 'e***@e***' + } + ] }; - const spyon = jest - .spyOn(CognitoUser.prototype, 'updateAttributes') + const spyon = jest.spyOn(CognitoUser.prototype, 'updateAttributes') .mockImplementationOnce((attrs, callback: any) => { callback(null, 'SUCCESS', codeDeliverDetailsResult); }); @@ -3128,20 +3126,20 @@ describe('auth unit test', () => { sub: 'sub', }; const payloadData = { - email: { + 'email': { isUpdated: false, codeDeliveryDetails: { AttributeName: 'email', DeliveryMedium: 'EMAIL', - Destination: 'e***@e***', - }, - }, - phone_number: { - isUpdated: true, + Destination: 'e***@e***' + } }, - sub: { - isUpdated: true, + 'phone_number': { + isUpdated: true }, + 'sub': { + isUpdated: true + } }; const listenToHub = Hub.listen('auth', ({ payload }) => { const { event } = payload; diff --git a/packages/auth/package.json b/packages/auth/package.json index bf5db6fb0c9..3534901efc0 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -60,7 +60,7 @@ "name": "Auth (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Auth }", - "limit": "55.73 kB" + "limit": "55.15 kB" } ], "jest": { diff --git a/packages/datastore/package.json b/packages/datastore/package.json index 20b142357d9..cfd61a67ed4 100644 --- a/packages/datastore/package.json +++ b/packages/datastore/package.json @@ -73,7 +73,7 @@ "name": "DataStore (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, DataStore }", - "limit": "137.68 kB" + "limit": "137.1 kB" } ], "jest": { From 2f0b16bd15fea1a46a723d1ebfda00d3de2b56b0 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:45:48 -0500 Subject: [PATCH 11/16] Revert "feat: custom user agent InAppMessaging changes for UI handoff (#11639)" This reverts commit 4d389da22c9f39d5a5d7cd6df9116327a9d6a04e. --- packages/core/src/Platform/types.ts | 2 - .../InAppMessaging/InAppMessaging.test.ts | 21 +- packages/notifications/internals/package.json | 8 - packages/notifications/package.json | 3 +- .../src/InAppMessaging/InAppMessaging.ts | 308 ++++++++++++++- .../Providers/AWSPinpointProvider/index.ts | 4 +- .../notifications/src/InAppMessaging/types.ts | 10 +- packages/notifications/src/Notifications.ts | 2 +- .../common/AWSPinpointProviderCommon/index.ts | 17 +- .../src/internals/InternalInAppMessaging.ts | 352 ------------------ packages/notifications/src/internals/index.ts | 4 - packages/notifications/src/internals/utils.ts | 20 - packages/notifications/src/types.ts | 6 +- packages/predictions/package.json | 2 +- 14 files changed, 316 insertions(+), 443 deletions(-) delete mode 100644 packages/notifications/internals/package.json delete mode 100644 packages/notifications/src/internals/InternalInAppMessaging.ts delete mode 100644 packages/notifications/src/internals/index.ts delete mode 100644 packages/notifications/src/internals/utils.ts diff --git a/packages/core/src/Platform/types.ts b/packages/core/src/Platform/types.ts index b2a7e98e701..1f979677816 100644 --- a/packages/core/src/Platform/types.ts +++ b/packages/core/src/Platform/types.ts @@ -105,8 +105,6 @@ export enum GeoAction { } export enum InAppMessagingAction { None = '0', - SyncMessages = '1', - IdentifyUser = '2', } export enum InteractionsAction { None = '0', diff --git a/packages/notifications/__tests__/InAppMessaging/InAppMessaging.test.ts b/packages/notifications/__tests__/InAppMessaging/InAppMessaging.test.ts index bd2f4ac35eb..8a1500de5b5 100644 --- a/packages/notifications/__tests__/InAppMessaging/InAppMessaging.test.ts +++ b/packages/notifications/__tests__/InAppMessaging/InAppMessaging.test.ts @@ -6,7 +6,6 @@ import { Hub, HubCallback, HubCapsule, - InAppMessagingAction, StorageHelper, } from '@aws-amplify/core'; @@ -14,7 +13,6 @@ import { closestExpiryMessage, customHandledMessage, inAppMessages, - notificationsConfig, simpleInAppMessages, simpleInAppMessagingEvent, subcategoryConfig, @@ -29,7 +27,6 @@ import { import InAppMessaging, { InAppMessageInteractionEvent, } from '../../src/InAppMessaging'; -import { getUserAgentValue } from '../../src/internals/utils'; jest.mock('@aws-amplify/core'); jest.mock('../../src/common/eventListeners'); @@ -117,9 +114,7 @@ describe('InAppMessaging', () => { }); test('attaches a storage helper to the config', () => { - const config = inAppMessaging.configure({ - Notifications: notificationsConfig, - }); + const config = inAppMessaging.configure(subcategoryConfig); expect(config).toStrictEqual({ ...subcategoryConfig, @@ -156,12 +151,7 @@ describe('InAppMessaging', () => { }); test('does not listen to analytics events if `listenForAnalyticsEvents` is false', () => { - const config = { - Notifications: { - InAppMessaging: { listenForAnalyticsEvents: false }, - }, - }; - inAppMessaging.configure(config); + inAppMessaging.configure({ listenForAnalyticsEvents: false }); expect(hubSpy).not.toBeCalled(); }); @@ -294,14 +284,9 @@ describe('InAppMessaging', () => { test('identifies users with pluggables', async () => { await inAppMessaging.identifyUser(userId, userInfo); - const userAgentValue = getUserAgentValue( - InAppMessagingAction.IdentifyUser - ); - expect(mockInAppMessagingProvider.identifyUser).toBeCalledWith( userId, - userInfo, - userAgentValue + userInfo ); }); diff --git a/packages/notifications/internals/package.json b/packages/notifications/internals/package.json deleted file mode 100644 index 3c96ac96f1e..00000000000 --- a/packages/notifications/internals/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@aws-amplify/notifications/internals", - "types": "../lib-esm/internals/index.d.ts", - "main": "../lib/internals/index.js", - "module": "../lib-esm/internals/index.js", - "react-native": "../lib-esm/internals/index.js", - "sideEffects": false -} diff --git a/packages/notifications/package.json b/packages/notifications/package.json index 4e52fc542ef..e63495e815c 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -46,8 +46,7 @@ "files": [ "lib", "lib-esm", - "src", - "internals" + "src" ], "dependencies": { "@aws-amplify/cache": "5.1.9", diff --git a/packages/notifications/src/InAppMessaging/InAppMessaging.ts b/packages/notifications/src/InAppMessaging/InAppMessaging.ts index a12e4e9461d..3551764e682 100644 --- a/packages/notifications/src/InAppMessaging/InAppMessaging.ts +++ b/packages/notifications/src/InAppMessaging/InAppMessaging.ts @@ -1,14 +1,78 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { + ConsoleLogger as Logger, + HubCallback, + HubCapsule, + Hub, + StorageHelper, +} from '@aws-amplify/core'; +import flatten from 'lodash/flatten'; + +import { + addEventListener, + EventListener, + notifyEventListeners, +} from '../common'; import { UserInfo } from '../types'; -import { InAppMessagingInterface, NotificationsSubCategory } from './types'; -import { InternalInAppMessagingClass } from '../internals/InternalInAppMessaging'; +import { AWSPinpointProvider } from './Providers'; +import { + InAppMessage, + InAppMessageInteractionEvent, + InAppMessagingInterface, + InAppMessagingConfig, + InAppMessageConflictHandler, + InAppMessagingEvent, + InAppMessagingProvider, + NotificationsSubCategory, + OnMessageInteractionEventHandler, +} from './types'; + +const STORAGE_KEY_SUFFIX = '_inAppMessages'; + +const logger = new Logger('Notifications.InAppMessaging'); + +export default class InAppMessaging implements InAppMessagingInterface { + private config: Record = {}; + private conflictHandler: InAppMessageConflictHandler; + private listeningForAnalyticEvents = false; + private pluggables: InAppMessagingProvider[] = []; + private storageSynced = false; + + constructor() { + this.config = { storage: new StorageHelper().getStorage() }; + this.setConflictHandler(this.defaultConflictHandler); + } + + /** + * Configure InAppMessaging + * @param {Object} config - InAppMessaging configuration object + */ + configure = ({ + listenForAnalyticsEvents = true, + ...config + }: InAppMessagingConfig = {}): InAppMessagingConfig => { + this.config = { ...this.config, ...config }; + + logger.debug('configure InAppMessaging', this.config); + + this.pluggables.forEach(pluggable => { + pluggable.configure(this.config[pluggable.getProviderName()]); + }); + + if (this.pluggables.length === 0) { + this.addPluggable(new AWSPinpointProvider()); + } + + if (listenForAnalyticsEvents && !this.listeningForAnalyticEvents) { + Hub.listen('analytics', this.analyticsListener); + this.listeningForAnalyticEvents = true; + } + + return this.config; + }; -export default class InAppMessaging - extends InternalInAppMessagingClass - implements InAppMessagingInterface -{ /** * Get the name of this module * @returns {string} name of this module @@ -17,13 +81,241 @@ export default class InAppMessaging return 'InAppMessaging'; } + /** + * Get a plugin from added plugins + * @param {string} providerName - the name of the plugin to get + */ + getPluggable = (providerName: string): InAppMessagingProvider => { + const pluggable = + this.pluggables.find( + pluggable => pluggable.getProviderName() === providerName + ) ?? null; + + if (!pluggable) { + logger.debug(`No plugin found with name ${providerName}`); + } + + return pluggable; + }; + + /** + * Add plugin into InAppMessaging + * @param {InAppMessagingProvider} pluggable - an instance of the plugin + */ + addPluggable = (pluggable: InAppMessagingProvider): void => { + if ( + pluggable && + pluggable.getCategory() === 'Notifications' && + pluggable.getSubCategory() === 'InAppMessaging' + ) { + if (this.getPluggable(pluggable.getProviderName())) { + throw new Error( + `Pluggable ${pluggable.getProviderName()} has already been added.` + ); + } + this.pluggables.push(pluggable); + pluggable.configure(this.config[pluggable.getProviderName()]); + } + }; + + /** + * Remove a plugin from added plugins + * @param {string} providerName - the name of the plugin to remove + */ + removePluggable = (providerName: string): void => { + const index = this.pluggables.findIndex( + pluggable => pluggable.getProviderName() === providerName + ); + if (index === -1) { + logger.debug(`No plugin found with name ${providerName}`); + } else { + this.pluggables.splice(index, 1); + } + }; + /** * Get the map resources that are currently available through the provider * @param {string} provider * @returns - Array of available map resources */ - syncMessages = (): Promise => super.syncMessages(); + syncMessages = (): Promise => + Promise.all( + this.pluggables.map(async pluggable => { + try { + const messages = await pluggable.getInAppMessages(); + const key = `${pluggable.getProviderName()}${STORAGE_KEY_SUFFIX}`; + await this.setMessages(key, messages); + } catch (err) { + logger.error('Failed to sync messages', err); + throw err; + } + }) + ); + + clearMessages = (): Promise => + Promise.all( + this.pluggables.map(async pluggable => { + const key = `${pluggable.getProviderName()}${STORAGE_KEY_SUFFIX}`; + await this.removeMessages(key); + }) + ); + + dispatchEvent = async (event: InAppMessagingEvent): Promise => { + const messages: InAppMessage[][] = await Promise.all( + this.pluggables.map(async pluggable => { + const key = `${pluggable.getProviderName()}${STORAGE_KEY_SUFFIX}`; + const messages = await this.getMessages(key); + return pluggable.processInAppMessages(messages, event); + }) + ); + + const flattenedMessages = flatten(messages); + + if (flattenedMessages.length) { + notifyEventListeners( + InAppMessageInteractionEvent.MESSAGE_RECEIVED, + this.conflictHandler(flattenedMessages) + ); + } + }; identifyUser = (userId: string, userInfo: UserInfo): Promise => - super.identifyUser(userId, userInfo); + Promise.all( + this.pluggables.map(async pluggable => { + try { + await pluggable.identifyUser(userId, userInfo); + } catch (err) { + logger.error('Failed to identify user', err); + throw err; + } + }) + ); + + onMessageReceived = ( + handler: OnMessageInteractionEventHandler + ): EventListener => + addEventListener(InAppMessageInteractionEvent.MESSAGE_RECEIVED, handler); + + onMessageDisplayed = ( + handler: OnMessageInteractionEventHandler + ): EventListener => + addEventListener(InAppMessageInteractionEvent.MESSAGE_DISPLAYED, handler); + + onMessageDismissed = ( + handler: OnMessageInteractionEventHandler + ): EventListener => + addEventListener(InAppMessageInteractionEvent.MESSAGE_DISMISSED, handler); + + onMessageActionTaken = ( + handler: OnMessageInteractionEventHandler + ): EventListener => + addEventListener( + InAppMessageInteractionEvent.MESSAGE_ACTION_TAKEN, + handler + ); + + notifyMessageInteraction = ( + message: InAppMessage, + type: InAppMessageInteractionEvent + ): void => { + notifyEventListeners(type, message); + }; + + setConflictHandler = (handler: InAppMessageConflictHandler): void => { + this.conflictHandler = handler; + }; + + private analyticsListener: HubCallback = ({ payload }: HubCapsule) => { + const { event, data } = payload; + switch (event) { + case 'record': { + this.dispatchEvent(data); + break; + } + default: + break; + } + }; + + private syncStorage = async (): Promise => { + const { storage } = this.config; + try { + // Only run sync() if it's available (i.e. React Native) + if (typeof storage.sync === 'function') { + await storage.sync(); + } + this.storageSynced = true; + } catch (err) { + logger.error('Failed to sync storage', err); + } + }; + + private getMessages = async (key: string): Promise => { + try { + if (!this.storageSynced) { + await this.syncStorage(); + } + const { storage } = this.config; + const storedMessages = storage.getItem(key); + return storedMessages ? JSON.parse(storedMessages) : []; + } catch (err) { + logger.error('Failed to retrieve in-app messages from storage', err); + } + }; + + private setMessages = async ( + key: string, + messages: InAppMessage[] + ): Promise => { + if (!messages) { + return; + } + + try { + if (!this.storageSynced) { + await this.syncStorage(); + } + const { storage } = this.config; + storage.setItem(key, JSON.stringify(messages)); + } catch (err) { + logger.error('Failed to store in-app messages', err); + } + }; + + private removeMessages = async (key: string): Promise => { + try { + if (!this.storageSynced) { + await this.syncStorage(); + } + const { storage } = this.config; + storage.removeItem(key); + } catch (err) { + logger.error('Failed to remove in-app messages from storage', err); + } + }; + + private defaultConflictHandler = (messages: InAppMessage[]): InAppMessage => { + // default behavior is to return the message closest to expiry + // this function assumes that messages processed by providers already filters out expired messages + const sorted = messages.sort((a, b) => { + const endDateA = a.metadata?.endDate; + const endDateB = b.metadata?.endDate; + // if both message end dates are falsy or have the same date string, treat them as equal + if (endDateA === endDateB) { + return 0; + } + // if only message A has an end date, treat it as closer to expiry + if (endDateA && !endDateB) { + return -1; + } + // if only message B has an end date, treat it as closer to expiry + if (!endDateA && endDateB) { + return 1; + } + // otherwise, compare them + return new Date(endDateA) < new Date(endDateB) ? -1 : 1; + }); + // always return the top sorted + return sorted[0]; + }; } diff --git a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts index 9e888d2c939..d6b8c3d5ea8 100644 --- a/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts +++ b/packages/notifications/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts @@ -111,7 +111,7 @@ export default class AWSPinpointProvider return this.config; }; - getInAppMessages = async (userAgentValue?: string) => { + getInAppMessages = async () => { if (!this.initialized) { await this.init(); } @@ -129,7 +129,7 @@ export default class AWSPinpointProvider }; this.logger.debug('getting in-app messages'); const response: GetInAppMessagesOutput = await getInAppMessages( - { credentials, region, userAgentValue }, + { credentials, region }, input ); const { InAppMessageCampaigns: messages } = diff --git a/packages/notifications/src/InAppMessaging/types.ts b/packages/notifications/src/InAppMessaging/types.ts index ff8fe47fd43..34fdd74d6ab 100644 --- a/packages/notifications/src/InAppMessaging/types.ts +++ b/packages/notifications/src/InAppMessaging/types.ts @@ -4,7 +4,6 @@ import { EventListener } from '../common'; import { AWSPinpointProviderConfig } from '../common/AWSPinpointProviderCommon/types'; import { - NotificationsConfig, NotificationsProvider, NotificationsSubCategory as NotificationsSubCategories, UserInfo, @@ -14,13 +13,10 @@ export type NotificationsSubCategory = Extract< NotificationsSubCategories, 'InAppMessaging' >; -export type InternalNotificationsSubCategory = 'InternalInAppMessaging'; export interface InAppMessagingInterface { - configure: (config: NotificationsConfig) => InAppMessagingConfig; - getModuleName: () => - | NotificationsSubCategory - | InternalNotificationsSubCategory; + configure: (config: InAppMessagingConfig) => InAppMessagingConfig; + getModuleName: () => NotificationsSubCategory; getPluggable: (providerName: string) => InAppMessagingProvider; addPluggable: (pluggable: InAppMessagingProvider) => void; removePluggable: (providerName: string) => void; @@ -52,7 +48,7 @@ export interface InAppMessagingProvider extends NotificationsProvider { getSubCategory(): NotificationsSubCategory; // get in-app messages from provider - getInAppMessages(userAgentValue?: string): Promise; + getInAppMessages(): Promise; // filters in-app messages based on event input and provider logic processInAppMessages( diff --git a/packages/notifications/src/Notifications.ts b/packages/notifications/src/Notifications.ts index 7858bdd1c10..8910778cfff 100644 --- a/packages/notifications/src/Notifications.ts +++ b/packages/notifications/src/Notifications.ts @@ -38,7 +38,7 @@ class NotificationsClass { logger.debug('configure Notifications', config); // Configure sub-categories - this.inAppMessaging.configure({ Notifications: this.config }); + this.inAppMessaging.configure(this.config.InAppMessaging); if (this.config.Push) { try { diff --git a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts index 37018c3229b..808df8c26b4 100644 --- a/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts +++ b/packages/notifications/src/common/AWSPinpointProviderCommon/index.ts @@ -77,16 +77,12 @@ export default abstract class AWSPinpointProviderCommon return this.config; } - identifyUser = async ( - userId: string, - userInfo: UserInfo, - userAgentValue?: string - ): Promise => { + identifyUser = async (userId: string, userInfo: UserInfo): Promise => { if (!this.initialized) { await this.init(); } try { - await this.updateEndpoint(userId, userInfo, userAgentValue); + await this.updateEndpoint(userId, userInfo); } catch (err) { this.logger.error('Error identifying user', err); throw err; @@ -165,8 +161,7 @@ export default abstract class AWSPinpointProviderCommon protected updateEndpoint = async ( userId: string = null, - userInfo: AWSPinpointUserInfo = null, - userAgentValue?: string + userInfo: AWSPinpointUserInfo = null ): Promise => { const credentials = await this.getCredentials(); // Shallow compare to determine if credentials stored here are outdated @@ -235,11 +230,7 @@ export default abstract class AWSPinpointProviderCommon }; this.logger.debug('updating endpoint'); await updateEndpoint( - { - credentials, - region, - userAgentValue: userAgentValue || this.getUserAgentValue(), - }, + { credentials, region, userAgentValue: this.getUserAgentValue() }, input ); this.endpointInitialized = true; diff --git a/packages/notifications/src/internals/InternalInAppMessaging.ts b/packages/notifications/src/internals/InternalInAppMessaging.ts deleted file mode 100644 index 1ec5d8818ed..00000000000 --- a/packages/notifications/src/internals/InternalInAppMessaging.ts +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - Amplify, - ConsoleLogger as Logger, - HubCallback, - HubCapsule, - Hub, - StorageHelper, - CustomUserAgentDetails, - InAppMessagingAction, -} from '@aws-amplify/core'; -import flatten from 'lodash/flatten'; - -import { - addEventListener, - EventListener, - notifyEventListeners, -} from '../common'; -import { NotificationsConfig, UserInfo } from '../types'; -import { AWSPinpointProvider } from '../InAppMessaging/Providers'; -import { - InAppMessage, - InAppMessageInteractionEvent, - InAppMessagingInterface, - InAppMessagingConfig, - InAppMessageConflictHandler, - InAppMessagingEvent, - InAppMessagingProvider, - OnMessageInteractionEventHandler, - InternalNotificationsSubCategory, - NotificationsSubCategory, -} from '../InAppMessaging/types'; -import { getUserAgentValue } from './utils'; - -const STORAGE_KEY_SUFFIX = '_inAppMessages'; - -const logger = new Logger('Notifications.InAppMessaging'); - -export class InternalInAppMessagingClass implements InAppMessagingInterface { - private config: Record = {}; - private conflictHandler: InAppMessageConflictHandler; - private listeningForAnalyticEvents = false; - private pluggables: InAppMessagingProvider[] = []; - private storageSynced = false; - - constructor() { - this.config = { storage: new StorageHelper().getStorage() }; - this.setConflictHandler(this.defaultConflictHandler); - } - - /** - * Configure InAppMessaging - * @param {Object} config - InAppMessaging configuration object - */ - configure({ - Notifications: notificationsConfig, - }: NotificationsConfig = {}): InAppMessagingConfig { - const { listenForAnalyticsEvents = true, ...config }: InAppMessagingConfig = - notificationsConfig?.InAppMessaging || {}; - - this.config = { ...this.config, ...config }; - - logger.debug('configure InAppMessaging', this.config); - - this.pluggables.forEach(pluggable => { - pluggable.configure(this.config[pluggable.getProviderName()]); - }); - - if (this.pluggables.length === 0) { - this.addPluggable(new AWSPinpointProvider()); - } - - if (listenForAnalyticsEvents && !this.listeningForAnalyticEvents) { - Hub.listen('analytics', this.analyticsListener); - this.listeningForAnalyticEvents = true; - } - - return this.config; - } - - /** - * Get the name of this module - * @returns {string} name of this module - */ - getModuleName(): NotificationsSubCategory | InternalNotificationsSubCategory { - return 'InternalInAppMessaging'; - } - - /** - * Get a plugin from added plugins - * @param {string} providerName - the name of the plugin to get - */ - getPluggable = (providerName: string): InAppMessagingProvider => { - const pluggable = - this.pluggables.find( - pluggable => pluggable.getProviderName() === providerName - ) ?? null; - - if (!pluggable) { - logger.debug(`No plugin found with name ${providerName}`); - } - - return pluggable; - }; - - /** - * Add plugin into InAppMessaging - * @param {InAppMessagingProvider} pluggable - an instance of the plugin - */ - addPluggable = (pluggable: InAppMessagingProvider): void => { - if ( - pluggable && - pluggable.getCategory() === 'Notifications' && - pluggable.getSubCategory() === 'InAppMessaging' - ) { - if (this.getPluggable(pluggable.getProviderName())) { - throw new Error( - `Pluggable ${pluggable.getProviderName()} has already been added.` - ); - } - this.pluggables.push(pluggable); - pluggable.configure(this.config[pluggable.getProviderName()]); - } - }; - - /** - * Remove a plugin from added plugins - * @param {string} providerName - the name of the plugin to remove - */ - removePluggable = (providerName: string): void => { - const index = this.pluggables.findIndex( - pluggable => pluggable.getProviderName() === providerName - ); - if (index === -1) { - logger.debug(`No plugin found with name ${providerName}`); - } else { - this.pluggables.splice(index, 1); - } - }; - - /** - * Get the map resources that are currently available through the provider - * @param {CustomUserAgentDetails} customUserAgentDetails optional parameter to send user agent details - * @returns - Array of available map resources - */ - public syncMessages( - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return Promise.all( - this.pluggables.map(async pluggable => { - try { - const messages = await pluggable.getInAppMessages( - getUserAgentValue( - InAppMessagingAction.SyncMessages, - customUserAgentDetails - ) - ); - const key = `${pluggable.getProviderName()}${STORAGE_KEY_SUFFIX}`; - await this.setMessages(key, messages); - } catch (err) { - logger.error('Failed to sync messages', err); - throw err; - } - }) - ); - } - - public clearMessages(): Promise { - return Promise.all( - this.pluggables.map(async pluggable => { - const key = `${pluggable.getProviderName()}${STORAGE_KEY_SUFFIX}`; - await this.removeMessages(key); - }) - ); - } - - public async dispatchEvent(event: InAppMessagingEvent): Promise { - const messages: InAppMessage[][] = await Promise.all( - this.pluggables.map(async pluggable => { - const key = `${pluggable.getProviderName()}${STORAGE_KEY_SUFFIX}`; - const messages = await this.getMessages(key); - return pluggable.processInAppMessages(messages, event); - }) - ); - - const flattenedMessages = flatten(messages); - - if (flattenedMessages.length) { - notifyEventListeners( - InAppMessageInteractionEvent.MESSAGE_RECEIVED, - this.conflictHandler(flattenedMessages) - ); - } - } - - public identifyUser( - userId: string, - userInfo: UserInfo, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - return Promise.all( - this.pluggables.map(async pluggable => { - try { - await pluggable.identifyUser( - userId, - userInfo, - getUserAgentValue( - InAppMessagingAction.IdentifyUser, - customUserAgentDetails - ) - ); - } catch (err) { - logger.error('Failed to identify user', err); - throw err; - } - }) - ); - } - - onMessageReceived = ( - handler: OnMessageInteractionEventHandler - ): EventListener => - addEventListener(InAppMessageInteractionEvent.MESSAGE_RECEIVED, handler); - - onMessageDisplayed = ( - handler: OnMessageInteractionEventHandler - ): EventListener => - addEventListener(InAppMessageInteractionEvent.MESSAGE_DISPLAYED, handler); - - onMessageDismissed = ( - handler: OnMessageInteractionEventHandler - ): EventListener => - addEventListener(InAppMessageInteractionEvent.MESSAGE_DISMISSED, handler); - - onMessageActionTaken = ( - handler: OnMessageInteractionEventHandler - ): EventListener => - addEventListener( - InAppMessageInteractionEvent.MESSAGE_ACTION_TAKEN, - handler - ); - - notifyMessageInteraction = ( - message: InAppMessage, - type: InAppMessageInteractionEvent - ): void => { - notifyEventListeners(type, message); - }; - - setConflictHandler = (handler: InAppMessageConflictHandler): void => { - this.conflictHandler = handler; - }; - - private analyticsListener: HubCallback = ({ payload }: HubCapsule) => { - const { event, data } = payload; - switch (event) { - case 'record': { - this.dispatchEvent(data); - break; - } - default: - break; - } - }; - - private syncStorage = async (): Promise => { - const { storage } = this.config; - try { - // Only run sync() if it's available (i.e. React Native) - if (typeof storage.sync === 'function') { - await storage.sync(); - } - this.storageSynced = true; - } catch (err) { - logger.error('Failed to sync storage', err); - } - }; - - private getMessages = async (key: string): Promise => { - try { - if (!this.storageSynced) { - await this.syncStorage(); - } - const { storage } = this.config; - const storedMessages = storage.getItem(key); - return storedMessages ? JSON.parse(storedMessages) : []; - } catch (err) { - logger.error('Failed to retrieve in-app messages from storage', err); - } - }; - - private setMessages = async ( - key: string, - messages: InAppMessage[] - ): Promise => { - if (!messages) { - return; - } - - try { - if (!this.storageSynced) { - await this.syncStorage(); - } - const { storage } = this.config; - storage.setItem(key, JSON.stringify(messages)); - } catch (err) { - logger.error('Failed to store in-app messages', err); - } - }; - - private removeMessages = async (key: string): Promise => { - try { - if (!this.storageSynced) { - await this.syncStorage(); - } - const { storage } = this.config; - storage.removeItem(key); - } catch (err) { - logger.error('Failed to remove in-app messages from storage', err); - } - }; - - private defaultConflictHandler = (messages: InAppMessage[]): InAppMessage => { - // default behavior is to return the message closest to expiry - // this function assumes that messages processed by providers already filters out expired messages - const sorted = messages.sort((a, b) => { - const endDateA = a.metadata?.endDate; - const endDateB = b.metadata?.endDate; - // if both message end dates are falsy or have the same date string, treat them as equal - if (endDateA === endDateB) { - return 0; - } - // if only message A has an end date, treat it as closer to expiry - if (endDateA && !endDateB) { - return -1; - } - // if only message B has an end date, treat it as closer to expiry - if (!endDateA && endDateB) { - return 1; - } - // otherwise, compare them - return new Date(endDateA) < new Date(endDateB) ? -1 : 1; - }); - // always return the top sorted - return sorted[0]; - }; -} - -export const InternalInAppMessaging = new InternalInAppMessagingClass(); -Amplify.register(InternalInAppMessaging); diff --git a/packages/notifications/src/internals/index.ts b/packages/notifications/src/internals/index.ts deleted file mode 100644 index defed39d7ce..00000000000 --- a/packages/notifications/src/internals/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -export { InternalInAppMessaging } from './InternalInAppMessaging'; diff --git a/packages/notifications/src/internals/utils.ts b/packages/notifications/src/internals/utils.ts deleted file mode 100644 index 824f05e48d1..00000000000 --- a/packages/notifications/src/internals/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - Category, - CustomUserAgentDetails, - getAmplifyUserAgent, - InAppMessagingAction, -} from '@aws-amplify/core'; - -export const getUserAgentValue = ( - action: InAppMessagingAction, - customUserAgentDetails?: CustomUserAgentDetails -) => { - return getAmplifyUserAgent({ - category: Category.InAppMessaging, - action, - ...customUserAgentDetails, - }); -}; diff --git a/packages/notifications/src/types.ts b/packages/notifications/src/types.ts index a6558957b28..c539117d54c 100644 --- a/packages/notifications/src/types.ts +++ b/packages/notifications/src/types.ts @@ -22,11 +22,7 @@ export interface NotificationsProvider { getProviderName(): string; // identify the current user with the provider - identifyUser( - userId: string, - userInfo: UserInfo, - userAgentValue?: string - ): Promise; + identifyUser(userId: string, userInfo: UserInfo): Promise; } export interface NotificationsConfig { diff --git a/packages/predictions/package.json b/packages/predictions/package.json index 6f4bf2ce234..b594bd735a8 100644 --- a/packages/predictions/package.json +++ b/packages/predictions/package.json @@ -63,7 +63,7 @@ "name": "Predictions (all providers)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIPredictionsProvider }", - "limit": "103.55 kB" + "limit": "103.5 kB" }, { "name": "Predictions (Convert provider)", From 288a3bbd5d99ce46e9f085a107183b3dffb8f76e Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:46:44 -0500 Subject: [PATCH 12/16] Revert "feat: custom user agent Storage changes for UI handoff (#11627)" This reverts commit b0231af9d7fe631ef9e0e669df7a20802e3a21b3. --- packages/core/src/Platform/types.ts | 1 - packages/predictions/package.json | 4 +- .../common/S3ClientUtils-unit-test.ts | 19 +- .../providers/CustomUserAgent.test.ts | 89 ++-- packages/storage/internals/package.json | 8 - packages/storage/package.json | 3 +- packages/storage/src/Storage.ts | 293 ++++++++++- packages/storage/src/common/S3ClientUtils.ts | 9 +- packages/storage/src/common/StorageUtils.ts | 18 +- .../storage/src/internals/InternalStorage.ts | 476 ------------------ packages/storage/src/internals/index.ts | 3 - .../storage/src/providers/AWSS3Provider.ts | 70 +-- .../providers/AWSS3ProviderManagedUpload.ts | 1 + .../storage/src/providers/AWSS3UploadTask.ts | 9 +- packages/storage/src/types/Provider.ts | 42 +- packages/storage/src/types/Storage.ts | 19 +- 16 files changed, 395 insertions(+), 669 deletions(-) delete mode 100644 packages/storage/internals/package.json delete mode 100644 packages/storage/src/internals/InternalStorage.ts delete mode 100644 packages/storage/src/internals/index.ts diff --git a/packages/core/src/Platform/types.ts b/packages/core/src/Platform/types.ts index 1f979677816..a4901e1d2e1 100644 --- a/packages/core/src/Platform/types.ts +++ b/packages/core/src/Platform/types.ts @@ -127,7 +127,6 @@ export enum StorageAction { Copy = '4', Remove = '5', GetProperties = '6', - Cancel = '7', } type ActionMap = { diff --git a/packages/predictions/package.json b/packages/predictions/package.json index b594bd735a8..95641f8d968 100644 --- a/packages/predictions/package.json +++ b/packages/predictions/package.json @@ -63,7 +63,7 @@ "name": "Predictions (all providers)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIPredictionsProvider }", - "limit": "103.5 kB" + "limit": "103.2 kB" }, { "name": "Predictions (Convert provider)", @@ -81,7 +81,7 @@ "name": "Predictions (Interpret provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIInterpretPredictionsProvider }", - "limit": "43.45 kB" + "limit": "43.4 kB" } ], "jest": { diff --git a/packages/storage/__tests__/common/S3ClientUtils-unit-test.ts b/packages/storage/__tests__/common/S3ClientUtils-unit-test.ts index e063bc764dc..3af62d562b6 100644 --- a/packages/storage/__tests__/common/S3ClientUtils-unit-test.ts +++ b/packages/storage/__tests__/common/S3ClientUtils-unit-test.ts @@ -69,26 +69,40 @@ describe('S3ClientUtils tests', () => { }); test('createS3Client test', async () => { - expect.assertions(3); + expect.assertions(4); const s3Config = loadS3Config({ region: 'us-west-2', useAccelerateEndpoint: true, + storageAction: StorageAction.Get, credentials, }); + expect(s3Config.userAgentValue).toEqual( + getAmplifyUserAgent({ + category: Category.Storage, + action: StorageAction.Get, + }) + ); expect(s3Config.region).toEqual('us-west-2'); expect(s3Config.useAccelerateEndpoint).toBe(true); expect(await s3Config.credentials()).toBe(credentials); }); test('createS3Client injects credentials provider', async () => { - expect.assertions(3); + expect.assertions(4); jest .spyOn(Credentials, 'get') .mockImplementationOnce(() => Promise.resolve(credentials)); const s3Config = loadS3Config({ region: 'us-west-2', useAccelerateEndpoint: true, + storageAction: StorageAction.Get, }); + expect(s3Config.userAgentValue).toEqual( + getAmplifyUserAgent({ + category: Category.Storage, + action: StorageAction.Get, + }) + ); expect(s3Config.region).toEqual('us-west-2'); expect(s3Config.useAccelerateEndpoint).toBe(true); expect(await s3Config.credentials()).toEqual(credentials); @@ -98,6 +112,7 @@ describe('S3ClientUtils tests', () => { const s3Config = loadS3Config({ region: 'us-west-2', dangerouslyConnectToHttpEndpointForTesting: true, + storageAction: StorageAction.Get, }); expect(s3Config).toMatchObject({ customEndpoint: 'http://localhost:20005', diff --git a/packages/storage/__tests__/providers/CustomUserAgent.test.ts b/packages/storage/__tests__/providers/CustomUserAgent.test.ts index 3c02f8097a3..ff55aa4c276 100644 --- a/packages/storage/__tests__/providers/CustomUserAgent.test.ts +++ b/packages/storage/__tests__/providers/CustomUserAgent.test.ts @@ -7,8 +7,6 @@ import { AWSS3Provider as StorageProvider } from '../../src/providers/AWSS3Provi import { StorageOptions } from '../../src'; import { headObject, getObject } from '../../src/AwsClients/S3'; import { presignUrl } from '@aws-amplify/core/internals/aws-client-utils'; -import { InternalStorageClass } from '../../src/internals/InternalStorage'; -import { getStorageUserAgentValue } from '../../src/common/StorageUtils'; jest.mock('../../src/AwsClients/S3'); jest.mock('@aws-amplify/core/internals/aws-client-utils'); @@ -32,8 +30,7 @@ const options: StorageOptions = { level: 'public', }; -let provider: StorageProvider; -let storage: InternalStorageClass; +let storage: StorageProvider; const originalLoadS3Config = utils.loadS3Config; // @ts-ignore @@ -45,15 +42,8 @@ describe('Each Storage call should create a client with the right StorageAction' jest.spyOn(Credentials, 'get').mockImplementationOnce(() => { return Promise.resolve(credentials); }); - - provider = new StorageProvider(); - provider.configure(options); - - storage = new InternalStorageClass(); - storage.configure(); - - storage.addPluggable(provider); - + storage = new StorageProvider(); + storage.configure(options); mockHeadObject.mockResolvedValue({ ContentLength: '100', ContentType: 'text/plain', @@ -65,28 +55,23 @@ describe('Each Storage call should create a client with the right StorageAction' afterEach(() => { jest.clearAllMocks(); - jest.resetAllMocks(); }); test('getUrl', async () => { - provider.get = jest.fn(provider.get); - await storage.get('test'); - expect(provider.get).toBeCalledWith( - 'test', - expect.anything(), - getStorageUserAgentValue(StorageAction.Get) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.Get, + }) ); }); test('getProperties', async () => { - provider.getProperties = jest.fn(provider.getProperties); - await storage.getProperties('test'); - expect(provider.getProperties).toBeCalledWith( - 'test', - undefined, - getStorageUserAgentValue(StorageAction.GetProperties) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.GetProperties, + }) ); }); @@ -98,59 +83,47 @@ describe('Each Storage call should create a client with the right StorageAction' }, }); - provider.get = jest.fn(provider.get); - await storage.get('test', { download: true }); - expect(provider.get).toBeCalledWith( - 'test', - expect.anything(), - getStorageUserAgentValue(StorageAction.Get) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.Get, + }) ); }); test('uploadData', async () => { - provider.put = jest.fn(provider.put); - await storage.put('test', 'testData'); - expect(provider.put).toBeCalledWith( - 'test', - 'testData', - expect.anything(), - getStorageUserAgentValue(StorageAction.Put) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.Put, + }) ); }); test('copy', async () => { - provider.copy = jest.fn(provider.copy); - await storage.copy({ key: 'testSrc' }, { key: 'testDest' }); - expect(provider.copy).toBeCalledWith( - { key: 'testSrc' }, - { key: 'testDest' }, - expect.anything(), - getStorageUserAgentValue(StorageAction.Copy) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.Copy, + }) ); }); test('list', async () => { - provider.list = jest.fn(provider.list); - await storage.list(''); - expect(provider.list).toBeCalledWith( - '', - undefined, - getStorageUserAgentValue(StorageAction.List) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.List, + }) ); }); test('remove', async () => { - provider.remove = jest.fn(provider.remove); - await storage.remove('test'); - expect(provider.remove).toBeCalledWith( - 'test', - undefined, - getStorageUserAgentValue(StorageAction.Remove) + expect(utils.loadS3Config).toBeCalledWith( + expect.objectContaining({ + storageAction: StorageAction.Remove, + }) ); }); }); diff --git a/packages/storage/internals/package.json b/packages/storage/internals/package.json deleted file mode 100644 index 9703093b04d..00000000000 --- a/packages/storage/internals/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@aws-amplify/storage/internals", - "types": "../lib-esm/internals/index.d.ts", - "main": "../lib/internals/index.js", - "module": "../lib-esm/internals/index.js", - "react-native": "../lib-esm/internals/index.js", - "sideEffects": false -} \ No newline at end of file diff --git a/packages/storage/package.json b/packages/storage/package.json index bc438cc8dd7..e774cf01ffa 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -43,8 +43,7 @@ "files": [ "lib", "lib-esm", - "src", - "internals" + "src" ], "dependencies": { "@aws-amplify/core": "5.8.3", diff --git a/packages/storage/src/Storage.ts b/packages/storage/src/Storage.ts index 93f38a66581..289be8945d7 100644 --- a/packages/storage/src/Storage.ts +++ b/packages/storage/src/Storage.ts @@ -1,7 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Amplify, ConsoleLogger as Logger } from '@aws-amplify/core'; +import { + Amplify, + ConsoleLogger as Logger, + parseAWSExports, +} from '@aws-amplify/core'; import { AWSS3Provider } from './providers'; import { StorageCopySource, @@ -23,7 +27,8 @@ import { StorageGetPropertiesOutput, } from './types'; import { PutObjectInput } from './AwsClients/S3'; -import { InternalStorageClass } from './internals/InternalStorage'; +import { isCancelError } from './AwsClients/S3/utils'; +import { AWSS3UploadTask } from './providers/AWSS3UploadTask'; const logger = new Logger('StorageClass'); const loggerStorageInstance = new Logger('Storage'); // Logging relating to Storage instance management @@ -32,16 +37,175 @@ const DEFAULT_PROVIDER = 'AWSS3'; /** * Provide storage methods to use AWS S3 */ -export class Storage extends InternalStorageClass { +export class Storage { + /** + * @private + */ + private _config; + private _pluggables: StorageProvider[]; + + /** + * Similar to the API module. This weak map allows users to cancel their in-flight request made using the Storage + * module. For every get or put request, a unique AbortConttroller will be generated and injected to it's underlying + * Xhr HTTP handler. This map maintains a mapping of Request to AbortController. When .cancel is invoked, it will + * attempt to retrieve it's corresponding abortController and cancel the in-flight request. + */ + private _abortControllerMap: WeakMap, AbortController>; + /** * @public */ public vault: Storage; + /** + * Initialize Storage + * @param {Object} config - Configuration object for storage + */ + constructor() { + this._config = {}; + this._pluggables = []; + this._abortControllerMap = new WeakMap, AbortController>(); + logger.debug('Storage Options', this._config); + + this.get = this.get.bind(this); + this.put = this.put.bind(this); + this.remove = this.remove.bind(this); + this.list = this.list.bind(this); + } + public getModuleName() { return 'Storage'; } + /** + * add plugin into Storage category + * @param {Object} pluggable - an instance of the plugin + */ + public addPluggable(pluggable: StorageProvider) { + if (pluggable && pluggable.getCategory() === 'Storage') { + this._pluggables.push(pluggable); + let config = {}; + + config = pluggable.configure(this._config[pluggable.getProviderName()]); + + return config; + } + } + + /** + * Get the plugin object + * @param providerName - the name of the plugin + */ + public getPluggable(providerName: string) { + const pluggable = this._pluggables.find( + pluggable => pluggable.getProviderName() === providerName + ); + if (pluggable === undefined) { + logger.debug('No plugin found with providerName', providerName); + return null; + } else return pluggable; + } + + /** + * Remove the plugin object + * @param providerName - the name of the plugin + */ + public removePluggable(providerName: string) { + this._pluggables = this._pluggables.filter( + pluggable => pluggable.getProviderName() !== providerName + ); + return; + } + + /** + * Configure Storage + * @param {Object} config - Configuration object for storage + * @return {Object} - Current configuration + */ + configure(config?) { + logger.debug('configure Storage'); + if (!config) return this._config; + + const amplifyConfig = parseAWSExports(config); + + const storageConfig = amplifyConfig.Storage ?? {}; + + const defaultProviderConfigKeys = [ + 'bucket', + 'region', + 'level', + 'track', + 'customPrefix', + 'ContentMD5', + 'serverSideEncryption', + 'SSECustomerAlgorithm', + 'SSECustomerKey', + // TODO(AllanZhengYP): remove in V6. + 'SSECustomerKeyMD5', + 'SSEKMSKeyId', + ]; + + const hasDefaultProviderConfigKeys = (config: object) => + Object.keys(config).find(key => defaultProviderConfigKeys.includes(key)); + + if ( + hasDefaultProviderConfigKeys(storageConfig) && + !storageConfig[DEFAULT_PROVIDER] + ) { + storageConfig[DEFAULT_PROVIDER] = {}; + } + + Object.entries(storageConfig).forEach(([key, value]) => { + if ( + key && + defaultProviderConfigKeys.includes(key) && + value !== undefined + ) { + storageConfig[DEFAULT_PROVIDER][key] = value; + delete storageConfig[key]; + } + }); + + // only update new values for each provider + Object.keys(storageConfig).forEach(providerName => { + if (typeof storageConfig[providerName] !== 'string') { + this._config[providerName] = { + ...this._config[providerName], + ...storageConfig[providerName], + }; + } + }); + + this._pluggables.forEach(pluggable => { + pluggable.configure(this._config[pluggable.getProviderName()]); + }); + + if (this._pluggables.length === 0) { + this.addPluggable(new AWSS3Provider()); + } + + return this._config; + } + + private getAbortController(): AbortController { + return new AbortController(); + } + + private updateRequestToBeCancellable( + request: Promise, + abortController: AbortController + ) { + this._abortControllerMap.set(request, abortController); + } + + private isUploadTask(x: unknown): x is UploadTask { + return ( + typeof x !== 'undefined' && + typeof x['pause'] === 'function' && + typeof x['resume'] === 'function' + ); + } + /** * Cancels an inflight request * @@ -54,7 +218,19 @@ export class Storage extends InternalStorageClass { request: Promise | UploadTask, message?: string ): void | Promise { - return super.cancel(request, message); + if (request instanceof AWSS3UploadTask) { + return request._cancel(); + } + const abortController = this._abortControllerMap.get( + request as Promise + ); + if (abortController) { + // TODO: [v6] clean up the aborted promise in the weak map. + // Not doing it yet to avoid breaking changes when users may abort a request twice. + abortController.abort(message); + } else { + logger.debug('The request does not map to any cancel token'); + } } /** @@ -75,7 +251,28 @@ export class Storage extends InternalStorageClass { dest: Parameters[1], config?: StorageCopyConfig ): StorageCopyOutput { - return super.copy(src, dest, config); + const provider = config?.provider || DEFAULT_PROVIDER; + const plugin = this._pluggables.find( + pluggable => pluggable.getProviderName() === provider + ); + if (plugin === undefined) { + logger.debug('No plugin found with providerName', provider); + return Promise.reject( + 'No plugin found in Storage for the provider' + ) as StorageCopyOutput; + } + const abortController = this.getAbortController(); + if (typeof plugin.copy !== 'function') { + return Promise.reject( + `.copy is not implemented on provider ${plugin.getProviderName()}` + ) as StorageCopyOutput; + } + const responsePromise = plugin.copy(src, dest, { + ...config, + abortSignal: abortController.signal, + }); + this.updateRequestToBeCancellable(responsePromise, abortController); + return responsePromise as StorageCopyOutput; } /** @@ -93,14 +290,52 @@ export class Storage extends InternalStorageClass { public get< T extends StorageProvider | { [key: string]: any; download?: boolean } >(key: string, config?: StorageGetConfig): StorageGetOutput { - return super.get(key, config); + const provider = config?.provider || DEFAULT_PROVIDER; + const plugin = this._pluggables.find( + pluggable => pluggable.getProviderName() === provider + ); + if (plugin === undefined) { + logger.debug('No plugin found with providerName', provider); + return Promise.reject( + 'No plugin found in Storage for the provider' + ) as StorageGetOutput; + } + const abortController = this.getAbortController(); + const responsePromise = plugin.get(key, { + ...config, + abortSignal: abortController.signal, + }); + this.updateRequestToBeCancellable(responsePromise, abortController); + return responsePromise as StorageGetOutput; + } + + public isCancelError(error: any) { + return isCancelError(error); } public getProperties( key: string, config?: StorageGetPropertiesConfig ): StorageGetPropertiesOutput { - return super.getProperties(key, config); + const provider = config?.provider || DEFAULT_PROVIDER; + const plugin = this._pluggables.find( + pluggable => pluggable.getProviderName() === provider + ); + if (plugin === undefined) { + logger.debug('No plugin found with providerName', provider); + throw new Error('No plugin found with providerName'); + } + const abortController = this.getAbortController(); + if (typeof plugin.getProperties !== 'function') { + return Promise.reject( + `.getProperties is not implemented on provider ${plugin.getProviderName()}` + ) as StorageGetPropertiesOutput; + } + const responsePromise = plugin?.getProperties(key, { + ...config, + }); + this.updateRequestToBeCancellable(responsePromise, abortController); + return responsePromise as StorageGetPropertiesOutput; } /** * Put a file in storage bucket specified to configure method @@ -120,7 +355,25 @@ export class Storage extends InternalStorageClass { object: Omit, config?: StoragePutConfig ): StoragePutOutput { - return super.put(key, object, config); + const provider = config?.provider || DEFAULT_PROVIDER; + const plugin = this._pluggables.find( + pluggable => pluggable.getProviderName() === provider + ); + if (plugin === undefined) { + logger.debug('No plugin found with providerName', provider); + return Promise.reject( + 'No plugin found in Storage for the provider' + ) as StoragePutOutput; + } + const abortController = this.getAbortController(); + const response = plugin.put(key, object, { + ...config, + abortSignal: abortController.signal, + }); + if (!this.isUploadTask(response)) { + this.updateRequestToBeCancellable(response, abortController); + } + return response as StoragePutOutput; } /** @@ -137,7 +390,17 @@ export class Storage extends InternalStorageClass { key: string, config?: StorageRemoveConfig ): StorageRemoveOutput { - return super.remove(key, config); + const provider = config?.provider || DEFAULT_PROVIDER; + const plugin = this._pluggables.find( + pluggable => pluggable.getProviderName() === provider + ); + if (plugin === undefined) { + logger.debug('No plugin found with providerName', provider); + return Promise.reject( + 'No plugin found in Storage for the provider' + ) as StorageRemoveOutput; + } + return plugin.remove(key, config) as StorageRemoveOutput; } /** @@ -154,7 +417,17 @@ export class Storage extends InternalStorageClass { path: string, config?: StorageListConfig ): StorageListOutput { - return super.list(path, config); + const provider = config?.provider || DEFAULT_PROVIDER; + const plugin = this._pluggables.find( + pluggable => pluggable.getProviderName() === provider + ); + if (plugin === undefined) { + logger.debug('No plugin found with providerName', provider); + return Promise.reject( + 'No plugin found in Storage for the provider' + ) as StorageListOutput; + } + return plugin.list(path, config) as StorageListOutput; } } diff --git a/packages/storage/src/common/S3ClientUtils.ts b/packages/storage/src/common/S3ClientUtils.ts index ce70108c88b..1e441ab5e58 100644 --- a/packages/storage/src/common/S3ClientUtils.ts +++ b/packages/storage/src/common/S3ClientUtils.ts @@ -74,13 +74,14 @@ interface S3InputConfig { useAccelerateEndpoint?: boolean; abortSignal?: AbortSignal; emitter?: EventEmitter; - userAgentValue?: string; + storageAction: StorageAction; dangerouslyConnectToHttpEndpointForTesting?: boolean; } export interface S3ResolvedConfig extends Omit { region: string; + userAgentValue?: string; credentials: () => Promise; customEndpoint?: string; forcePathStyle?: boolean; @@ -90,7 +91,7 @@ export interface S3ResolvedConfig * A function that persists the s3 configs, so we don't need to * assign each config parameter for every s3 API call. * - * @internal + * @inernal */ export const loadS3Config = (config: S3InputConfig): S3ResolvedConfig => { if (!config.region) { @@ -103,6 +104,10 @@ export const loadS3Config = (config: S3InputConfig): S3ResolvedConfig => { credentials: config.credentials ? () => Promise.resolve(config.credentials!) : credentialsProvider, + userAgentValue: getAmplifyUserAgent({ + category: Category.Storage, + action: config.storageAction, + }), ...(config.dangerouslyConnectToHttpEndpointForTesting ? { customEndpoint: localTestingStorageEndpoint, diff --git a/packages/storage/src/common/StorageUtils.ts b/packages/storage/src/common/StorageUtils.ts index 7dc9a5da44e..a5b3c668277 100644 --- a/packages/storage/src/common/StorageUtils.ts +++ b/packages/storage/src/common/StorageUtils.ts @@ -1,12 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { - Category, - CustomUserAgentDetails, - getAmplifyUserAgent, - Hub, - StorageAction, -} from '@aws-amplify/core'; +import { Hub } from '@aws-amplify/core'; import { AMPLIFY_SYMBOL } from './StorageConstants'; export const byteLength = (x: unknown) => { @@ -54,16 +48,6 @@ export const isBlob = (x: unknown): x is Blob => { return typeof x !== 'undefined' && x instanceof Blob; }; -export const getStorageUserAgentValue = ( - action: StorageAction, - customUserAgentDetails?: CustomUserAgentDetails -): string => - getAmplifyUserAgent({ - category: Category.Storage, - action, - ...customUserAgentDetails, - }); - const isArrayBuffer = (x: unknown): x is ArrayBuffer => { return typeof x !== 'undefined' && x instanceof ArrayBuffer; }; diff --git a/packages/storage/src/internals/InternalStorage.ts b/packages/storage/src/internals/InternalStorage.ts deleted file mode 100644 index e2174361710..00000000000 --- a/packages/storage/src/internals/InternalStorage.ts +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { - Amplify, - CustomUserAgentDetails, - ConsoleLogger as Logger, - parseAWSExports, - StorageAction, -} from '@aws-amplify/core'; -import { AWSS3Provider } from '../providers'; -import { - StorageCopySource, - StorageCopyDestination, - StorageGetConfig, - StorageProvider, - StoragePutConfig, - StorageRemoveConfig, - StorageListConfig, - StorageCopyConfig, - StorageProviderWithCopy, - StorageGetOutput, - StoragePutOutput, - StorageRemoveOutput, - StorageListOutput, - StorageCopyOutput, - UploadTask, - StorageGetPropertiesConfig, - StorageGetPropertiesOutput, -} from '../types'; -import { PutObjectInput } from '../AwsClients/S3'; -import { isCancelError } from '../AwsClients/S3/utils'; -import { AWSS3UploadTask } from '../providers/AWSS3UploadTask'; -import { getStorageUserAgentValue } from '../common/StorageUtils'; - -const logger = new Logger('StorageClass'); -const loggerStorageInstance = new Logger('Storage'); // Logging relating to Storage instance management - -const DEFAULT_PROVIDER = 'AWSS3'; -/** - * Provide storage methods to use AWS S3 - */ -export class InternalStorageClass { - /** - * @private - */ - private _config; - private _pluggables: StorageProvider[]; - - /** - * Similar to the API module. This weak map allows users to cancel their in-flight request made using the Storage - * module. For every get or put request, a unique AbortConttroller will be generated and injected to it's underlying - * Xhr HTTP handler. This map maintains a mapping of Request to AbortController. When .cancel is invoked, it will - * attempt to retrieve it's corresponding abortController and cancel the in-flight request. - */ - private _abortControllerMap: WeakMap, AbortController>; - - /** - * Initialize Storage - * @param {Object} config - Configuration object for storage - */ - constructor() { - this._config = {}; - this._pluggables = []; - this._abortControllerMap = new WeakMap, AbortController>(); - logger.debug('Storage Options', this._config); - - this.get = this.get.bind(this); - this.put = this.put.bind(this); - this.remove = this.remove.bind(this); - this.list = this.list.bind(this); - } - - public getModuleName() { - return 'InternalStorage'; - } - - /** - * add plugin into Storage category - * @param {Object} pluggable - an instance of the plugin - */ - public addPluggable(pluggable: StorageProvider) { - if (pluggable && pluggable.getCategory() === 'Storage') { - this._pluggables.push(pluggable); - let config = {}; - - config = pluggable.configure(this._config[pluggable.getProviderName()]); - - return config; - } - } - - /** - * Get the plugin object - * @param providerName - the name of the plugin - */ - public getPluggable(providerName: string) { - const pluggable = this._pluggables.find( - pluggable => pluggable.getProviderName() === providerName - ); - if (pluggable === undefined) { - logger.debug('No plugin found with providerName', providerName); - return null; - } else return pluggable; - } - - /** - * Remove the plugin object - * @param providerName - the name of the plugin - */ - public removePluggable(providerName: string) { - this._pluggables = this._pluggables.filter( - pluggable => pluggable.getProviderName() !== providerName - ); - return; - } - - /** - * Configure Storage - * @param {Object} config - Configuration object for storage - * @return {Object} - Current configuration - */ - configure(config?) { - logger.debug('configure Storage'); - if (!config) return this._config; - - const amplifyConfig = parseAWSExports(config); - - const storageConfig = amplifyConfig.Storage ?? {}; - - const defaultProviderConfigKeys = [ - 'bucket', - 'region', - 'level', - 'track', - 'customPrefix', - 'ContentMD5', - 'serverSideEncryption', - 'SSECustomerAlgorithm', - 'SSECustomerKey', - // TODO(AllanZhengYP): remove in V6. - 'SSECustomerKeyMD5', - 'SSEKMSKeyId', - ]; - - const hasDefaultProviderConfigKeys = (config: object) => - Object.keys(config).find(key => defaultProviderConfigKeys.includes(key)); - - if ( - hasDefaultProviderConfigKeys(storageConfig) && - !storageConfig[DEFAULT_PROVIDER] - ) { - storageConfig[DEFAULT_PROVIDER] = {}; - } - - Object.entries(storageConfig).forEach(([key, value]) => { - if ( - key && - defaultProviderConfigKeys.includes(key) && - value !== undefined - ) { - storageConfig[DEFAULT_PROVIDER][key] = value; - delete storageConfig[key]; - } - }); - - // only update new values for each provider - Object.keys(storageConfig).forEach(providerName => { - if (typeof storageConfig[providerName] !== 'string') { - this._config[providerName] = { - ...this._config[providerName], - ...storageConfig[providerName], - }; - } - }); - - this._pluggables.forEach(pluggable => { - pluggable.configure(this._config[pluggable.getProviderName()]); - }); - - if (this._pluggables.length === 0) { - this.addPluggable(new AWSS3Provider()); - } - - return this._config; - } - - private getAbortController(): AbortController { - return new AbortController(); - } - - private updateRequestToBeCancellable( - request: Promise, - abortController: AbortController - ) { - this._abortControllerMap.set(request, abortController); - } - - private isUploadTask(x: unknown): x is UploadTask { - return ( - typeof x !== 'undefined' && - typeof x['pause'] === 'function' && - typeof x['resume'] === 'function' - ); - } - - /** - * Cancels an inflight request - * - * @param request - The request to cancel - * @param [message] - A message to include in the cancelation exception - */ - public cancel( - request: Promise | UploadTask, - message?: string, - customUserAgentDetails?: CustomUserAgentDetails - ): void | Promise { - if (request instanceof AWSS3UploadTask) { - return request._cancel( - getStorageUserAgentValue(StorageAction.Cancel, customUserAgentDetails) - ); - } - const abortController = this._abortControllerMap.get( - request as Promise - ); - if (abortController) { - // TODO: [v6] clean up the aborted promise in the weak map. - // Not doing it yet to avoid breaking changes when users may abort a request twice. - abortController.abort(message); - } else { - logger.debug('The request does not map to any cancel token'); - } - } - - /** - * Copies a file from src to dest. - * - * @param src - The source object. - * @param dest - The destination object. - * @param [config] - config for the Storage operation. - * @return A promise resolves to the copied object's key. - */ - public copy>( - src: StorageCopySource, - dest: StorageCopyDestination, - config?: StorageCopyConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageCopyOutput; - public copy( - src: Parameters[0], - dest: Parameters[1], - config?: StorageCopyConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageCopyOutput { - const provider = config?.provider || DEFAULT_PROVIDER; - const plugin = this._pluggables.find( - pluggable => pluggable.getProviderName() === provider - ); - if (plugin === undefined) { - logger.debug('No plugin found with providerName', provider); - return Promise.reject( - 'No plugin found in Storage for the provider' - ) as StorageCopyOutput; - } - const abortController = this.getAbortController(); - if (typeof plugin.copy !== 'function') { - return Promise.reject( - `.copy is not implemented on provider ${plugin.getProviderName()}` - ) as StorageCopyOutput; - } - const responsePromise = plugin.copy( - src, - dest, - { - ...config, - abortSignal: abortController.signal, - }, - getStorageUserAgentValue(StorageAction.Copy, customUserAgentDetails) - ); - this.updateRequestToBeCancellable(responsePromise, abortController); - return responsePromise as StorageCopyOutput; - } - - /** - * Get a presigned URL of the file or the object data when download:true - * - * @param key - key of the object - * @param [config] - config for the Storage operation. - * @return - A promise resolves to either a presigned url or the object - */ - // Adding & { download?: boolean }, if not T extends { download: true } ? ... : ... will not work properly - public get & { download?: boolean }>( - key: string, - config?: StorageGetConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageGetOutput; - public get< - T extends StorageProvider | { [key: string]: any; download?: boolean } - >( - key: string, - config?: StorageGetConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageGetOutput { - const provider = config?.provider || DEFAULT_PROVIDER; - const plugin = this._pluggables.find( - pluggable => pluggable.getProviderName() === provider - ); - if (plugin === undefined) { - logger.debug('No plugin found with providerName', provider); - return Promise.reject( - 'No plugin found in Storage for the provider' - ) as StorageGetOutput; - } - const abortController = this.getAbortController(); - const responsePromise = plugin.get( - key, - { - ...config, - abortSignal: abortController.signal, - }, - getStorageUserAgentValue(StorageAction.Get, customUserAgentDetails) - ); - this.updateRequestToBeCancellable(responsePromise, abortController); - return responsePromise as StorageGetOutput; - } - - public isCancelError(error: any) { - return isCancelError(error); - } - - public getProperties( - key: string, - config?: StorageGetPropertiesConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageGetPropertiesOutput { - const provider = config?.provider || DEFAULT_PROVIDER; - const plugin = this._pluggables.find( - pluggable => pluggable.getProviderName() === provider - ); - if (plugin === undefined) { - logger.debug('No plugin found with providerName', provider); - throw new Error('No plugin found with providerName'); - } - const abortController = this.getAbortController(); - if (typeof plugin.getProperties !== 'function') { - return Promise.reject( - `.getProperties is not implemented on provider ${plugin.getProviderName()}` - ) as StorageGetPropertiesOutput; - } - - const responsePromise = plugin?.getProperties( - key, - config, - getStorageUserAgentValue( - StorageAction.GetProperties, - customUserAgentDetails - ) - ); - this.updateRequestToBeCancellable(responsePromise, abortController); - return responsePromise as StorageGetPropertiesOutput; - } - /** - * Put a file in storage bucket specified to configure method - * @param key - key of the object - * @param object - File to be put in bucket - * @param [config] - { level : private|protected|public, contentType: MIME Types, - * progressCallback: function } - * @return - promise resolves to object on success - */ - public put>( - key: string, - object: any, - config?: StoragePutConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StoragePutOutput; - public put( - key: string, - object: Omit, - config?: StoragePutConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StoragePutOutput { - const provider = config?.provider || DEFAULT_PROVIDER; - const plugin = this._pluggables.find( - pluggable => pluggable.getProviderName() === provider - ); - if (plugin === undefined) { - logger.debug('No plugin found with providerName', provider); - return Promise.reject( - 'No plugin found in Storage for the provider' - ) as StoragePutOutput; - } - const abortController = this.getAbortController(); - const response = plugin.put( - key, - object, - { - ...config, - abortSignal: abortController.signal, - }, - getStorageUserAgentValue(StorageAction.Put, customUserAgentDetails) - ); - if (!this.isUploadTask(response)) { - this.updateRequestToBeCancellable(response, abortController); - } - return response as StoragePutOutput; - } - - /** - * Remove the object for specified key - * @param key - key of the object - * @param [config] - { level : private|protected|public } - * @return - Promise resolves upon successful removal of the object - */ - public remove>( - key: string, - config?: StorageRemoveConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageRemoveOutput; - public remove( - key: string, - config?: StorageRemoveConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageRemoveOutput { - const provider = config?.provider || DEFAULT_PROVIDER; - const plugin = this._pluggables.find( - pluggable => pluggable.getProviderName() === provider - ); - if (plugin === undefined) { - logger.debug('No plugin found with providerName', provider); - return Promise.reject( - 'No plugin found in Storage for the provider' - ) as StorageRemoveOutput; - } - return plugin.remove( - key, - config, - getStorageUserAgentValue(StorageAction.Remove, customUserAgentDetails) - ) as StorageRemoveOutput; - } - - /** - * List bucket objects relative to the level and prefix specified - * @param path - the path that contains objects - * @param [config] - { level : private|protected|public, maxKeys: NUMBER } - * @return - Promise resolves to list of keys for all objects in path - */ - public list>( - key: string, - config?: StorageListConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageListOutput; - public list( - path: string, - config?: StorageListConfig, - customUserAgentDetails?: CustomUserAgentDetails - ): StorageListOutput { - const provider = config?.provider || DEFAULT_PROVIDER; - const plugin = this._pluggables.find( - pluggable => pluggable.getProviderName() === provider - ); - if (plugin === undefined) { - logger.debug('No plugin found with providerName', provider); - return Promise.reject( - 'No plugin found in Storage for the provider' - ) as StorageListOutput; - } - return plugin.list( - path, - config, - getStorageUserAgentValue(StorageAction.List, customUserAgentDetails) - ) as StorageListOutput; - } -} - -export const InternalStorage: InternalStorageClass = new InternalStorageClass(); -Amplify.register(InternalStorage); diff --git a/packages/storage/src/internals/index.ts b/packages/storage/src/internals/index.ts deleted file mode 100644 index 4b6430fa267..00000000000 --- a/packages/storage/src/internals/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -export { InternalStorage } from './InternalStorage'; diff --git a/packages/storage/src/providers/AWSS3Provider.ts b/packages/storage/src/providers/AWSS3Provider.ts index 730bb80d8e7..c745df0fa76 100644 --- a/packages/storage/src/providers/AWSS3Provider.ts +++ b/packages/storage/src/providers/AWSS3Provider.ts @@ -232,14 +232,12 @@ export class AWSS3Provider implements StorageProvider { * @param {S3CopySource} src - Key and optionally access level and identityId of the source object. * @param {S3CopyDestination} dest - Key and optionally access level of the destination object. * @param {S3ProviderCopyConfig} [config] - Optional configuration for s3 commands. - * @param {string} userAgentValue Optional string containing custom user agent value * @return {Promise} The key of the copied object. */ public async copy( src: S3CopySource, dest: S3CopyDestination, - config?: S3ProviderCopyConfig, - userAgentValue?: string + config?: S3ProviderCopyConfig ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK || !this._isWithCredentials(this._config)) { @@ -313,7 +311,13 @@ export class AWSS3Provider implements StorageProvider { if (acl) params.ACL = acl; try { - await copyObject(loadS3Config({ ...opt, userAgentValue }), params); + await copyObject( + loadS3Config({ + ...opt, + storageAction: StorageAction.Copy, + }), + params + ); dispatchStorageEvent( track, 'copy', @@ -347,19 +351,16 @@ export class AWSS3Provider implements StorageProvider { * * @param {string} key - key of the object * @param {S3ProviderGetConfig} [config] - Optional configuration for the underlying S3 command - * @param {string} userAgentValue Optional string containing custom user agent value * @return {Promise} - A promise resolves to Amazon S3 presigned URL or the * GetObjectCommandOutput if download is set to true on success */ public async get( key: string, - config?: T, - userAgentValue?: string + config?: T ): Promise>; public async get( key: string, - config?: S3ProviderGetConfig, - userAgentValue?: string + config?: S3ProviderGetConfig ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK || !this._isWithCredentials(this._config)) { @@ -388,7 +389,7 @@ export class AWSS3Provider implements StorageProvider { const s3Config = loadS3Config({ ...opt, emitter, - userAgentValue, + storageAction: StorageAction.Get, }); logger.debug('get ' + key + ' from ' + final_key); @@ -509,14 +510,12 @@ export class AWSS3Provider implements StorageProvider { * * @param {string} key - key of the object * @param {S3ProviderGetPropertiesConfig} [config] - Optional configuration for the underlying S3 command - * @param {string} userAgentValue Optional string containing custom user agent value * @return {Promise} - A promise resolves to contentType, * contentLength, eTag, lastModified, metadata */ public async getProperties( key: string, - config?: S3ProviderGetPropertiesConfig, - userAgentValue?: string + config?: S3ProviderGetPropertiesConfig ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK || !this._isWithCredentials(this._config)) { @@ -534,7 +533,10 @@ export class AWSS3Provider implements StorageProvider { const final_key = prefix + key; logger.debug(`getProperties ${key} from ${final_key}`); - const s3Config = loadS3Config({ ...opt, userAgentValue }); + const s3Config = loadS3Config({ + ...opt, + storageAction: StorageAction.GetProperties, + }); const params: HeadObjectInput = { Bucket: bucket, Key: final_key, @@ -589,15 +591,13 @@ export class AWSS3Provider implements StorageProvider { * @param key - key of the object * @param object - File to be put in Amazon S3 bucket * @param [config] - Optional configuration for the underlying S3 command - * @param {string} userAgentValue Optional string containing custom user agent value * @return an instance of AWSS3UploadTask or a promise that resolves to an object with the new object's key on * success. */ public put( key: string, object: PutObjectInput['Body'], - config?: T, - userAgentValue?: string + config?: T ): S3ProviderPutOutput { const opt = Object.assign({}, this._config, config); const { bucket, track, progressCallback, level, resumable } = opt; @@ -661,18 +661,17 @@ export class AWSS3Provider implements StorageProvider { } const emitter = new events.EventEmitter(); - const uploader = new AWSS3ProviderManagedUpload( - params, - { ...opt, userAgentValue }, - emitter - ); + const uploader = new AWSS3ProviderManagedUpload(params, opt, emitter); if (acl) { params.ACL = acl; } if (resumable === true) { - const s3Config = loadS3Config({ ...opt, userAgentValue }); + const s3Config = loadS3Config({ + ...opt, + storageAction: StorageAction.Put, + }); const addTaskInput: AddTaskInput = { bucket, key, @@ -731,13 +730,11 @@ export class AWSS3Provider implements StorageProvider { * Remove the object for specified key * @param {string} key - key of the object * @param {S3ProviderRemoveConfig} [config] - Optional configuration for the underlying S3 command - * @param {string} userAgentValue Optional string containing custom user agent value * @return {Promise} - Promise resolves upon successful removal of the object */ public async remove( key: string, - config?: S3ProviderRemoveConfig, - userAgentValue?: string + config?: S3ProviderRemoveConfig ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK || !this._isWithCredentials(this._config)) { @@ -755,7 +752,10 @@ export class AWSS3Provider implements StorageProvider { Key: final_key, }; - const s3Config = loadS3Config({ ...opt, userAgentValue }); + const s3Config = loadS3Config({ + ...opt, + storageAction: StorageAction.Remove, + }); try { const response = await deleteObject(s3Config, params); dispatchStorageEvent( @@ -780,15 +780,17 @@ export class AWSS3Provider implements StorageProvider { private async _list( params: ListObjectsV2Input, opt: S3ClientOptions, - prefix: string, - userAgentValue?: string + prefix: string ): Promise { const list: S3ProviderListOutput = { results: [], hasNextToken: false, }; const response = await listObjectsV2( - loadS3Config({ ...opt, userAgentValue }), + loadS3Config({ + ...opt, + storageAction: StorageAction.List, + }), { ...params } ); if (response && response.Contents) { @@ -810,14 +812,12 @@ export class AWSS3Provider implements StorageProvider { * List bucket objects relative to the level and prefix specified * @param {string} path - the path that contains objects * @param {S3ProviderListConfig} [config] - Optional configuration for the underlying S3 command - * @param {string} userAgentValue Optional string containing custom user agent value * @return {Promise} - Promise resolves to list of keys, eTags, lastModified * and file size for all objects in path */ public async list( path: string, - config?: S3ProviderListConfig, - userAgentValue?: string + config?: S3ProviderListConfig ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK || !this._isWithCredentials(this._config)) { @@ -844,7 +844,7 @@ export class AWSS3Provider implements StorageProvider { params.ContinuationToken = nextToken; if (pageSize === 'ALL') { do { - listResult = await this._list(params, opt, prefix, userAgentValue); + listResult = await this._list(params, opt, prefix); list.results.push(...listResult.results); if (listResult.nextToken) params.ContinuationToken = listResult.nextToken; @@ -857,7 +857,7 @@ export class AWSS3Provider implements StorageProvider { ) params.MaxKeys = pageSize; else logger.warn(`pageSize should be from 0 - ${MAX_PAGE_SIZE}.`); - listResult = await this._list(params, opt, prefix, userAgentValue); + listResult = await this._list(params, opt, prefix); list.results.push(...listResult.results); list.hasNextToken = listResult.hasNextToken; list.nextToken = null ?? listResult.nextToken; diff --git a/packages/storage/src/providers/AWSS3ProviderManagedUpload.ts b/packages/storage/src/providers/AWSS3ProviderManagedUpload.ts index 8f2d1d29348..ee660cc1802 100644 --- a/packages/storage/src/providers/AWSS3ProviderManagedUpload.ts +++ b/packages/storage/src/providers/AWSS3ProviderManagedUpload.ts @@ -65,6 +65,7 @@ export class AWSS3ProviderManagedUpload { this.s3Config = loadS3Config({ ...opts, emitter, + storageAction: StorageAction.Put, }); } diff --git a/packages/storage/src/providers/AWSS3UploadTask.ts b/packages/storage/src/providers/AWSS3UploadTask.ts index dea535dcd74..2b9598137db 100644 --- a/packages/storage/src/providers/AWSS3UploadTask.ts +++ b/packages/storage/src/providers/AWSS3UploadTask.ts @@ -493,7 +493,7 @@ export class AWSS3UploadTask implements UploadTask { } } - async _cancel(userAgentValue?: string): Promise { + async _cancel(): Promise { if (this.state === AWSS3UploadTaskState.CANCELLED) { logger.warn('This task has already been cancelled'); return false; @@ -506,13 +506,8 @@ export class AWSS3UploadTask implements UploadTask { this.completedParts = []; this.bytesUploaded = 0; this.state = AWSS3UploadTaskState.CANCELLED; - - const config = { - ...this.s3Config, - userAgentValue, - }; try { - await abortMultipartUpload(config, { + await abortMultipartUpload(this.s3Config, { Bucket: this.params.Bucket, Key: (await this.prefixPromise) + this.params.Key, UploadId: this.uploadId, diff --git a/packages/storage/src/types/Provider.ts b/packages/storage/src/types/Provider.ts index 49ee57bea02..66add5bd76f 100644 --- a/packages/storage/src/types/Provider.ts +++ b/packages/storage/src/types/Provider.ts @@ -20,36 +20,26 @@ export interface StorageProvider { copy?( src: StorageCopySource, dest: StorageCopyDestination, - config?, - userAgentValue?: string + config? ): Promise; // configure your provider configure(config: object): object; // get object/pre-signed url from storage - get(key: string, options?, userAgentValue?: string): Promise; + get(key: string, options?): Promise; // get properties of object - getProperties?( - key: string, - options?, - userAgentValue?: string - ): Promise; + getProperties?(key: string, options?): Promise; // upload storage object - put( - key: string, - object, - options?, - userAgentValue?: string - ): Promise | UploadTask; + put(key: string, object, options?): Promise | UploadTask; // remove object - remove(key: string, options?, userAgentValue?: string): Promise; + remove(key: string, options?): Promise; // list objects for the path - list(path, options?, userAgentValue?: string): Promise; + list(path, options?): Promise; // return 'Storage'; getCategory(): string; @@ -70,17 +60,12 @@ export interface StorageProviderWithCopy extends StorageProvider { copy( src: StorageCopySource, dest: StorageCopyDestination, - config?, - userAgentValue?: string + config? ): Promise; } export interface StorageProviderWithGetProperties extends StorageProvider { - getProperties( - key: string, - options?, - userAgentValue?: string - ): Promise; + getProperties(key: string, options?): Promise; } export type StorageProviderApi = @@ -90,14 +75,3 @@ export type StorageProviderApi = | 'remove' | 'list' | 'getProperties'; - -// Map of api to index of options (config) parameter -// Used to glean config type from StorageProvider -export type StorageProviderApiOptionsIndexMap = { - copy: 2; - get: 1; - put: 2; - remove: 1; - list: 1; - getProperties: 1; -}; diff --git a/packages/storage/src/types/Storage.ts b/packages/storage/src/types/Storage.ts index e1c42c7885e..98387026df5 100644 --- a/packages/storage/src/types/Storage.ts +++ b/packages/storage/src/types/Storage.ts @@ -8,7 +8,6 @@ import { ICredentials } from '@aws-amplify/core'; import { StorageProvider, StorageProviderApi, - StorageProviderApiOptionsIndexMap, AWSS3Provider, StorageProviderWithCopy, S3ProviderGetOuput, @@ -29,12 +28,8 @@ type Tail = ((...t: T) => void) extends ( type Last = T[Exclude>]; -// Utility type to extract the config parameter type of a function -// Uses position of params per API to determine which parameter to target -type ConfigParameter< - F extends (...args: any) => any, - U extends StorageProviderApi -> = Parameters[StorageProviderApiOptionsIndexMap[U]]; +// Utility type to extract the last parameter type of a function +type LastParameter any> = Last>; export interface StorageOptions { credentials?: ICredentials; @@ -82,20 +77,20 @@ type StorageOperationConfig< | StorageProviderWithGetProperties, U extends StorageProviderApi > = ReturnType extends 'AWSS3' - ? ConfigParameter // check if it has 'copy' function because 'copy' is optional + ? LastParameter // check if it has 'copy' function because 'copy' is optional : T extends StorageProviderWithGetProperties & StorageProviderWithCopy - ? ConfigParameter & { + ? LastParameter & { provider: ReturnType; } : T extends StorageProviderWithCopy - ? ConfigParameter], U> & { + ? LastParameter]> & { provider: ReturnType; } : T extends StorageProviderWithGetProperties - ? ConfigParameter], U> & { + ? LastParameter]> & { provider: ReturnType; } - : ConfigParameter], U> & { + : LastParameter]> & { provider: ReturnType; }; From ba2ae7183b096fac71e5d4ef9a5c160d11202841 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:49:25 -0500 Subject: [PATCH 13/16] Revert "feat: custom user agent Geo changes for UI handoff (#11632)" This reverts commit 01bfa8f692737bd14422f7dc2eae11ed00c19048. --- packages/analytics/package.json | 4 +- packages/api-rest/package.json | 2 +- packages/auth/package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/Platform/types.ts | 9 +- packages/datastore/package.json | 2 +- packages/geo/internals/package.json | 8 - packages/geo/package.json | 3 +- packages/geo/src/Geo.ts | 214 +++++++++- .../AmazonLocationServiceProvider.ts | 59 +-- packages/geo/src/internals/InternalGeo.ts | 393 ------------------ packages/geo/src/internals/index.ts | 3 - packages/geo/src/internals/utils.ts | 15 - packages/geo/src/types/Provider.ts | 30 +- packages/notifications/package.json | 4 +- packages/predictions/package.json | 2 +- 16 files changed, 242 insertions(+), 510 deletions(-) delete mode 100644 packages/geo/internals/package.json delete mode 100644 packages/geo/src/internals/InternalGeo.ts delete mode 100644 packages/geo/src/internals/index.ts delete mode 100644 packages/geo/src/internals/utils.ts diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 714bcdd6cd8..0ba62cfe1c4 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -66,13 +66,13 @@ "name": "Analytics (Pinpoint)", "path": "./lib-esm/index.js", "import": "{ Amplify, Analytics, AWSPinpointProvider }", - "limit": "31.5 kB" + "limit": "31.1 kB" }, { "name": "Analytics (Kinesis)", "path": "./lib-esm/index.js", "import": "{ Amplify, Analytics, AWSKinesisProvider }", - "limit": "60.5 kB" + "limit": "60.4 kB" } ], "jest": { diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json index d371c11d5dd..7fd6d0ec4f2 100644 --- a/packages/api-rest/package.json +++ b/packages/api-rest/package.json @@ -56,7 +56,7 @@ "name": "API (rest client)", "path": "./lib-esm/index.js", "import": "{ Amplify, RestAPI }", - "limit": "31.5 kB" + "limit": "31 kB" } ], "jest": { diff --git a/packages/auth/package.json b/packages/auth/package.json index 3534901efc0..62149d3ac07 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -60,7 +60,7 @@ "name": "Auth (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Auth }", - "limit": "55.15 kB" + "limit": "55.1 kB" } ], "jest": { diff --git a/packages/core/package.json b/packages/core/package.json index c26c8c2279c..088e9dd21e9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -104,7 +104,7 @@ "name": "Core (Credentials)", "path": "./lib-esm/index.js", "import": "{ Credentials }", - "limit": "13.45 kB" + "limit": "13.33 kB" }, { "name": "Core (Signer)", diff --git a/packages/core/src/Platform/types.ts b/packages/core/src/Platform/types.ts index a4901e1d2e1..09b1bdf5fa7 100644 --- a/packages/core/src/Platform/types.ts +++ b/packages/core/src/Platform/types.ts @@ -94,14 +94,7 @@ export enum DataStoreAction { GraphQl = '2', } export enum GeoAction { - SearchByText = '1', - SearchForSuggestions = '2', - SearchByPlaceId = '3', - SearchByCoordinates = '4', - SaveGeofences = '5', - GetGeofence = '6', - ListGeofences = '7', - DeleteGeofences = '8', + None = '0', } export enum InAppMessagingAction { None = '0', diff --git a/packages/datastore/package.json b/packages/datastore/package.json index cfd61a67ed4..1fb130f0352 100644 --- a/packages/datastore/package.json +++ b/packages/datastore/package.json @@ -73,7 +73,7 @@ "name": "DataStore (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, DataStore }", - "limit": "137.1 kB" + "limit": "137 kB" } ], "jest": { diff --git a/packages/geo/internals/package.json b/packages/geo/internals/package.json deleted file mode 100644 index 0b47f7d5742..00000000000 --- a/packages/geo/internals/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@aws-amplify/geo/internals", - "types": "../lib-esm/internals/index.d.ts", - "main": "../lib/internals/index.js", - "module": "../lib-esm/internals/index.js", - "react-native": "../lib-esm/internals/index.js", - "sideEffects": false -} diff --git a/packages/geo/package.json b/packages/geo/package.json index 1d06adf2b45..6a9bf5d5625 100644 --- a/packages/geo/package.json +++ b/packages/geo/package.json @@ -43,8 +43,7 @@ "files": [ "lib", "lib-esm", - "src", - "internals" + "src" ], "dependencies": { "@aws-amplify/core": "5.8.3", diff --git a/packages/geo/src/Geo.ts b/packages/geo/src/Geo.ts index 3e692cef103..77c5ecca2f4 100644 --- a/packages/geo/src/Geo.ts +++ b/packages/geo/src/Geo.ts @@ -1,11 +1,22 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Amplify, ConsoleLogger as Logger } from '@aws-amplify/core'; +import { + Amplify, + ConsoleLogger as Logger, + parseAWSExports, +} from '@aws-amplify/core'; +import { AmazonLocationServiceProvider } from './Providers/AmazonLocationServiceProvider'; + +import { validateCoordinates } from './util'; + import { Place, + GeoConfig, Coordinates, SearchByTextOptions, SearchByCoordinatesOptions, + GeoProvider, + MapStyle, GeofenceId, GeofenceInput, GeofenceOptions, @@ -16,10 +27,23 @@ import { DeleteGeofencesResults, searchByPlaceIdOptions, } from './types'; -import { InternalGeoClass } from './internals/InternalGeo'; -export class GeoClass extends InternalGeoClass { +const logger = new Logger('Geo'); + +const DEFAULT_PROVIDER = 'AmazonLocationService'; +export class GeoClass { static MODULE = 'Geo'; + /** + * @private + */ + private _config: GeoConfig; + private _pluggables: GeoProvider[]; + + constructor() { + this._config = {}; + this._pluggables = []; + logger.debug('Geo Options', this._config); + } /** * get the name of the module category @@ -29,6 +53,91 @@ export class GeoClass extends InternalGeoClass { return GeoClass.MODULE; } + /** + * add plugin into Geo category + * @param {Object} pluggable - an instance of the plugin + */ + public addPluggable(pluggable: GeoProvider) { + if (pluggable && pluggable.getCategory() === 'Geo') { + this._pluggables.push(pluggable); + const config = pluggable.configure( + this._config[pluggable.getProviderName()] + ); + + return config; + } + } + + /** + * Get the plugin object + * @param providerName - the name of the plugin + */ + public getPluggable(providerName: string) { + const pluggable = this._pluggables.find( + pluggable => pluggable.getProviderName() === providerName + ); + if (pluggable === undefined) { + logger.debug('No plugin found with providerName', providerName); + throw new Error('No plugin found in Geo for the provider'); + } else return pluggable; + } + + /** + * Remove the plugin object + * @param providerName - the name of the plugin + */ + public removePluggable(providerName: string) { + this._pluggables = this._pluggables.filter( + pluggable => pluggable.getProviderName() !== providerName + ); + return; + } + + /** + * Configure Geo + * @param {Object} config - Configuration object for Geo + * @return {Object} - Current configuration + */ + configure(config?) { + logger.debug('configure Geo'); + + if (!config) return this._config; + + const amplifyConfig = parseAWSExports(config); + this._config = Object.assign({}, this._config, amplifyConfig.Geo, config); + + this._pluggables.forEach(pluggable => { + pluggable.configure(this._config[pluggable.getProviderName()]); + }); + + if (this._pluggables.length === 0) { + this.addPluggable(new AmazonLocationServiceProvider()); + } + return this._config; + } + + /** + * Get the map resources that are currently available through the provider + * @param {string} provider + * @returns - Array of available map resources + */ + public getAvailableMaps(provider = DEFAULT_PROVIDER): MapStyle[] { + const prov = this.getPluggable(provider); + + return prov.getAvailableMaps(); + } + + /** + * Get the map resource set as default in amplify config + * @param {string} provider + * @returns - Map resource set as the default in amplify config + */ + public getDefaultMap(provider = DEFAULT_PROVIDER): MapStyle { + const prov = this.getPluggable(provider); + + return prov.getDefaultMap(); + } + /** * Search by text input with optional parameters * @param {string} text - The text string that is to be searched for @@ -39,7 +148,15 @@ export class GeoClass extends InternalGeoClass { text: string, options?: SearchByTextOptions ): Promise { - return super.searchByText(text, options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + try { + return await prov.searchByText(text, options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -52,7 +169,15 @@ export class GeoClass extends InternalGeoClass { text: string, options?: SearchByTextOptions ) { - return super.searchForSuggestions(text, options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + try { + return await prov.searchForSuggestions(text, options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -65,7 +190,15 @@ export class GeoClass extends InternalGeoClass { placeId: string, options?: searchByPlaceIdOptions ) { - return super.searchByPlaceId(placeId, options); + const providerName = DEFAULT_PROVIDER; + const prov = this.getPluggable(providerName); + + try { + return await prov.searchByPlaceId(placeId, options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -78,7 +211,17 @@ export class GeoClass extends InternalGeoClass { coordinates: Coordinates, options?: SearchByCoordinatesOptions ): Promise { - return super.searchByCoordinates(coordinates, options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + const [lng, lat] = coordinates; + try { + validateCoordinates(lng, lat); + return await prov.searchByCoordinates(coordinates, options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -93,7 +236,23 @@ export class GeoClass extends InternalGeoClass { geofences: GeofenceInput | GeofenceInput[], options?: GeofenceOptions ): Promise { - return super.saveGeofences(geofences, options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + // If single geofence input, make it an array for batch call + let geofenceInputArray; + if (!Array.isArray(geofences)) { + geofenceInputArray = [geofences]; + } else { + geofenceInputArray = geofences; + } + + try { + return await prov.saveGeofences(geofenceInputArray, options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -106,7 +265,15 @@ export class GeoClass extends InternalGeoClass { geofenceId: GeofenceId, options?: GeofenceOptions ): Promise { - return super.getGeofence(geofenceId, options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + try { + return await prov.getGeofence(geofenceId, options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -119,7 +286,15 @@ export class GeoClass extends InternalGeoClass { public async listGeofences( options?: ListGeofenceOptions ): Promise { - return super.listGeofences(options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + try { + return await prov.listGeofences(options); + } catch (error) { + logger.debug(error); + throw error; + } } /** @@ -134,7 +309,24 @@ export class GeoClass extends InternalGeoClass { geofenceIds: string | string[], options?: GeofenceOptions ): Promise { - return super.deleteGeofences(geofenceIds, options); + const { providerName = DEFAULT_PROVIDER } = options || {}; + const prov = this.getPluggable(providerName); + + // If single geofence input, make it an array for batch call + let geofenceIdsInputArray; + if (!Array.isArray(geofenceIds)) { + geofenceIdsInputArray = [geofenceIds]; + } else { + geofenceIdsInputArray = geofenceIds; + } + + // Delete geofences + try { + return await prov.deleteGeofences(geofenceIdsInputArray, options); + } catch (error) { + logger.debug(error); + throw error; + } } } diff --git a/packages/geo/src/Providers/AmazonLocationServiceProvider.ts b/packages/geo/src/Providers/AmazonLocationServiceProvider.ts index 69594e981ce..918ef822e13 100644 --- a/packages/geo/src/Providers/AmazonLocationServiceProvider.ts +++ b/packages/geo/src/Providers/AmazonLocationServiceProvider.ts @@ -6,7 +6,6 @@ import { ConsoleLogger as Logger, Credentials, getAmplifyUserAgentObject, - CustomUserAgentDetails, } from '@aws-amplify/core'; import { Place as PlaceResult, @@ -148,13 +147,11 @@ export class AmazonLocationServiceProvider implements GeoProvider { * Search by text input with optional parameters * @param {string} text - The text string that is to be searched for * @param {SearchByTextOptions} options? - Optional parameters to the search - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @returns {Promise} - Promise resolves to a list of Places that match search parameters */ public async searchByText( text: string, - options?: SearchByTextOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: SearchByTextOptions ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK) { @@ -184,7 +181,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); const command = new SearchPlaceIndexForTextCommand(locationServiceInput); @@ -215,14 +212,12 @@ export class AmazonLocationServiceProvider implements GeoProvider { * Search for suggestions based on the input text * @param {string} text - The text string that is to be searched for * @param {SearchByTextOptions} options? - Optional parameters to the search - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @returns {Promise} - Resolves to an array of search suggestion strings */ public async searchForSuggestions( text: string, - options?: SearchByTextOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: SearchByTextOptions ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK) { @@ -252,7 +247,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); const command = new SearchPlaceIndexForSuggestionsCommand( locationServiceInput @@ -287,8 +282,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { public async searchByPlaceId( placeId: string, - options?: searchByPlaceIdOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: searchByPlaceIdOptions ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK) { @@ -301,7 +295,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); const searchByPlaceIdInput: GetPlaceCommandInput = { @@ -335,8 +329,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { */ public async searchByCoordinates( coordinates: Coordinates, - options?: SearchByCoordinatesOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: SearchByCoordinatesOptions ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK) { @@ -360,7 +353,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); const command = new SearchPlaceIndexForPositionCommand( locationServiceInput @@ -391,15 +384,13 @@ export class AmazonLocationServiceProvider implements GeoProvider { * Create geofences inside of a geofence collection * @param geofences - Array of geofence objects to create * @param options? - Optional parameters for creating geofences - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @returns {Promise} - Promise that resolves to an object with: * successes: list of geofences successfully created * errors: list of geofences that failed to create */ public async saveGeofences( geofences: GeofenceInput[], - options?: AmazonLocationServiceGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: AmazonLocationServiceGeofenceOptions ): Promise { if (geofences.length < 1) { throw new Error('Geofence input array is empty'); @@ -451,8 +442,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { try { response = await this._AmazonLocationServiceBatchPutGeofenceCall( batch, - options?.collectionName || this._config.geofenceCollections.default, - customUserAgentDetails + options?.collectionName || this._config.geofenceCollections.default ); } catch (error) { // If the API call fails, add the geofences to the errors array and move to next batch @@ -502,13 +492,11 @@ export class AmazonLocationServiceProvider implements GeoProvider { * Get geofence from a geofence collection * @param geofenceId:string * @param options?: Optional parameters for getGeofence - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @returns {Promise} - Promise that resolves to a geofence object */ public async getGeofence( geofenceId: GeofenceId, - options?: AmazonLocationServiceGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: AmazonLocationServiceGeofenceOptions ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK) { @@ -529,7 +517,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); // Create Amazon Location Service command @@ -567,14 +555,12 @@ export class AmazonLocationServiceProvider implements GeoProvider { /** * List geofences from a geofence collection * @param options?: ListGeofenceOptions - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @returns {Promise} - Promise that resolves to an object with: * entries: list of geofences - 100 geofences are listed per page * nextToken: token for next page of geofences */ public async listGeofences( - options?: AmazonLocationServiceListGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: AmazonLocationServiceListGeofenceOptions ): Promise { const credentialsOK = await this._ensureCredentials(); if (!credentialsOK) { @@ -593,7 +579,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); // Create Amazon Location Service input @@ -650,15 +636,13 @@ export class AmazonLocationServiceProvider implements GeoProvider { * Delete geofences from a geofence collection * @param geofenceIds: string|string[] * @param options?: GeofenceOptions - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details * @returns {Promise} - Promise that resolves to an object with: * successes: list of geofences successfully deleted * errors: list of geofences that failed to delete */ public async deleteGeofences( geofenceIds: string[], - options?: AmazonLocationServiceGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: AmazonLocationServiceGeofenceOptions ): Promise { if (geofenceIds.length < 1) { throw new Error('GeofenceId input array is empty'); @@ -701,8 +685,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { try { response = await this._AmazonLocationServiceBatchDeleteGeofenceCall( batch, - options?.collectionName || this._config.geofenceCollections.default, - customUserAgentDetails + options?.collectionName || this._config.geofenceCollections.default ); } catch (error) { // If the API call fails, add the geofences to the errors array and move to next batch @@ -789,8 +772,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { private async _AmazonLocationServiceBatchPutGeofenceCall( PascalGeofences: BatchPutGeofenceRequestEntry[], - collectionName?: string, - customUserAgentDetails?: CustomUserAgentDetails + collectionName?: string ) { // Create the BatchPutGeofence input const geofenceInput: BatchPutGeofenceCommandInput = { @@ -802,7 +784,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); const command = new BatchPutGeofenceCommand(geofenceInput); @@ -817,8 +799,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { private async _AmazonLocationServiceBatchDeleteGeofenceCall( geofenceIds: string[], - collectionName?: string, - customUserAgentDetails?: CustomUserAgentDetails + collectionName?: string ): Promise { // Create the BatchDeleteGeofence input const deleteGeofencesInput: BatchDeleteGeofenceCommandInput = { @@ -830,7 +811,7 @@ export class AmazonLocationServiceProvider implements GeoProvider { const client = new LocationClient({ credentials: this._config.credentials, region: this._config.region, - customUserAgent: getAmplifyUserAgentObject(customUserAgentDetails), + customUserAgent: getAmplifyUserAgentObject(), }); const command = new BatchDeleteGeofenceCommand(deleteGeofencesInput); diff --git a/packages/geo/src/internals/InternalGeo.ts b/packages/geo/src/internals/InternalGeo.ts deleted file mode 100644 index 65b7cd7cfef..00000000000 --- a/packages/geo/src/internals/InternalGeo.ts +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { - Amplify, - CustomUserAgentDetails, - GeoAction, - ConsoleLogger as Logger, - parseAWSExports, -} from '@aws-amplify/core'; -import { AmazonLocationServiceProvider } from '../Providers/AmazonLocationServiceProvider'; - -import { validateCoordinates } from '../util'; - -import { - Place, - GeoConfig, - Coordinates, - SearchByTextOptions, - SearchByCoordinatesOptions, - GeoProvider, - MapStyle, - GeofenceId, - GeofenceInput, - GeofenceOptions, - SaveGeofencesResults, - Geofence, - ListGeofenceOptions, - ListGeofenceResults, - DeleteGeofencesResults, - searchByPlaceIdOptions, -} from '../types'; -import { getGeoUserAgentDetails } from './utils'; - -const logger = new Logger('Geo'); - -const DEFAULT_PROVIDER = 'AmazonLocationService'; -export class InternalGeoClass { - static MODULE = 'InternalGeo'; - /** - * @private - */ - private _config: GeoConfig; - private _pluggables: GeoProvider[]; - - constructor() { - this._config = {}; - this._pluggables = []; - logger.debug('Geo Options', this._config); - } - - /** - * get the name of the module category - * @returns {string} name of the module category - */ - public getModuleName() { - return InternalGeoClass.MODULE; - } - - /** - * add plugin into Geo category - * @param {Object} pluggable - an instance of the plugin - */ - public addPluggable(pluggable: GeoProvider) { - if (pluggable && pluggable.getCategory() === 'Geo') { - this._pluggables.push(pluggable); - const config = pluggable.configure( - this._config[pluggable.getProviderName()] - ); - - return config; - } - } - - /** - * Get the plugin object - * @param providerName - the name of the plugin - */ - public getPluggable(providerName: string) { - const pluggable = this._pluggables.find( - pluggable => pluggable.getProviderName() === providerName - ); - if (pluggable === undefined) { - logger.debug('No plugin found with providerName', providerName); - throw new Error('No plugin found in Geo for the provider'); - } else return pluggable; - } - - /** - * Remove the plugin object - * @param providerName - the name of the plugin - */ - public removePluggable(providerName: string) { - this._pluggables = this._pluggables.filter( - pluggable => pluggable.getProviderName() !== providerName - ); - return; - } - - /** - * Configure Geo - * @param {Object} config - Configuration object for Geo - * @return {Object} - Current configuration - */ - configure(config?) { - logger.debug('configure Geo'); - - if (!config) return this._config; - - const amplifyConfig = parseAWSExports(config); - this._config = Object.assign({}, this._config, amplifyConfig.Geo, config); - - this._pluggables.forEach(pluggable => { - pluggable.configure(this._config[pluggable.getProviderName()]); - }); - - if (this._pluggables.length === 0) { - this.addPluggable(new AmazonLocationServiceProvider()); - } - return this._config; - } - - /** - * Get the map resources that are currently available through the provider - * @param {string} provider - * @returns - Array of available map resources - */ - public getAvailableMaps(provider = DEFAULT_PROVIDER): MapStyle[] { - const prov = this.getPluggable(provider); - - return prov.getAvailableMaps(); - } - - /** - * Get the map resource set as default in amplify config - * @param {string} provider - * @returns - Map resource set as the default in amplify config - */ - public getDefaultMap(provider = DEFAULT_PROVIDER): MapStyle { - const prov = this.getPluggable(provider); - - return prov.getDefaultMap(); - } - - /** - * Search by text input with optional parameters - * @param {string} text - The text string that is to be searched for - * @param {SearchByTextOptions} options? - Optional parameters to the search - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Promise resolves to a list of Places that match search parameters - */ - public async searchByText( - text: string, - options?: SearchByTextOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - try { - return await prov.searchByText( - text, - options, - getGeoUserAgentDetails(GeoAction.SearchByText, customUserAgentDetails) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * Search for search term suggestions based on input text - * @param {string} text - The text string that is to be search for - * @param {SearchByTextOptions} options? - Optional parameters to the search - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Resolves to an array of search suggestion strings - */ - public async searchForSuggestions( - text: string, - options?: SearchByTextOptions, - customUserAgentDetails?: CustomUserAgentDetails - ) { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - try { - return await prov.searchForSuggestions( - text, - options, - getGeoUserAgentDetails( - GeoAction.SearchForSuggestions, - customUserAgentDetails - ) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * Search for location by unique ID - * @param {string} placeId - Unique ID of the location that is to be searched for - * @param {searchByPlaceIdOptions} options? - Optional parameters to the search - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Resolves to a place with the given placeId - */ - public async searchByPlaceId( - placeId: string, - options?: searchByPlaceIdOptions, - customUserAgentDetails?: CustomUserAgentDetails - ) { - const providerName = DEFAULT_PROVIDER; - const prov = this.getPluggable(providerName); - - try { - return await prov.searchByPlaceId( - placeId, - options, - getGeoUserAgentDetails( - GeoAction.SearchByPlaceId, - customUserAgentDetails - ) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * Reverse geocoding search via a coordinate point on the map - * @param coordinates - Coordinates array for the search input - * @param options - Options parameters for the search - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Promise that resolves to a place matching search coordinates - */ - public async searchByCoordinates( - coordinates: Coordinates, - options?: SearchByCoordinatesOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - const [lng, lat] = coordinates; - try { - validateCoordinates(lng, lat); - return await prov.searchByCoordinates( - coordinates, - options, - getGeoUserAgentDetails(GeoAction.SearchByCoordinates) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * Create geofences - * @param geofences - Single or array of geofence objects to create - * @param options? - Optional parameters for creating geofences - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Promise that resolves to an object with: - * successes: list of geofences successfully created - * errors: list of geofences that failed to create - */ - public async saveGeofences( - geofences: GeofenceInput | GeofenceInput[], - options?: GeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - // If single geofence input, make it an array for batch call - let geofenceInputArray; - if (!Array.isArray(geofences)) { - geofenceInputArray = [geofences]; - } else { - geofenceInputArray = geofences; - } - - try { - return await prov.saveGeofences( - geofenceInputArray, - options, - getGeoUserAgentDetails(GeoAction.SaveGeofences, customUserAgentDetails) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * Get a single geofence by geofenceId - * @param geofenceId: GeofenceId - The string id of the geofence to get - * @param options?: GeofenceOptions - Optional parameters for getting a geofence - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns Promise - Promise that resolves to a geofence object - */ - public async getGeofence( - geofenceId: GeofenceId, - options?: GeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - try { - return await prov.getGeofence( - geofenceId, - options, - getGeoUserAgentDetails(GeoAction.GetGeofence, customUserAgentDetails) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * List geofences - * @param options?: ListGeofenceOptions - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Promise that resolves to an object with: - * entries: list of geofences - 100 geofences are listed per page - * nextToken: token for next page of geofences - */ - public async listGeofences( - options?: ListGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - try { - return await prov.listGeofences( - options, - getGeoUserAgentDetails(GeoAction.ListGeofences, customUserAgentDetails) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } - - /** - * Delete geofences - * @param geofenceIds: string|string[] - * @param options?: GeofenceOptions - * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details - * @returns {Promise} - Promise that resolves to an object with: - * successes: list of geofences successfully deleted - * errors: list of geofences that failed to delete - */ - public async deleteGeofences( - geofenceIds: string | string[], - options?: GeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise { - const { providerName = DEFAULT_PROVIDER } = options || {}; - const prov = this.getPluggable(providerName); - - // If single geofence input, make it an array for batch call - let geofenceIdsInputArray; - if (!Array.isArray(geofenceIds)) { - geofenceIdsInputArray = [geofenceIds]; - } else { - geofenceIdsInputArray = geofenceIds; - } - - // Delete geofences - try { - return await prov.deleteGeofences( - geofenceIdsInputArray, - options, - getGeoUserAgentDetails( - GeoAction.DeleteGeofences, - customUserAgentDetails - ) - ); - } catch (error) { - logger.debug(error); - throw error; - } - } -} - -export const InternalGeo = new InternalGeoClass(); -Amplify.register(InternalGeo); diff --git a/packages/geo/src/internals/index.ts b/packages/geo/src/internals/index.ts deleted file mode 100644 index fe42b92c29f..00000000000 --- a/packages/geo/src/internals/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -export { InternalGeo } from './InternalGeo'; diff --git a/packages/geo/src/internals/utils.ts b/packages/geo/src/internals/utils.ts deleted file mode 100644 index 65412e68320..00000000000 --- a/packages/geo/src/internals/utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { Category, CustomUserAgentDetails, GeoAction } from '@aws-amplify/core'; - -export const getGeoUserAgentDetails = ( - action: GeoAction, - customUserAgentDetails?: CustomUserAgentDetails -): CustomUserAgentDetails => { - return { - category: Category.Geo, - action, - ...customUserAgentDetails, - }; -}; diff --git a/packages/geo/src/types/Provider.ts b/packages/geo/src/types/Provider.ts index 563ecbfcb8f..847033784b3 100644 --- a/packages/geo/src/types/Provider.ts +++ b/packages/geo/src/types/Provider.ts @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { CustomUserAgentDetails } from '@aws-amplify/core'; import { SearchByTextOptions, SearchByCoordinatesOptions, @@ -36,55 +35,42 @@ export interface GeoProvider { getDefaultMap(): MapStyle; // search by a text string and return a list of places - searchByText( - text: string, - options?: SearchByTextOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise; + searchByText(text: string, options?: SearchByTextOptions): Promise; // search by coordinates and return a matching place searchByCoordinates( coordinates: Coordinates, - options?: SearchByCoordinatesOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: SearchByCoordinatesOptions ): Promise; searchForSuggestions( text: string, - options?: SearchByTextOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: SearchByTextOptions ): Promise; searchByPlaceId( placeId: string, - options?: searchByPlaceIdOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: searchByPlaceIdOptions ): Promise; // create geofences saveGeofences( geofences: GeofenceInput[], - options?: GeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: GeofenceOptions ): Promise; // get a single geofence getGeofence( geofenceId: GeofenceId, - options?: ListGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: ListGeofenceOptions ): Promise; // list all geofences - listGeofences( - options?: ListGeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails - ): Promise; + listGeofences(options?: ListGeofenceOptions): Promise; // Delete geofences deleteGeofences( geofenceIds: string[], - options?: GeofenceOptions, - customUserAgentDetails?: CustomUserAgentDetails + options?: GeofenceOptions ): Promise; } diff --git a/packages/notifications/package.json b/packages/notifications/package.json index e63495e815c..f8f229d8f22 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -65,13 +65,13 @@ "name": "Notifications (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, Notifications }", - "limit": "30.5 kB" + "limit": "29.95 kB" }, { "name": "Notifications (with Analytics)", "path": "./lib-esm/index.js", "import": "{ Amplify, Notifications, Analytics }", - "limit": "30.5 kB" + "limit": "29.95 kB" } ] } diff --git a/packages/predictions/package.json b/packages/predictions/package.json index 95641f8d968..69fb94d1da5 100644 --- a/packages/predictions/package.json +++ b/packages/predictions/package.json @@ -69,7 +69,7 @@ "name": "Predictions (Convert provider)", "path": "./lib-esm/index.js", "import": "{ Amplify, Predictions, AmazonAIConvertPredictionsProvider }", - "limit": "58 kB" + "limit": "57.9 kB" }, { "name": "Predictions (Identify provider)", From fdd39c9994d03c5cc9236fc9ed7e7b7c124f505b Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 16:51:56 -0500 Subject: [PATCH 14/16] Revert "feat: custom user agent core changes for UI handoff (#11602)" This reverts commit 7365c34b28015af199dbfdb3713cc26e096d1213. --- packages/core/__tests__/Platform-test.ts | 18 ++++-------------- packages/core/package.json | 2 +- packages/core/src/Platform/index.ts | 11 +++-------- packages/core/src/Platform/types.ts | 8 -------- 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/packages/core/__tests__/Platform-test.ts b/packages/core/__tests__/Platform-test.ts index 67de5d4a6e3..8198ff1f888 100644 --- a/packages/core/__tests__/Platform-test.ts +++ b/packages/core/__tests__/Platform-test.ts @@ -40,18 +40,12 @@ describe('Platform test', () => { expect( getAmplifyUserAgentObject({ category: Category.API, - action: ApiAction.GraphQl, - additionalInfo: [ - ['amplify-ui', '1.x.x'], - ['uicomponent', '1'], - ], + action: ApiAction.None, }) ).toStrictEqual([ ['aws-amplify', version], - [Category.API, ApiAction.GraphQl], + [Category.API, ApiAction.None], ['framework', Framework.WebUnknown], - ['amplify-ui', '1.x.x'], - ['uicomponent', '1'], ]); }); }); @@ -67,14 +61,10 @@ describe('Platform test', () => { expect( getAmplifyUserAgent({ category: Category.API, - action: ApiAction.GraphQl, - additionalInfo: [ - ['amplify-ui', '1.x.x'], - ['uicomponent', '1'], - ], + action: ApiAction.None, }) ).toBe( - `${Platform.userAgent} ${Category.API}/${ApiAction.GraphQl} framework/${Framework.WebUnknown} amplify-ui/1.x.x uicomponent/1` + `${Platform.userAgent} ${Category.API}/${ApiAction.None} framework/${Framework.WebUnknown}` ); }); }); diff --git a/packages/core/package.json b/packages/core/package.json index 088e9dd21e9..f1ce211d16d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -104,7 +104,7 @@ "name": "Core (Credentials)", "path": "./lib-esm/index.js", "import": "{ Credentials }", - "limit": "13.33 kB" + "limit": "13.3 kB" }, { "name": "Core (Signer)", diff --git a/packages/core/src/Platform/index.ts b/packages/core/src/Platform/index.ts index ad2877ef097..e9e3ef704be 100644 --- a/packages/core/src/Platform/index.ts +++ b/packages/core/src/Platform/index.ts @@ -4,7 +4,7 @@ import { CustomUserAgentDetails, Framework } from './types'; import { version } from './version'; import { detectFramework, observeFrameworkChanges } from './detectFramework'; -import type { UserAgent as AWSUserAgent } from '@aws-sdk/types'; +import { UserAgent as AWSUserAgent } from '@aws-sdk/types'; const BASE_USER_AGENT = `aws-amplify`; @@ -32,17 +32,12 @@ export const getAmplifyUserAgentObject = ({ category, action, framework, - additionalInfo, }: CustomUserAgentDetails = {}): AWSUserAgent => { - let userAgent: AWSUserAgent = [[BASE_USER_AGENT, version]]; + const userAgent: AWSUserAgent = [[BASE_USER_AGENT, version]]; if (category) { userAgent.push([category, action]); } - userAgent.push(['framework', framework || detectFramework()]); - - if (additionalInfo) { - userAgent = userAgent.concat(additionalInfo); - } + userAgent.push(['framework', detectFramework()]); return userAgent; }; diff --git a/packages/core/src/Platform/types.ts b/packages/core/src/Platform/types.ts index 09b1bdf5fa7..86929507b36 100644 --- a/packages/core/src/Platform/types.ts +++ b/packages/core/src/Platform/types.ts @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import type { UserAgent as AWSUserAgent } from '@aws-sdk/types'; export enum Framework { // < 100 - Web frameworks @@ -144,15 +143,8 @@ type UserAgentDetailsWithCategory = type CustomUserAgentDetailsBase = { framework?: Framework; - // DO NOT REMOVE: - // Used by other Amplify teams to pass information to the custom user agent - /** Accepts an array of arrays with exactly 1 or 2 values and translates - those arrays to "item" or "item1/item2" strings on the custom user agent */ - additionalInfo?: AWSUserAgent; }; -// DO NOT MAKE ANY PROP REQUIRED on CustomUserAgentDetails -// This object is used by other Amplify teams to pass information to the user agent export type CustomUserAgentDetails = | (CustomUserAgentDetailsBase & { category?: never; action?: never }) | UserAgentDetailsWithCategory From f71d173ecc73a5ef3f08f4b2a60805a7652fdbfb Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 17:14:39 -0500 Subject: [PATCH 15/16] fix: Bundle size adjustment --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index f1ce211d16d..c15cec4f85a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -104,7 +104,7 @@ "name": "Core (Credentials)", "path": "./lib-esm/index.js", "import": "{ Credentials }", - "limit": "13.3 kB" + "limit": "13.35 kB" }, { "name": "Core (Signer)", From b3480b8995b4d778aaf5ef624680a2de280bdc39 Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 22 Aug 2023 17:20:24 -0500 Subject: [PATCH 16/16] fix: Revert integ test workflow --- .github/workflows/push-integ-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-integ-test.yml b/.github/workflows/push-integ-test.yml index 337e08e69de..a7a09f7a539 100644 --- a/.github/workflows/push-integ-test.yml +++ b/.github/workflows/push-integ-test.yml @@ -8,7 +8,7 @@ concurrency: on: push: branches: - - feat/rollback-user-agent/main + - feat/example-integ-test-branch/main jobs: e2e: