diff --git a/src/dispatch/Authentication.ts b/src/dispatch/Authentication.ts index 856b0da0..eea0d22e 100644 --- a/src/dispatch/Authentication.ts +++ b/src/dispatch/Authentication.ts @@ -3,19 +3,22 @@ import { Config } from '../orchestration/Orchestration'; import { AwsCredentialIdentity } from '@aws-sdk/types'; import { FetchHttpHandler } from '@aws-sdk/fetch-http-handler'; import { CRED_KEY, CRED_RENEW_MS } from '../utils/constants'; +import { getCookieName } from '../utils/cookies-utils'; export abstract class Authentication { + protected applicationId: string; protected cognitoIdentityClient: CognitoIdentityClient; protected config: Config; protected credentials: AwsCredentialIdentity | undefined; - constructor(config: Config) { + constructor(config: Config, applicationId: string) { const region: string = config.identityPoolId!.split(':')[0]; this.config = config; this.cognitoIdentityClient = new CognitoIdentityClient({ fetchRequestHandler: new FetchHttpHandler(), region }); + this.applicationId = applicationId; } /** @@ -80,7 +83,15 @@ export abstract class Authentication { return new Promise((resolve, reject) => { let credentials: AwsCredentialIdentity; try { - credentials = JSON.parse(localStorage.getItem(CRED_KEY)!); + credentials = JSON.parse( + localStorage.getItem( + getCookieName( + this.config.cookieAttributes.unique, + CRED_KEY, + this.applicationId + ) + )! + ); } catch (e) { // Error retrieving, decoding or parsing the cred string -- abort return reject(); diff --git a/src/dispatch/BasicAuthentication.ts b/src/dispatch/BasicAuthentication.ts index 908cdff9..796b58bd 100644 --- a/src/dispatch/BasicAuthentication.ts +++ b/src/dispatch/BasicAuthentication.ts @@ -4,12 +4,13 @@ import { FetchHttpHandler } from '@aws-sdk/fetch-http-handler'; import { StsClient } from './StsClient'; import { CRED_KEY } from '../utils/constants'; import { Authentication } from './Authentication'; +import { getCookieName } from '../utils/cookies-utils'; export class BasicAuthentication extends Authentication { private stsClient: StsClient; - constructor(config: Config) { - super(config); + constructor(config: Config, applicationId: string) { + super(config, applicationId); const region: string = config.identityPoolId!.split(':')[0]; this.stsClient = new StsClient({ fetchRequestHandler: new FetchHttpHandler(), @@ -51,7 +52,11 @@ export class BasicAuthentication extends Authentication { this.credentials = credentials; try { localStorage.setItem( - CRED_KEY, + getCookieName( + this.config.cookieAttributes.unique, + CRED_KEY, + this.applicationId + ), JSON.stringify(credentials) ); } catch (e) { diff --git a/src/dispatch/EnhancedAuthentication.ts b/src/dispatch/EnhancedAuthentication.ts index adc3ac5f..26c52e77 100644 --- a/src/dispatch/EnhancedAuthentication.ts +++ b/src/dispatch/EnhancedAuthentication.ts @@ -2,10 +2,11 @@ import { Config } from '../orchestration/Orchestration'; import { AwsCredentialIdentity } from '@aws-sdk/types'; import { CRED_KEY } from '../utils/constants'; import { Authentication } from './Authentication'; +import { getCookieName } from '../utils/cookies-utils'; export class EnhancedAuthentication extends Authentication { - constructor(config: Config) { - super(config); + constructor(config: Config, applicationId: string) { + super(config, applicationId); } /** * Provides credentials for an anonymous (guest) user. These credentials are retrieved from Cognito's enhanced @@ -34,7 +35,11 @@ export class EnhancedAuthentication extends Authentication { this.credentials = credentials; try { localStorage.setItem( - CRED_KEY, + getCookieName( + this.config.cookieAttributes.unique, + CRED_KEY, + this.applicationId + ), JSON.stringify(credentials) ); } catch (e) { diff --git a/src/dispatch/__tests__/BasicAuthentication.test.ts b/src/dispatch/__tests__/BasicAuthentication.test.ts index 36ae038e..de66eeb9 100644 --- a/src/dispatch/__tests__/BasicAuthentication.test.ts +++ b/src/dispatch/__tests__/BasicAuthentication.test.ts @@ -1,6 +1,6 @@ import { BasicAuthentication } from '../BasicAuthentication'; import { CRED_KEY } from '../../utils/constants'; -import { DEFAULT_CONFIG } from '../../test-utils/test-utils'; +import { APPLICATION_ID, DEFAULT_CONFIG } from '../../test-utils/test-utils'; const assumeRole = jest.fn(); const mockGetId = jest.fn(); @@ -54,7 +54,7 @@ describe('BasicAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); localStorage.setItem( CRED_KEY, @@ -88,7 +88,7 @@ describe('BasicAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); localStorage.setItem(CRED_KEY, 'corrupt'); @@ -107,13 +107,16 @@ describe('BasicAuthentication tests', () => { test('when credential is not in localStorage then authentication chain retrieves credential from basic authflow', async () => { // Init - const auth = new BasicAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new BasicAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Run const credentials = await auth.ChainAnonymousCredentialsProvider(); @@ -139,7 +142,7 @@ describe('BasicAuthentication tests', () => { } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); localStorage.setItem( CRED_KEY, @@ -181,13 +184,16 @@ describe('BasicAuthentication tests', () => { sessionToken: 'z' }); - const auth = new BasicAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new BasicAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -210,13 +216,16 @@ describe('BasicAuthentication tests', () => { throw e; }); // Init - const auth = new BasicAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new BasicAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Assert return expect(auth.ChainAnonymousCredentialsProvider()).rejects.toEqual( @@ -230,13 +239,16 @@ describe('BasicAuthentication tests', () => { throw e; }); // Init - const auth = new BasicAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new BasicAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Assert return expect(auth.ChainAnonymousCredentialsProvider()).rejects.toEqual( @@ -250,13 +262,16 @@ describe('BasicAuthentication tests', () => { throw e; }); // Init - const auth = new BasicAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new BasicAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Assert expect(auth.ChainAnonymousCredentialsProvider()).rejects.toEqual(e); @@ -272,7 +287,7 @@ describe('BasicAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -321,7 +336,7 @@ describe('BasicAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -358,7 +373,7 @@ describe('BasicAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); // Run const credentials = await auth.ChainAnonymousCredentialsProvider(); @@ -396,7 +411,7 @@ describe('BasicAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new BasicAuthentication(config); + const auth = new BasicAuthentication(config, APPLICATION_ID); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -414,13 +429,77 @@ describe('BasicAuthentication tests', () => { throw e; }); // Init - const auth = new BasicAuthentication({ + const auth = new BasicAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); + + // Run + const credentials = await auth.ChainAnonymousCredentialsProvider(); + + // Assert + expect(credentials).toEqual( + expect.objectContaining({ + accessKeyId: 'x', + secretAccessKey: 'y', + sessionToken: 'z' + }) + ); + }); + + test('when unique cookie names are used then cookie name with application id appended is stored', async () => { + // Init + const config = { ...DEFAULT_CONFIG, ...{ identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN + guestRoleArn: GUEST_ROLE_ARN, + allowCookies: true, + cookieAttributes: { + ...DEFAULT_CONFIG.cookieAttributes, + ...{ unique: true } + } } - }); + }; + const auth = new BasicAuthentication(config, APPLICATION_ID); + + // Run + await auth.ChainAnonymousCredentialsProvider(); + const credentials = JSON.parse( + localStorage.getItem(`${CRED_KEY}_${APPLICATION_ID}`)! + ); + + // Assert + expect(credentials).toEqual( + expect.objectContaining({ + accessKeyId: 'x', + secretAccessKey: 'y', + sessionToken: 'z' + }) + ); + }); + + test('when unique cookie names are used then cookie name with application id appended is retrieved', async () => { + // Init + const config = { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN, + allowCookies: true, + cookieAttributes: { + ...DEFAULT_CONFIG.cookieAttributes, + ...{ unique: true } + } + } + }; + const auth = new BasicAuthentication(config, APPLICATION_ID); // Run const credentials = await auth.ChainAnonymousCredentialsProvider(); diff --git a/src/dispatch/__tests__/EnhancedAuthentication.test.ts b/src/dispatch/__tests__/EnhancedAuthentication.test.ts index fd330d54..3588bf63 100644 --- a/src/dispatch/__tests__/EnhancedAuthentication.test.ts +++ b/src/dispatch/__tests__/EnhancedAuthentication.test.ts @@ -1,6 +1,7 @@ import { CRED_KEY } from '../../utils/constants'; import { EnhancedAuthentication } from '../EnhancedAuthentication'; -import { DEFAULT_CONFIG } from '../../test-utils/test-utils'; +import { APPLICATION_ID, DEFAULT_CONFIG } from '../../test-utils/test-utils'; +import { getCookieName } from '../../utils/cookies-utils'; const mockGetId = jest.fn(); const getCredentials = jest.fn(); @@ -41,7 +42,7 @@ describe('EnhancedAuthentication tests', () => { } }; - const auth = new EnhancedAuthentication(config); + const auth = new EnhancedAuthentication(config, APPLICATION_ID); localStorage.setItem( CRED_KEY, @@ -76,7 +77,7 @@ describe('EnhancedAuthentication tests', () => { } }; - const auth = new EnhancedAuthentication(config); + const auth = new EnhancedAuthentication(config, APPLICATION_ID); localStorage.setItem(CRED_KEY, 'corrupt'); @@ -95,12 +96,15 @@ describe('EnhancedAuthentication tests', () => { test('when credential is not in the store authentication chain retrieves credential from basic authflow', async () => { // Init - const auth = new EnhancedAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID - } - }); + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID + } + }, + APPLICATION_ID + ); // Run const credentials = await auth.ChainAnonymousCredentialsProvider(); @@ -126,7 +130,7 @@ describe('EnhancedAuthentication tests', () => { } }; - const auth = new EnhancedAuthentication(config); + const auth = new EnhancedAuthentication(config, APPLICATION_ID); localStorage.setItem( CRED_KEY, @@ -168,13 +172,16 @@ describe('EnhancedAuthentication tests', () => { sessionToken: 'z' }); - const auth = new EnhancedAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -197,13 +204,16 @@ describe('EnhancedAuthentication tests', () => { throw new Error('mockGetId error'); }); - const auth = new EnhancedAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Assert expect((auth: EnhancedAuthentication) => { @@ -217,13 +227,16 @@ describe('EnhancedAuthentication tests', () => { throw new Error('mockGetId error'); }); - const auth = new EnhancedAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN - } - }); + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); // Assert expect((auth: EnhancedAuthentication) => { @@ -241,7 +254,7 @@ describe('EnhancedAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new EnhancedAuthentication(config); + const auth = new EnhancedAuthentication(config, APPLICATION_ID); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -290,7 +303,7 @@ describe('EnhancedAuthentication tests', () => { guestRoleArn: GUEST_ROLE_ARN } }; - const auth = new EnhancedAuthentication(config); + const auth = new EnhancedAuthentication(config, APPLICATION_ID); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -327,12 +340,15 @@ describe('EnhancedAuthentication tests', () => { }) ); - const auth = new EnhancedAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID - } - }); + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID + } + }, + APPLICATION_ID + ); // Run const credentials = await auth.ChainAnonymousCredentialsProvider(); @@ -364,12 +380,15 @@ describe('EnhancedAuthentication tests', () => { }) ); - const auth = new EnhancedAuthentication({ - ...DEFAULT_CONFIG, - ...{ - identityPoolId: IDENTITY_POOL_ID - } - }); + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID + } + }, + APPLICATION_ID + ); // Run await auth.ChainAnonymousCredentialsProvider(); @@ -387,13 +406,76 @@ describe('EnhancedAuthentication tests', () => { throw new Error('mockGetId error'); }); - const auth = new EnhancedAuthentication({ + const auth = new EnhancedAuthentication( + { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + guestRoleArn: GUEST_ROLE_ARN + } + }, + APPLICATION_ID + ); + + // Run + const credentials = await auth.ChainAnonymousCredentialsProvider(); + + // Assert + expect(credentials).toEqual( + expect.objectContaining({ + accessKeyId: 'x', + secretAccessKey: 'y', + sessionToken: 'z' + }) + ); + }); + + test('when unique cookie names are used then cookie name with application id appended is stored', async () => { + // Init + const config = { ...DEFAULT_CONFIG, ...{ identityPoolId: IDENTITY_POOL_ID, - guestRoleArn: GUEST_ROLE_ARN + allowCookies: true, + cookieAttributes: { + ...DEFAULT_CONFIG.cookieAttributes, + ...{ unique: true } + } } - }); + }; + + const auth = new EnhancedAuthentication(config, APPLICATION_ID); + + // Run + await auth.ChainAnonymousCredentialsProvider(); + const credentials = JSON.parse( + localStorage.getItem(`${CRED_KEY}_${APPLICATION_ID}`)! + ); + + // Assert + expect(credentials).toEqual( + expect.objectContaining({ + accessKeyId: 'x', + secretAccessKey: 'y', + sessionToken: 'z' + }) + ); + }); + test('when unique cookie names are used then cookie name with application id appended is retrieved', async () => { + // Init + const config = { + ...DEFAULT_CONFIG, + ...{ + identityPoolId: IDENTITY_POOL_ID, + allowCookies: true, + cookieAttributes: { + ...DEFAULT_CONFIG.cookieAttributes, + ...{ unique: true } + } + } + }; + + const auth = new EnhancedAuthentication(config, APPLICATION_ID); // Run const credentials = await auth.ChainAnonymousCredentialsProvider(); diff --git a/src/orchestration/Orchestration.ts b/src/orchestration/Orchestration.ts index 4155bbd7..a94a4c15 100644 --- a/src/orchestration/Orchestration.ts +++ b/src/orchestration/Orchestration.ts @@ -238,7 +238,7 @@ export class Orchestration { applicationVersion ); - this.dispatchManager = this.initDispatch(region); + this.dispatchManager = this.initDispatch(region, applicationId); this.pluginManager = this.initPluginManager( applicationId, applicationVersion @@ -378,7 +378,7 @@ export class Orchestration { ); } - private initDispatch(region: string) { + private initDispatch(region: string, applicationId: string) { const dispatch: Dispatch = new Dispatch( region, this.config.endpointUrl, @@ -395,12 +395,12 @@ export class Orchestration { if (this.config.identityPoolId && this.config.guestRoleArn) { dispatch.setAwsCredentials( - new BasicAuthentication(this.config) + new BasicAuthentication(this.config, applicationId) .ChainAnonymousCredentialsProvider ); } else if (this.config.identityPoolId) { dispatch.setAwsCredentials( - new EnhancedAuthentication(this.config) + new EnhancedAuthentication(this.config, applicationId) .ChainAnonymousCredentialsProvider ); } diff --git a/src/sessions/SessionManager.ts b/src/sessions/SessionManager.ts index ac33d712..ccdf3480 100644 --- a/src/sessions/SessionManager.ts +++ b/src/sessions/SessionManager.ts @@ -1,4 +1,4 @@ -import { storeCookie, getCookie } from '../utils/cookies-utils'; +import { storeCookie, getCookie, getCookieName } from '../utils/cookies-utils'; import { v4 } from 'uuid'; import { Config } from '../orchestration/Orchestration'; @@ -176,7 +176,11 @@ export class SessionManager { private createOrRenewSessionCookie(session: Session, expires: Date) { if (btoa) { storeCookie( - this.sessionCookieName(), + getCookieName( + this.config.cookieAttributes.unique, + SESSION_COOKIE_NAME, + this.appMonitorDetails.id! + ), btoa(JSON.stringify(session)), this.config.cookieAttributes, undefined, @@ -201,7 +205,13 @@ export class SessionManager { private getSessionFromCookie() { if (this.useCookies()) { - const cookie: string = getCookie(this.sessionCookieName()); + const cookie: string = getCookie( + getCookieName( + this.config.cookieAttributes.unique, + SESSION_COOKIE_NAME, + this.appMonitorDetails.id! + ) + ); if (cookie && atob) { try { @@ -286,11 +296,4 @@ export class SessionManager { private sample(): boolean { return Math.random() < this.config.sessionSampleRate; } - - private sessionCookieName(): string { - if (this.config.cookieAttributes.unique) { - return `${SESSION_COOKIE_NAME}_${this.appMonitorDetails.id}`; - } - return SESSION_COOKIE_NAME; - } } diff --git a/src/utils/cookies-utils.ts b/src/utils/cookies-utils.ts index 20740d41..dc174edc 100644 --- a/src/utils/cookies-utils.ts +++ b/src/utils/cookies-utils.ts @@ -69,3 +69,14 @@ export const getCookie = (name: string): string => { } return ''; }; + +export const getCookieName = ( + isUnique: boolean, + cookiePrefix: string, + appMonitorId: string +): string => { + if (isUnique) { + return `${cookiePrefix}_${appMonitorId}`; + } + return cookiePrefix; +};