diff --git a/index.js b/index.js index 36d352d..2db19b9 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,27 @@ const fetch = require('node-fetch'); const jwt = require('jsonwebtoken'); const FormData = require('form-data'); +const MISSING_PARAMS = 'missing_params'; +const SIGN_FAILED = 'sign_failed'; +const REQUEST_FAILED = 'request_failed'; +const UNEXPECTED_RESPONSE_BODY = 'invalid_response_body'; + +const throwRequestFailedError = details => { + const error = new Error( + `Request failed while swapping the jwt token. ${details}` + ); + error.code = REQUEST_FAILED; + throw error; +}; + +const throwUnexpectedResponseError = details => { + const error = new Error( + `Unexpected response received while swapping the jwt token. ${details}` + ); + error.code = UNEXPECTED_RESPONSE_BODY; + throw error; +}; + async function authorize(options) { let { clientId, @@ -34,9 +55,11 @@ async function authorize(options) { !privateKey ? errors.push('privateKey') : ''; !metaScopes || metaScopes.length === 0 ? errors.push('metaScopes') : ''; if (errors.length > 0) { - return Promise.reject( - new Error(`Required parameter(s) ${errors.join(', ')} are missing`) + const missingParamsError = new Error( + `Required parameter(s) ${errors.join(', ')} are missing` ); + missingParamsError.code = MISSING_PARAMS; + throw missingParamsError; } if (metaScopes.constructor !== Array) { @@ -66,7 +89,8 @@ async function authorize(options) { { algorithm: 'RS256' } ); } catch (tokenError) { - return Promise.reject(tokenError); + tokenError.code = SIGN_FAILED; + throw tokenError; } const form = new FormData(); @@ -81,19 +105,24 @@ async function authorize(options) { }; return fetch(`${ims}/ims/exchange/jwt/`, postOptions) - .then(res => res.json()) + .catch(e => throwRequestFailedError(e.message)) + .then(res => { + if (res.ok) { + return res.json(); + } else { + throwRequestFailedError(res.statusText); + } + }) .then(json => { const { access_token, error, error_description } = json; if (!access_token) { if (error && error_description) { - return Promise.reject(new Error(`${error}: ${error_description}`)); + const swapError = new Error(error_description); + swapError.code = error; + throw swapError; } else { - return Promise.reject( - new Error( - `An unknown error occurred while swapping jwt. The response is as follows: ${JSON.stringify( - json - )}` - ) + throwUnexpectedResponseError( + `The response body is as follows: ${JSON.stringify(json)}` ); } } diff --git a/test/auth.test.js b/test/auth.test.js index baf3e86..5f28ca8 100644 --- a/test/auth.test.js +++ b/test/auth.test.js @@ -23,18 +23,34 @@ let mockResultSuccess = Promise.resolve({ json: () => Promise.resolve({ access_token: mockAccessToken, expires_in: 123456 }) }); +// attempting to contact the API threw an error +let mockEndpointFailure = Promise.reject(new Error('Endpoint error.')); +// simple API failure let mockResultFailure = Promise.resolve({ ok: false, + status: 400, + statusText: 'You did something wrong for the jwt exchange.', json: () => Promise.resolve({ error: 'my_error_code', error_description: 'This is the error description.' }) }); -let mockResultFailureUnknown = Promise.resolve({ - ok: false, +// no access token, error, or error_description +let mockResultFailureMalformedServerResponse = Promise.resolve({ + ok: true, + status: 200, json: () => Promise.resolve({ foo: 'bar', baz: 'faz' }) }); +let mockResultFailureNoJWT = Promise.resolve({ + ok: true, + status: 200, + json: () => + Promise.resolve({ + error: 'my_error_code_no_jwt', + error_description: 'This is the error description. No JWT present.' + }) +}); let fetch = require('node-fetch'); jest.mock('node-fetch', () => jest.fn()); @@ -217,6 +233,23 @@ describe('Fetch jwt', () => { ).resolves.toEqual({ access_token: mockAccessToken, expires_in: 123456 }); }); + test('endpoint error thrown, unkown reason', () => { + expect.assertions(1); + fetch.mockImplementation(() => mockEndpointFailure); + return expect( + auth({ + clientId, + clientSecret, + technicalAccountId, + orgId, + metaScopes, + privateKey + }) + ).rejects.toThrow( + 'Request failed while swapping the jwt token. Endpoint error.' + ); + }); + test('invalid jwt, expected endpoint error', () => { expect.assertions(1); fetch.mockImplementation(() => mockResultFailure); @@ -229,12 +262,33 @@ describe('Fetch jwt', () => { metaScopes, privateKey }) - ).rejects.toThrow('my_error_code: This is the error description.'); + ).rejects.toThrow( + 'Request failed while swapping the jwt token. You did something wrong for the jwt exchange.' + ); + }); + + test('malformed server response, dump entire json() call', () => { + expect.assertions(1); + fetch.mockImplementation(() => mockResultFailureMalformedServerResponse); + return expect( + auth({ + clientId, + clientSecret, + technicalAccountId, + orgId, + metaScopes, + privateKey + }) + ).rejects.toThrow( + new Error( + 'Unexpected response received while swapping the jwt token. The response body is as follows: {"foo":"bar","baz":"faz"}' + ) + ); }); - test('invalid jwt, unknown error', () => { + test('no access token, valid server response', () => { expect.assertions(1); - fetch.mockImplementation(() => mockResultFailureUnknown); + fetch.mockImplementation(() => mockResultFailureNoJWT); return expect( auth({ clientId, @@ -245,7 +299,7 @@ describe('Fetch jwt', () => { privateKey }) ).rejects.toThrow( - 'An unknown error occurred while swapping jwt. The response is as follows: {"foo":"bar","baz":"faz"}' + new Error('This is the error description. No JWT present.') ); }); });