Skip to content
Open
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
118 changes: 65 additions & 53 deletions lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,42 +105,50 @@ export default class Auth {
constructor(authObject, options) {
if (!authObject) {
throw new Error('authObject are required. see readme.');
} else if (!authObject.client_id) {
throw new Error('client_id or client_secret is missing or invalid');
} else if (typeof authObject.client_id !== 'string') {
throw new TypeError('client_id or client_secret must be strings');
} else if (!authObject.client_secret) {
throw new Error('client_id or client_secret is missing or invalid');
} else if (typeof authObject.client_secret !== 'string') {
throw new TypeError('client_id or client_secret must be strings');
} else if (!authObject.account_id) {
throw new Error('account_id is missing or invalid');
} else if (!Number.isInteger(Number.parseInt(authObject.account_id))) {
throw new TypeError(
'account_id must be an Integer (Integers in String format are accepted)',
);
} else if (!authObject.auth_url) {
throw new Error('auth_url is missing or invalid');
} else if (typeof authObject.auth_url !== 'string') {
throw new TypeError('auth_url must be a string');
} else if (
!/https:\/\/[\da-z-]{28}\.auth\.marketingcloudapis\.com\//gm.test(authObject.auth_url)
) {
throw new Error(
'auth_url must be in format https://mcXXXXXXXXXXXXXXXXXXXXXXXXXX.auth.marketingcloudapis.com/',
);
} else if (authObject.scope && !Array.isArray(authObject.scope)) {
throw new Error('Scope must be in array format');
} else if (authObject.scope && getInvalidScopes(authObject.scope).length > 0) {
throw new Error(
getInvalidScopes(authObject.scope)
.map((value) => '"' + value + '"')
.join(',') + ' is/are invalid scope(s)',
);
} else if (!authObject.access_token) {
if (!authObject.client_id) {
throw new Error('client_id or client_secret is missing or invalid');
} else if (typeof authObject.client_id !== 'string') {
throw new TypeError('client_id or client_secret must be strings');
} else if (!authObject.client_secret) {
throw new Error('client_id or client_secret is missing or invalid');
} else if (typeof authObject.client_secret !== 'string') {
throw new TypeError('client_id or client_secret must be strings');
} else if (!authObject.account_id) {
throw new Error('account_id is missing or invalid');
} else if (!Number.isInteger(Number.parseInt(authObject.account_id))) {
throw new TypeError(
'account_id must be an Integer (Integers in String format are accepted)',
);
} else if (!authObject.auth_url) {
throw new Error('auth_url is missing or invalid');
} else if (typeof authObject.auth_url !== 'string') {
throw new TypeError('auth_url must be a string');
} else if (
!/https:\/\/[\da-z-]{28}\.auth\.marketingcloudapis\.com\//gm.test(
authObject.auth_url,
)
) {
throw new Error(
'auth_url must be in format https://mcXXXXXXXXXXXXXXXXXXXXXXXXXX.auth.marketingcloudapis.com/',
);
} else if (authObject.scope && !Array.isArray(authObject.scope)) {
throw new Error('Scope must be in array format');
} else if (authObject.scope && getInvalidScopes(authObject.scope).length > 0) {
throw new Error(
getInvalidScopes(authObject.scope)
.map((value) => '"' + value + '"')
.join(',') + ' is/are invalid scope(s)',
);
}
} else if (authObject.access_token) {
// no action needed
}

this.authObject = Object.assign(this.authObject || {}, authObject);
this.options = options;
}

/**
*
*
Expand All @@ -149,30 +157,34 @@ export default class Auth {
* @returns {Promise.<object>} current session information
*/
async getAccessToken(forceRefresh, remainingAttempts) {
if (remainingAttempts === undefined) {
remainingAttempts = this.options.requestAttempts;
}
if (Boolean(forceRefresh) || _isExpired(this.authObject)) {
try {
remainingAttempts--;
this.authObject = await _requestToken(this.authObject);
} catch (error) {
if (
this.options.retryOnConnectionError &&
remainingAttempts > 0 &&
isConnectionError(error.code)
) {
if (this.options?.eventHandlers?.onConnectionError) {
this.options.eventHandlers.onConnectionError(error, remainingAttempts);
if (this.authObject.access_token) {
return this.authObject;
} else {
if (remainingAttempts === undefined) {
remainingAttempts = this.options.requestAttempts;
}
if (Boolean(forceRefresh) || _isExpired(this.authObject)) {
try {
remainingAttempts--;
this.authObject = await _requestToken(this.authObject);
} catch (error) {
if (
this.options.retryOnConnectionError &&
remainingAttempts > 0 &&
isConnectionError(error.code)
) {
if (this.options?.eventHandlers?.onConnectionError) {
this.options.eventHandlers.onConnectionError(error, remainingAttempts);
}
return this.getAccessToken(forceRefresh, remainingAttempts);
}
return this.getAccessToken(forceRefresh, remainingAttempts);
}

throw new RestError(error);
}
throw new RestError(error);
}

if (this.options?.eventHandlers?.onRefresh) {
this.options.eventHandlers.onRefresh(this.authObject);
if (this.options?.eventHandlers?.onRefresh) {
this.options.eventHandlers.onRefresh(this.authObject);
}
}
}
return this.authObject;
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default class SDK {
if (options.retryOnConnectionError == undefined) {
options.retryOnConnectionError = true;
}

this.auth = new Auth(authObject, options);
this.rest = new Rest(this.auth, options);
this.soap = new Soap(this.auth, options);
Expand Down
14 changes: 13 additions & 1 deletion test/auth.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert } from 'chai';
import SDK from '../lib/index.js';
import { defaultSdk, mock } from './utils.js';
import { defaultSdk, mock, sdkWithAccessToken } from './utils.js';
import { success, unauthorized } from './resources/auth.js';
import { isConnectionError } from '../lib/util.js';

Expand Down Expand Up @@ -182,4 +182,16 @@ describe('auth', function () {
assert.lengthOf(mock.history.post, 2);
return;
});

it('should return an auth payload if access token is present', async function () {
//given

// when
const sdk = sdkWithAccessToken();
await sdk.auth.getAccessToken();
const auth = await sdk.auth.getAccessToken();
// then
assert.equal(auth.access_token, success.response.access_token);
return;
});
});
36 changes: 36 additions & 0 deletions test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,39 @@ export const defaultSdk = () => {
},
);
};

export const sdkWithAccessToken = () => {
return new SDK(
{
access_token: 'TESTTOKEN',
refresh_token: 'yyyyyy',
token_type: 'Bearer',
expires_in: 1079,
scope: 'tracking_events_read file_locations_read data_extensions_read list_and_subscribers_read audiences_read journeys_read automations_read',
soap_instance_url: 'https://xxxxxxxxxxx.soap.marketingcloudapis.com/',
rest_instance_url: 'https://xxxxxxxxxxx.rest.marketingcloudapis.com/',
},
{
eventHandlers: {
logRequest: () => {
return;
},

logResponse: () => {
return;
},
onConnectionError: () => {
return;
},
onRefresh: () => {
return;
},
onLoop: () => {
return;
},
},
retryOnConnectionError: true,
requestAttempts: 2,
},
);
};