Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 92 additions & 10 deletions src/dispatch/Dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { Config } from '../orchestration/Orchestration';
import { v4 } from 'uuid';
import { RetryHttpHandler } from './RetryHttpHandler';
import { InternalLogger } from '../utils/InternalLogger';
import { CRED_KEY, IDENTITY_KEY } from '../utils/constants';
import { BasicAuthentication } from '../dispatch/BasicAuthentication';
import { EnhancedAuthentication } from '../dispatch/EnhancedAuthentication';

type SendFunction = (
putRumEventsRequest: PutRumEventsRequest
Expand All @@ -31,6 +34,7 @@ export type ClientBuilder = (
) => DataPlaneClient;

export class Dispatch {
private applicationId: string;
private region: string;
private endpoint: URL;
private eventCache: EventCache;
Expand All @@ -41,13 +45,23 @@ export class Dispatch {
private config: Config;
private disableCodes = ['403', '404'];
private headers: any;
private credentialProvider:
| AwsCredentialIdentity
| AwsCredentialIdentityProvider
| undefined;

private shouldPurgeCredentials = true;
private credentialStorageKey: string;
private identityStorageKey: string;

constructor(
applicationId: string,
region: string,
endpoint: URL,
eventCache: EventCache,
config: Config
) {
this.applicationId = applicationId;
this.region = region;
this.endpoint = endpoint;
this.eventCache = eventCache;
Expand All @@ -68,6 +82,14 @@ export class Dispatch {
} else {
this.rum = this.buildClient(this.endpoint, this.region, undefined);
}

this.credentialStorageKey = this.config.cookieAttributes.unique
? `${CRED_KEY}_${applicationId}`
: CRED_KEY;

this.identityStorageKey = this.config.cookieAttributes.unique
? `${IDENTITY_KEY}_${applicationId}`
: IDENTITY_KEY;
}

/**
Expand Down Expand Up @@ -100,6 +122,7 @@ export class Dispatch {
| AwsCredentialIdentity
| AwsCredentialIdentityProvider
): void {
this.credentialProvider = credentialProvider;
this.rum = this.buildClient(
this.endpoint,
this.region,
Expand All @@ -112,6 +135,23 @@ export class Dispatch {
}
}

public setCognitoCredentials(
identityPoolId: string,
guestRoleArn?: string
) {
if (identityPoolId && guestRoleArn) {
this.setAwsCredentials(
new BasicAuthentication(this.config, this.applicationId)
.ChainAnonymousCredentialsProvider
);
} else {
this.setAwsCredentials(
new EnhancedAuthentication(this.config, this.applicationId)
.ChainAnonymousCredentialsProvider
);
}
}

/**
* Send meta data and events to the AWS RUM data plane service via fetch.
*/
Expand Down Expand Up @@ -273,17 +313,32 @@ export class Dispatch {
}

private handleReject = (e: any): { response: HttpResponse } => {
if (e instanceof Error && this.disableCodes.includes(e.message)) {
// RUM disables only when dispatch fails and we are certain
// that subsequent attempts will not succeed, such as when
// credentials are invalid or the app monitor does not exist.
if (this.config.debug) {
InternalLogger.error(
'Dispatch failed with status code:',
e.message
);
if (e instanceof Error) {
if (
e.message === '403' &&
this.config.signing &&
this.shouldPurgeCredentials
) {
// If auth fails and a credentialProvider has been configured,
// then we need to make sure that the cached credentials are for
// the intended RUM app monitor. Otherwise, the irrelevant cached
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Does this cause a risk of infinite cycle of purge and creation of new client if say the user actually just happens to have configuration issues on their IAM permissions?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok i think this isn't an issue with the shouldPurge flag you added

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we only purge once, and let the normal retries/backoff do their thing

// credentials will never get evicted.
//
// The next retry or request will be made with fresh credentials.
this.shouldPurgeCredentials = false;
this.forceRebuildClient();
} else if (this.disableCodes.includes(e.message)) {
// RUM disables only when dispatch fails and we are certain
// that subsequent attempts will not succeed, such as when
// credentials are invalid or the app monitor does not exist.
if (this.config.debug) {
InternalLogger.error(
'Dispatch failed with status code:',
e.message
);
}
this.disable();
}
this.disable();
}
throw e;
};
Expand Down Expand Up @@ -314,4 +369,31 @@ export class Dispatch {
headers: this.headers
});
};

/**
* Purges the cached credentials and rebuilds the dataplane client. This is only necessary
* if signing is enabled and the cached credentials are for the wrong app monitor.
*
* @param credentialProvider - The credential or provider use to sign requests
*/
private forceRebuildClient() {
InternalLogger.warn('Removing credentials from local storage');
localStorage.removeItem(this.credentialStorageKey);

if (this.config.identityPoolId) {
InternalLogger.info(
'Rebuilding client with fresh cognito credentials'
);
localStorage.removeItem(this.identityStorageKey);
this.setCognitoCredentials(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: will this retrigger credential fetching?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

 public setCognitoCredentials(
        identityPoolId: string,
        guestRoleArn?: string
    ) {
        if (identityPoolId && guestRoleArn) {
            this.setAwsCredentials(
                new BasicAuthentication(this.config, this.applicationId)
                    .ChainAnonymousCredentialsProvider
            );
        } else {
            this.setAwsCredentials(
                new EnhancedAuthentication(this.config, this.applicationId)
                    .ChainAnonymousCredentialsProvider
            );
        }
    }

this.config.identityPoolId,
this.config.guestRoleArn
);
} else if (this.credentialProvider) {
InternalLogger.info(
'Rebuilding client with most recently passed provider'
);
this.setAwsCredentials(this.credentialProvider);
}
}
}
Loading