Skip to content

Commit

Permalink
feat: Add config option to omit request signature. (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
qhanam committed Oct 28, 2022
1 parent 00563f5 commit 49ae45c
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 143 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 57 additions & 44 deletions src/dispatch/DataPlaneClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,82 +42,95 @@ export declare type DataPlaneClientConfig = {
beaconRequestHandler: HttpHandler;
endpoint: URL;
region: string;
credentials: CredentialProvider | Credentials;
credentials: CredentialProvider | Credentials | undefined;
};

export class DataPlaneClient {
private config: DataPlaneClientConfig;
private awsSigV4: SignatureV4;
private awsSigV4: SignatureV4 | undefined;

constructor(config: DataPlaneClientConfig) {
this.config = config;
this.awsSigV4 = new SignatureV4({
applyChecksum: true,
credentials: config.credentials,
region: config.region,
service: SERVICE,
uriEscapePath: true,
sha256: Sha256
});
if (config.credentials) {
this.awsSigV4 = new SignatureV4({
applyChecksum: true,
credentials: config.credentials,
region: config.region,
service: SERVICE,
uriEscapePath: true,
sha256: Sha256
});
}
}

public sendFetch = async (
putRumEventsRequest: PutRumEventsRequest
): Promise<{ response: HttpResponse }> => {
const serializedRequest: string = JSON.stringify(
serializeRequest(putRumEventsRequest)
const options = await this.getHttpRequestOptions(
putRumEventsRequest,
CONTENT_TYPE_JSON
);
const path = this.config.endpoint.pathname.replace(/\/$/, '');
const request = new HttpRequest({
method: METHOD,
headers: {
'content-type': CONTENT_TYPE_JSON,
'X-Amz-Content-Sha256': await hashAndEncode(serializedRequest),
host: this.config.endpoint.host
},
protocol: this.config.endpoint.protocol,
hostname: this.config.endpoint.hostname,
path: `${path}/appmonitors/${putRumEventsRequest.AppMonitorDetails.id}/`,
body: serializedRequest
});

const signedRequest: HttpRequest = (await this.awsSigV4.sign(
request
)) as any;
let request: HttpRequest = new HttpRequest(options);
if (this.awsSigV4) {
request = (await this.awsSigV4.sign(request)) as HttpRequest;
}
const httpResponse: Promise<{
response: HttpResponse;
}> = this.config.fetchRequestHandler.handle(signedRequest);
}> = this.config.fetchRequestHandler.handle(request);
return httpResponse;
};

public sendBeacon = async (
putRumEventsRequest: PutRumEventsRequest
): Promise<{ response: HttpResponse }> => {
const options = await this.getHttpRequestOptions(
putRumEventsRequest,
CONTENT_TYPE_TEXT
);
let request: HttpRequest = new HttpRequest(options);
if (this.awsSigV4) {
request = (await this.awsSigV4.presign(
request,
REQUEST_PRESIGN_ARGS
)) as HttpRequest;
}
const httpResponse: Promise<{
response: HttpResponse;
}> = this.config.beaconRequestHandler.handle(request);
return httpResponse;
};

private getHttpRequestOptions = async (
putRumEventsRequest: PutRumEventsRequest,
contentType: string
) => {
const serializedRequest: string = JSON.stringify(
serializeRequest(putRumEventsRequest)
);
const path = this.config.endpoint.pathname.replace(/\/$/, '');
const request = new HttpRequest({
const options = {
method: METHOD,
protocol: this.config.endpoint.protocol,
headers: {
'content-type': CONTENT_TYPE_TEXT,
'X-Amz-Content-Sha256': await hashAndEncode(serializedRequest),
'content-type': contentType,
host: this.config.endpoint.host
},
protocol: this.config.endpoint.protocol,
hostname: this.config.endpoint.hostname,
path: `${path}/appmonitors/${putRumEventsRequest.AppMonitorDetails.id}`,
body: serializedRequest
});

const preSignedRequest: HttpRequest = (await this.awsSigV4.presign(
request,
REQUEST_PRESIGN_ARGS
)) as any;
const httpResponse: Promise<{
response: HttpResponse;
}> = this.config.beaconRequestHandler.handle(preSignedRequest);
return httpResponse;
};
if (this.awsSigV4) {
return {
...options,
headers: {
...options.headers,
'X-Amz-Content-Sha256': await hashAndEncode(
serializedRequest
)
}
};
}
return options;
};
}

Expand Down
22 changes: 13 additions & 9 deletions src/dispatch/Dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const NO_CRED_MSG = 'CWR: Cannot dispatch; no AWS credentials.';
export type ClientBuilder = (
endpoint: URL,
region: string,
credentials: CredentialProvider | Credentials
credentials: CredentialProvider | Credentials | undefined
) => DataPlaneClient;

export class Dispatch {
Expand All @@ -48,14 +48,18 @@ export class Dispatch {
this.buildClient = config.clientBuilder || this.defaultClientBuilder;
this.config = config;
this.startDispatchTimer();
this.rum = {
sendFetch: (): Promise<{ response: HttpResponse }> => {
return Promise.reject(new Error(NO_CRED_MSG));
},
sendBeacon: (): Promise<{ response: HttpResponse }> => {
return Promise.reject(new Error(NO_CRED_MSG));
}
};
if (config.signing) {
this.rum = {
sendFetch: (): Promise<{ response: HttpResponse }> => {
return Promise.reject(new Error(NO_CRED_MSG));
},
sendBeacon: (): Promise<{ response: HttpResponse }> => {
return Promise.reject(new Error(NO_CRED_MSG));
}
};
} else {
this.rum = this.buildClient(this.endpoint, this.region, undefined);
}
}

/**
Expand Down
60 changes: 35 additions & 25 deletions src/dispatch/__tests__/BeaconHttpHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import * as Utils from '../../test-utils/test-utils';
import { BeaconHttpHandler } from '../BeaconHttpHandler';
import { DataPlaneClient } from '../DataPlaneClient';
import { HttpResponse } from '@aws-sdk/protocol-http';
import { HttpHandler, HttpResponse } from '@aws-sdk/protocol-http';
import { advanceTo } from 'jest-date-mock';

const sendBeacon = jest.fn(() => true);

const createDataPlaneClient = (
config: { signing: boolean } = { signing: true }
): DataPlaneClient => {
const beaconHandler = new BeaconHttpHandler();
return new DataPlaneClient({
fetchRequestHandler: {} as HttpHandler,
beaconRequestHandler: beaconHandler,
endpoint: Utils.AWS_RUM_ENDPOINT,
region: Utils.AWS_RUM_REGION,
credentials: config.signing ? Utils.createAwsCredentials() : undefined
});
};

describe('BeaconHttpHandler tests', () => {
beforeEach(() => {
advanceTo(0);
Expand All @@ -15,14 +28,7 @@ describe('BeaconHttpHandler tests', () => {

test('when sendBeacon succeeds then HttpResponse status is 200', async () => {
// Init
const beaconHandler = new BeaconHttpHandler();
const client: DataPlaneClient = new DataPlaneClient({
fetchRequestHandler: undefined,
beaconRequestHandler: beaconHandler,
endpoint: Utils.AWS_RUM_ENDPOINT,
region: Utils.AWS_RUM_REGION,
credentials: Utils.createAwsCredentials()
});
const client: DataPlaneClient = createDataPlaneClient();

// Run
const response: HttpResponse = (
Expand All @@ -36,14 +42,7 @@ describe('BeaconHttpHandler tests', () => {
test('when sendBeacon fails then promise is rejected', async () => {
// Init
global.navigator.sendBeacon = jest.fn(() => false);
const beaconHandler = new BeaconHttpHandler();
const client: DataPlaneClient = new DataPlaneClient({
fetchRequestHandler: undefined,
beaconRequestHandler: beaconHandler,
endpoint: Utils.AWS_RUM_ENDPOINT,
region: Utils.AWS_RUM_REGION,
credentials: Utils.createAwsCredentials()
});
const client: DataPlaneClient = createDataPlaneClient();

// Run
const response: Promise<{ response: HttpResponse }> = client.sendBeacon(
Expand All @@ -55,14 +54,7 @@ describe('BeaconHttpHandler tests', () => {

test('sendBeacon builds correct url', async () => {
// Init
const beaconHandler = new BeaconHttpHandler();
const client: DataPlaneClient = new DataPlaneClient({
fetchRequestHandler: undefined,
beaconRequestHandler: beaconHandler,
endpoint: Utils.AWS_RUM_ENDPOINT,
region: Utils.AWS_RUM_REGION,
credentials: Utils.createAwsCredentials()
});
const client: DataPlaneClient = createDataPlaneClient();

// Run
const response: HttpResponse = (
Expand All @@ -75,4 +67,22 @@ describe('BeaconHttpHandler tests', () => {
'https://rumservicelambda.us-west-2.amazonaws.com/appmonitors/application123?X-Amz-Algorithm=AWS4-HMAC-SHA256'
);
});

test('when signing is false then sendBeacon omits the signature', async () => {
// Init
const client: DataPlaneClient = createDataPlaneClient({
signing: false
});

// Run
const response: HttpResponse = (
await client.sendBeacon(Utils.PUT_RUM_EVENTS_REQUEST)
).response;

// Assert
const url: string = (sendBeacon.mock.calls[0] as any)[0];
expect(url).toEqual(
'https://rumservicelambda.us-west-2.amazonaws.com/appmonitors/application123'
);
});
});

0 comments on commit 49ae45c

Please sign in to comment.