Skip to content

Commit

Permalink
feat: add specific error handling for SSL errors with cloud private i…
Browse files Browse the repository at this point in the history
…nstances (#54)

* return correct data for requests that don't connect
* add some types to local variables in request wrapper
  • Loading branch information
dpopp07 committed Oct 3, 2019
1 parent 2c83eb8 commit 056ec9a
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 13 deletions.
44 changes: 36 additions & 8 deletions lib/requestwrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export class RequestWrapper {
} catch (e) {
// ignore the error, use the object, and tack on a warning
errorBody = axiosError.data;
errorBody.warning = 'body contains circular reference';
errorBody.warning = 'Body contains circular reference';
}

error.body = errorBody;
Expand All @@ -270,14 +270,20 @@ export class RequestWrapper {
if (isAuthenticationError(axiosError)) {
error.message = 'Access is denied due to invalid credentials.';
}

} else if (axiosError.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
error.message = 'Response not received. Body of error is HTTP ClientRequest object';
error.body = axiosError.request;

error.message = axiosError.message;
error.statusText = axiosError.code;
error.body = 'Response not received - no connection was made to the service.';

// when a request to a private cloud instance has an ssl problem, it never connects and follows this branch of the error handling
if (isSelfSignedCertificateError(axiosError)) {
error.message = `If you're trying to call a service on ICP or Cloud Pak for Data, you ` +
`may not have a valid SSL certificate. If you need to access the service without setting that up, try using ` +
`the disableSslVerification option in your client configuration and your authentication configuration if applicable.`;
}
} else {
// Something happened in setting up the request that triggered an Error
error.message = axiosError.message;
Expand Down Expand Up @@ -311,11 +317,11 @@ function parsePath(path: string, params: Object): string {
*/
function isAuthenticationError(error: any): boolean {
let isAuthErr = false;
const code = error.status;
const body = error.data;
const code: number = error.status || null;
const body: any = error.data || {};

// handle specific error from iam service, should be relevant across platforms
const isIamServiceError = body.context &&
const isIamServiceError: boolean = body.context &&
body.context.url &&
body.context.url.indexOf('iam') > -1;

Expand All @@ -326,6 +332,28 @@ function isAuthenticationError(error: any): boolean {
return isAuthErr;
}

/**
* Determine if the error is due to a bad self signed certificate
* @private
* @param {Object} error - error object returned from axios
* @returns {boolean} true if error is due to an SSL error
*/
function isSelfSignedCertificateError(error: any): boolean {
let result = false;

const sslCode = 'DEPTH_ZERO_SELF_SIGNED_CERT';
const sslMessage = 'self signed certificate';

const hasSslCode = error.code === sslCode;
const hasSslMessage = hasStringProperty(error, 'message') && error.message.includes(sslMessage);

if (hasSslCode || hasSslMessage) {
result = true;
}

return result;
}

/**
* Return true if object has a specified property that is a string
* @private
Expand Down
38 changes: 33 additions & 5 deletions test/unit/requestWrapper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,9 @@ describe('formatError', () => {
'x-global-transaction-id': 'fhd7s8hfudj9ksoo0wpnd78a',
},
},
request: {
message: 'request was made but no response was received',
},
request: 'fake-http-request-object',
message: 'error in building the request',
code: 'SOME_STATUS_KEY',
};

it('should get the message from errors[0].message', () => {
Expand Down Expand Up @@ -486,8 +485,37 @@ describe('formatError', () => {
delete basicAxiosError.response;
const error = requestWrapperInstance.formatError(basicAxiosError);
expect(error instanceof Error).toBe(true);
expect(error.message).toBe('Response not received. Body of error is HTTP ClientRequest object');
expect(error.body).toEqual(basicAxiosError.request);
expect(error.message).toBe('error in building the request');
expect(error.statusText).toBe('SOME_STATUS_KEY');
expect(error.body).toBe('Response not received - no connection was made to the service.');
});

it('check the SSL error handler - message condition', () => {
// save the original message
const originalMessage = basicAxiosError.message;

basicAxiosError.message = 'request has self signed certificate';
const error = requestWrapperInstance.formatError(basicAxiosError);

// put the original message back in, before expectations in case they fail
basicAxiosError.message = originalMessage;

expect(error instanceof Error).toBe(true);
expect(error.message).toMatch(/may not have a valid SSL certificate/);
});

it('check the SSL error handler - code condition', () => {
// save the original code
const originalCode = basicAxiosError.code;

basicAxiosError.code = 'DEPTH_ZERO_SELF_SIGNED_CERT';
const error = requestWrapperInstance.formatError(basicAxiosError);

// put the original message back in, before expectations in case they fail
basicAxiosError.code = originalCode;

expect(error instanceof Error).toBe(true);
expect(error.message).toMatch(/may not have a valid SSL certificate/);
});

it('check the message flow', () => {
Expand Down

0 comments on commit 056ec9a

Please sign in to comment.