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
51 changes: 40 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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)}`
);
}
}
Expand Down
66 changes: 60 additions & 6 deletions test/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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.')
);
});
});