From b4946396f88b9075cc894b0ac6c62846a9ad0cb7 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Mon, 12 Oct 2026 16:29:52 -0400 Subject: [PATCH 1/2] DX-4068 Fix OAuth Issues --- lib/application.js | 8 +-- lib/client.js | 108 ++++++++++++++++++++++++++--------------- lib/csrOrder.js | 6 ++- lib/disconnect.js | 8 +-- lib/dlda.js | 6 ++- lib/importTnOrder.js | 86 +++++++++++++++++++++++--------- lib/importToAccount.js | 6 ++- lib/lidbs.js | 6 ++- lib/lsrOrder.js | 12 +++-- lib/order.js | 8 +-- lib/portIn.js | 94 ++++++++++++++++++++++++----------- lib/portOut.js | 8 +-- lib/site.js | 16 +++--- lib/subscription.js | 8 +-- lib/tnOption.js | 1 - lib/tnReservation.js | 8 +-- 16 files changed, 252 insertions(+), 137 deletions(-) diff --git a/lib/application.js b/lib/application.js index 0ae50ce..4046a92 100644 --- a/lib/application.js +++ b/lib/application.js @@ -51,8 +51,10 @@ Application._createApplication = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(APPLICATION_PATH), {application: item}); - request.buffer().then(res =>{ + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(APPLICATION_PATH), {application: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -64,8 +66,6 @@ Application._createApplication = function(client, item, callback){ else{ client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; diff --git a/lib/client.js b/lib/client.js index a4dbca9..aaada7c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -41,10 +41,13 @@ class Client { // Start background token refresh if using OAuth and auto-refresh is enabled if (this.autoRefreshToken && this.clientId && this.clientSecret && !this.tempAccessToken) { - this.tokenReadyPromise = new Promise((resolve) => { + this.tokenReadyPromise = new Promise((resolve, reject) => { this._tokenReadyResolve = resolve; + this._tokenReadyReject = reject; }); - this._scheduleTokenRefresh(); + // avoid an unhandled rejection if the fetch fails before any request awaits it + this.tokenReadyPromise.catch(() => {}); + process.nextTick(() => this._refreshToken()); } } @@ -199,52 +202,77 @@ class Client { return this.prepareRequest(superagent.del(this.prepareUrl(path))); } - makeRequest (method) { - var callback = arguments[arguments.length - 1]; - var args = Array.prototype.slice.call(arguments, 1, arguments.length - 1); - var self = this; - - const executeRequest = (isRetry = false) => { - var request = self["create" + method[0].toUpperCase() + method.substr(1).toLowerCase() + "Request"].apply(self, args); - request.buffer().then(res => { - self.checkResponse(res, callback); - }).catch(err => { - // Check for 401 error and no retry yet - if (!isRetry && err.status === 401 && self.autoRefreshToken && self.clientId && self.clientSecret) { - console.log("Got 401, refreshing token and retrying..."); - self.tempAccessToken = null; - self.tempAccessTokenExpiration = null; - - self.tokenReadyPromise = new Promise((resolve, reject) => { - self._tokenReadyResolve = resolve; - self._tokenReadyReject = reject; - }); - self._refreshToken(); - - self.tokenReadyPromise.then(() => { - executeRequest(true); - }).catch((refreshErr) => { - console.error("Token refresh failed during retry:", refreshErr); - return callback(err); // Return original 401 - }); - } else { - return callback(err); - } - }); + buildAuthenticatedRequest (buildRequest, callback) { + const build = (tokenErr) => { + let request; + try { + request = buildRequest(); + } catch (err) { + return callback(tokenErr || err); + } + callback(null, request); }; - + if (this.tokenReadyPromise) { - this.tokenReadyPromise.then(() => { - executeRequest(); - }).catch((err) => { + this.tokenReadyPromise.then(() => build(), (err) => { console.error("Token fetch failed:", err); - executeRequest(); + build(err); }); } else { - executeRequest(); + build(); } } + sendRequest (buildRequest, callback) { + const execute = (isRetry) => { + this.buildAuthenticatedRequest(buildRequest, (err, request) => { + if (err) { + return callback(err); + } + request.buffer().then((res) => callback(null, res), (err) => { + if (!isRetry && this._canRefreshOn401(err)) { + console.log("Got 401, refreshing token and retrying..."); + return this._forceTokenRefresh().then(() => execute(true), (refreshErr) => { + console.error("Token refresh failed during retry:", refreshErr); + callback(err); + }); + } + callback(err); + }); + }); + }; + execute(false); + } + + _canRefreshOn401 (err) { + return err.status === 401 && this.autoRefreshToken && this.clientId && this.clientSecret; + } + + _forceTokenRefresh () { + this.tempAccessToken = null; + this.tempAccessTokenExpiration = null; + const tokenReadyPromise = new Promise((resolve, reject) => { + this._tokenReadyResolve = resolve; + this._tokenReadyReject = reject; + }); + this.tokenReadyPromise = tokenReadyPromise; + this._refreshToken(); + return tokenReadyPromise; + } + + makeRequest (method) { + const callback = arguments[arguments.length - 1]; + const args = Array.prototype.slice.call(arguments, 1, arguments.length - 1); + const createRequest = this["create" + method[0].toUpperCase() + method.substr(1).toLowerCase() + "Request"]; + + this.sendRequest(() => createRequest.apply(this, args), (err, res) => { + if (err) { + return callback(err); + } + this.checkResponse(res, callback); + }); + } + checkResponse (res, callback) { let defaultHandler = function(parsedData){ if (!res.ok) { diff --git a/lib/csrOrder.js b/lib/csrOrder.js index 852afe4..e9c67ba 100644 --- a/lib/csrOrder.js +++ b/lib/csrOrder.js @@ -68,8 +68,10 @@ CsrOrder.prototype.getNotes = function(callback){ CsrOrder.prototype.addNote = function(note, callback){ var self = this; - var request = this.client.createPostRequest(this.client.concatAccountPath(CSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note}); - request.buffer().then(res =>{ + this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(CSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ diff --git a/lib/disconnect.js b/lib/disconnect.js index cc4a69c..a8a3cfb 100644 --- a/lib/disconnect.js +++ b/lib/disconnect.js @@ -68,8 +68,10 @@ Disconnect.prototype.getNotes = function(callback){ Disconnect.prototype.addNote = function(note, callback){ var self = this; - var request = this.client.createPostRequest(this.client.concatAccountPath(DISCONNECT_PATH + "/" + this.id + "/notes"), {note: note}); - request.buffer().then(res => { + this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(DISCONNECT_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -86,8 +88,6 @@ Disconnect.prototype.addNote = function(note, callback){ else{ self.client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; diff --git a/lib/dlda.js b/lib/dlda.js index c05488f..8db46f1 100644 --- a/lib/dlda.js +++ b/lib/dlda.js @@ -11,8 +11,10 @@ Dlda.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(DLDA_PATH), {dldaOrder: item}); - request.buffer().then(res => { + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(DLDA_PATH), {dldaOrder: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err,id){ if(err){ diff --git a/lib/importTnOrder.js b/lib/importTnOrder.js index 2def779..975b87b 100644 --- a/lib/importTnOrder.js +++ b/lib/importTnOrder.js @@ -79,19 +79,26 @@ function sendFile(request, file, mediaType, callback){ else if(Buffer.isBuffer(file)){ stream = streamifier.createReadStream(file); } - else if(typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){ + else if(file && typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){ stream = file; } - if(stream){ - request.on("response", callback); - request.on("error", function(err, result){ - return err; - }); - stream.pipe(request); - - return; + if(!stream){ + return callback(new Error("Invalid data to send")); } - throw new Error("Invalid data to send"); + var done = false; + // a 4xx/5xx emits "response" then "error"; only report the first + var finish = function(err, res){ + if(!done){ + done = true; + callback(err, res); + } + }; + request.on("response", function(res){ + finish(null, res); + }); + request.on("error", finish); + stream.on("error", finish); + stream.pipe(request); } ImportTnOrder.prototype.createFile = function(file, mediaType, callback){ @@ -100,18 +107,25 @@ ImportTnOrder.prototype.createFile = function(file, mediaType, callback){ mediaType = null; } var client = this.client; - var request = client.createPostRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH)); - sendFile(request, file, mediaType, function(res){ - client.checkResponse(res, function(err, result){ + client.buildAuthenticatedRequest(() => client.createPostRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH)), function(err, request){ + if(err){ + return callback(err); + } + sendFile(request, file, mediaType, function(err, res){ if(err){ return callback(err); } - if (typeof result !== 'undefined') { - return callback(null, result.filename); - } - else { - return callback(null, ""); - } + client.checkResponse(res, function(err, result){ + if(err){ + return callback(err); + } + if (typeof result !== 'undefined') { + return callback(null, result.filename); + } + else { + return callback(null, ""); + } + }); }); }); }; @@ -121,10 +135,17 @@ ImportTnOrder.prototype.updateFile = function(fileName, file, mediaType, callbac callback = mediaType; mediaType = null; } - var request = this.client.createPutRequest(this.client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)); var client = this.client; - sendFile(request, file, mediaType, function(res){ - client.checkResponse(res, callback); + client.buildAuthenticatedRequest(() => client.createPutRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)), function(err, request){ + if(err){ + return callback(err); + } + sendFile(request, file, mediaType, function(err, res){ + if(err){ + return callback(err); + } + client.checkResponse(res, callback); + }); }); }; @@ -150,8 +171,25 @@ ImportTnOrder.prototype.getFiles = function(metadata, callback){ }); }; -ImportTnOrder.prototype.getFile = function(fileName, destination){ - var request = this.client.createGetRequest(this.client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)); +ImportTnOrder.prototype.getFile = function(fileName, destination, callback){ + if(typeof destination === "function"){ + callback = destination; + destination = null; + } + var client = this.client; + var buildRequest = () => client.createGetRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)); + if(!callback){ + return pipeToDestination(buildRequest(), destination); + } + client.buildAuthenticatedRequest(buildRequest, function(err, request){ + if(err){ + return callback(err); + } + callback(null, pipeToDestination(request, destination)); + }); +} + +function pipeToDestination(request, destination){ if(destination){ var stream = null; if(typeof destination === "string"){ diff --git a/lib/importToAccount.js b/lib/importToAccount.js index fc54f5c..92588bf 100644 --- a/lib/importToAccount.js +++ b/lib/importToAccount.js @@ -13,8 +13,10 @@ ImportToAccount.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(IMPORT_TO_ACCOUNT_PATH), {importToAccountNumberOrder: item}); - request.buffer().then(res =>{ + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(IMPORT_TO_ACCOUNT_PATH), {importToAccountNumberOrder: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok){ callback(null, res.body) }else { diff --git a/lib/lidbs.js b/lib/lidbs.js index 5cdf028..df991fa 100644 --- a/lib/lidbs.js +++ b/lib/lidbs.js @@ -11,8 +11,10 @@ Lidbs.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(LIDBS_ORDER_PATH), {lidbOrder: item}); - request.buffer().then(res =>{ + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(LIDBS_ORDER_PATH), {lidbOrder: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err,id){ if(err){ diff --git a/lib/lsrOrder.js b/lib/lsrOrder.js index 9b1e27d..4220597 100644 --- a/lib/lsrOrder.js +++ b/lib/lsrOrder.js @@ -41,8 +41,10 @@ LsrOrder.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(LSR_ORDER_PATH), {lsrOrder: item}); - request.buffer().then(res => { + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(LSR_ORDER_PATH), {lsrOrder: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err,id){ if(err){ @@ -83,8 +85,10 @@ LsrOrder.prototype.getNotes = function(callback){ LsrOrder.prototype.addNote = function(note, callback){ var self = this; - var request = this.client.createPostRequest(this.client.concatAccountPath(LSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note}); - request.buffer().then(res =>{ + this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(LSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ diff --git a/lib/order.js b/lib/order.js index 8cf5ec2..cb0779b 100644 --- a/lib/order.js +++ b/lib/order.js @@ -117,8 +117,10 @@ Order.prototype.getNotes = function(callback){ Order.prototype.addNote = function(note, callback){ var self = this; - var request = this.client.createPostRequest(this.client.concatAccountPath(ORDER_PATH + "/" + this.id + "/notes"), {note: note}); - request.buffer().then(res => { + this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(ORDER_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -135,8 +137,6 @@ Order.prototype.addNote = function(note, callback){ else{ self.client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; diff --git a/lib/portIn.js b/lib/portIn.js index 0a02477..ce0a434 100644 --- a/lib/portIn.js +++ b/lib/portIn.js @@ -130,8 +130,10 @@ PortIn.prototype.getNotes = function(callback){ PortIn.prototype.addNote = function(note, callback){ var self = this; - var request = this.client.createPostRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/notes"), {note: note}); - request.buffer().then(res => { + this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -148,8 +150,6 @@ PortIn.prototype.addNote = function(note, callback){ else{ self.client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; @@ -172,19 +172,26 @@ function sendFile(request, file, mediaType, callback){ else if(Buffer.isBuffer(file)){ stream = streamifier.createReadStream(file); } - else if(typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){ + else if(file && typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){ stream = file; } - if(stream){ - request.on("response", callback); - request.on("error", function(err, result){ - return err; - }); - stream.pipe(request); - - return; + if(!stream){ + return callback(new Error("Invalid data to send")); } - throw new Error("Invalid data to send"); + var done = false; + // a 4xx/5xx emits "response" then "error"; only report the first + var finish = function(err, res){ + if(!done){ + done = true; + callback(err, res); + } + }; + request.on("response", function(res){ + finish(null, res); + }); + request.on("error", finish); + stream.on("error", finish); + stream.pipe(request); } PortIn.prototype.createFile = function(file, mediaType, callback){ @@ -193,18 +200,25 @@ PortIn.prototype.createFile = function(file, mediaType, callback){ mediaType = null; } var client = this.client; - var request = client.createPostRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH)); - sendFile(request, file, mediaType, function(res){ - client.checkResponse(res, function(err, result){ + client.buildAuthenticatedRequest(() => client.createPostRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH)), function(err, request){ + if(err){ + return callback(err); + } + sendFile(request, file, mediaType, function(err, res){ if(err){ return callback(err); } - if (typeof result !== 'undefined') { - return callback(null, result.filename); - } - else { - return callback(null, ""); - } + client.checkResponse(res, function(err, result){ + if(err){ + return callback(err); + } + if (typeof result !== 'undefined') { + return callback(null, result.filename); + } + else { + return callback(null, ""); + } + }); }); }); }; @@ -214,10 +228,17 @@ PortIn.prototype.updateFile = function(fileName, file, mediaType, callback){ callback = mediaType; mediaType = null; } - var request = this.client.createPutRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)); var client = this.client; - sendFile(request, file, mediaType, function(res){ - client.checkResponse(res, callback); + client.buildAuthenticatedRequest(() => client.createPutRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)), function(err, request){ + if(err){ + return callback(err); + } + sendFile(request, file, mediaType, function(err, res){ + if(err){ + return callback(err); + } + client.checkResponse(res, callback); + }); }); }; @@ -243,8 +264,25 @@ PortIn.prototype.getFiles = function(metadata, callback){ }); }; -PortIn.prototype.getFile = function(fileName, destination){ - var request = this.client.createGetRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)); +PortIn.prototype.getFile = function(fileName, destination, callback){ + if(typeof destination === "function"){ + callback = destination; + destination = null; + } + var client = this.client; + var buildRequest = () => client.createGetRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)); + if(!callback){ + return pipeToDestination(buildRequest(), destination); + } + client.buildAuthenticatedRequest(buildRequest, function(err, request){ + if(err){ + return callback(err); + } + callback(null, pipeToDestination(request, destination)); + }); +} + +function pipeToDestination(request, destination){ if(destination){ var stream = null; if(typeof destination === "string"){ diff --git a/lib/portOut.js b/lib/portOut.js index cc86318..91b63b3 100644 --- a/lib/portOut.js +++ b/lib/portOut.js @@ -49,8 +49,10 @@ PortOut.prototype.getNotes = function(callback){ PortOut.prototype.addNote = function(note, callback){ var self = this; - var request = this.client.createPostRequest(this.client.concatAccountPath(PORT_OUT_PATH + "/" + this.id + "/notes"), {note: note}); - request.buffer().then(res =>{ + this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(PORT_OUT_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -67,8 +69,6 @@ PortOut.prototype.addNote = function(note, callback){ else{ self.client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; diff --git a/lib/site.js b/lib/site.js index 7aeb26d..c2d3055 100644 --- a/lib/site.js +++ b/lib/site.js @@ -52,8 +52,10 @@ Site.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(SITE_PATH), {site: item}); - request.buffer().then(res =>{ + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(SITE_PATH), {site: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -65,8 +67,6 @@ Site.create = function(client, item, callback){ else{ client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; @@ -123,8 +123,10 @@ Site.prototype.getSipPeer = function(id, callback){ Site.prototype.createSipPeer = function(item, callback){ var client = this.client; var self = this; - var request = client.createPostRequest( this.client.concatAccountPath(SITE_PATH) + "/" + this.id + "/" + SIP_PEER_PATH, {sipPeer: item}); - request.buffer().then(res =>{ + client.sendRequest(() => client.createPostRequest(this.client.concatAccountPath(SITE_PATH) + "/" + this.id + "/" + SIP_PEER_PATH, {sipPeer: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -136,8 +138,6 @@ Site.prototype.createSipPeer = function(item, callback){ else{ client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; diff --git a/lib/subscription.js b/lib/subscription.js index c808e30..5f93763 100644 --- a/lib/subscription.js +++ b/lib/subscription.js @@ -57,8 +57,10 @@ Subscription.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(SUBSCRIPTION_PATH), {subscription: item}); - request.buffer().then(res => { + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(SUBSCRIPTION_PATH), {subscription: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -70,8 +72,6 @@ Subscription.create = function(client, item, callback){ else{ client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; diff --git a/lib/tnOption.js b/lib/tnOption.js index cc50165..085a76f 100644 --- a/lib/tnOption.js +++ b/lib/tnOption.js @@ -61,7 +61,6 @@ TnOption.create = function(client, order, callback){ group.telephoneNumbers = {telephoneNumber: group.telephoneNumbers} }); - var request = client.createPostRequest(client.concatAccountPath(TN_OPTION_PATH), {tnOptionOrder: order}); client.makeRequest("post", client.concatAccountPath(TN_OPTION_PATH), {tnOptionOrder: order}, function(err, item){ if(err){ return callback(err); diff --git a/lib/tnReservation.js b/lib/tnReservation.js index 107f2d2..627eb08 100644 --- a/lib/tnReservation.js +++ b/lib/tnReservation.js @@ -29,8 +29,10 @@ TnReservation.create = function(client, item, callback){ item = client; client = new Client(); } - var request = client.createPostRequest(client.concatAccountPath(TN_RESERVATION_PATH), {reservation: item}); - request.buffer().then(res => { + client.sendRequest(() => client.createPostRequest(client.concatAccountPath(TN_RESERVATION_PATH), {reservation: item}), function(err, res){ + if(err){ + return callback(err); + } if(res.ok && res.headers.location){ Client.getIdFromLocationHeader(res.headers.location, function(err, id){ if(err){ @@ -42,8 +44,6 @@ TnReservation.create = function(client, item, callback){ else{ client.checkResponse(res, callback); } - }).catch(err => { - return callback(err); }); }; From 5e9f9f444865616a647a2e6d516852ab6554fd94 Mon Sep 17 00:00:00 2001 From: ckoegel Date: Tue, 13 Oct 2026 15:01:37 -0400 Subject: [PATCH 2/2] tests --- test/client.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ test/dlda.js | 9 ++++++ test/helper.js | 8 ++++++ test/portIn.js | 70 ++++++++++++++++++++++++++++++++++++++++++++ test/tnOption.js | 23 +++++++++++++++ 5 files changed, 185 insertions(+) diff --git a/test/client.js b/test/client.js index b3f4aeb..5b224cf 100644 --- a/test/client.js +++ b/test/client.js @@ -418,6 +418,81 @@ describe("client tests", function(){ }); }) }); + describe("OAuth token handling", function(){ + var client; + afterEach(function(){ + client.destroy(); + nock.cleanAll(); + }); + it("should wait for the token before making a request", function(done){ + helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600}); + helper.nock().matchHeader("authorization", "Bearer fake_token").get("/test") + .reply(200, "test", {"Content-Type": "application/xml"}); + client = helper.createOAuthClient(); + client.makeRequest("get", "/test", function(err, r){ + if(err){ + return done(err); + } + r.test.should.equal("test"); + done(); + }); + }); + it("should fail with the token error if token fetch fails and no other auth is available", function(done){ + helper.nockToken().reply(401); + client = helper.createOAuthClient(); + client.makeRequest("get", "/test", function(err){ + if(!err){ + return done(new Error("An error is expected")); + } + err.status.should.equal(401); + done(); + }); + }); + it("should fall back to basic auth if token fetch fails", function(done){ + helper.nockToken().reply(401); + helper.nock().matchHeader("authorization", /^Basic /).get("/test").reply(200); + client = new Client("FakeAccountId", "FakeUserName", "FakePassword", "FakeClientId", "FakeClientSecret"); + client.makeRequest("get", "/test", done); + }); + it("should refresh the token and retry once on 401", function(done){ + helper.nockToken().reply(200, {access_token: "expired_token", expires_in: 3600}); + helper.nockToken().reply(200, {access_token: "fresh_token", expires_in: 3600}); + helper.nock().matchHeader("authorization", "Bearer expired_token").get("/test").reply(401); + helper.nock().matchHeader("authorization", "Bearer fresh_token").get("/test") + .reply(200, "test", {"Content-Type": "application/xml"}); + client = helper.createOAuthClient(); + client.makeRequest("get", "/test", function(err, r){ + if(err){ + return done(err); + } + r.test.should.equal("test"); + done(); + }); + }); + it("should not retry more than once on 401", function(done){ + helper.nockToken().times(2).reply(200, {access_token: "fake_token", expires_in: 3600}); + helper.nock().get("/test").times(2).reply(401); + client = helper.createOAuthClient(); + client.makeRequest("get", "/test", function(err){ + if(!err){ + return done(new Error("An error is expected")); + } + err.status.should.equal(401); + done(); + }); + }); + }); + describe("#buildAuthenticatedRequest", function(){ + it("should pass auth errors to the callback instead of throwing", function(done){ + var client = new Client("FakeAccountId", null, null, null, null); + client.buildAuthenticatedRequest(function(){ + return client.createGetRequest("/test"); + }, function(err){ + err.message.should.equal("No authentication method available"); + done(); + }); + }); + }); describe("#concatAccountPath", function(){ it("should return formatted url", function(){ var client = new Client("accountId"); diff --git a/test/dlda.js b/test/dlda.js index bbb337e..fe61f03 100644 --- a/test/dlda.js +++ b/test/dlda.js @@ -53,6 +53,15 @@ describe("Dlda", function(){ done(); }); }); + it("should fail on error status code", function(done){ + helper.nock().post("/accounts/FakeAccountId/dldas").reply(400); + Dlda.create(helper.createClient(), {}, function(err){ + if(err){ + return done(); + } + done(new Error("An error is expected")); + }); + }); }); describe("#getHistory", function(){ it("should return history", function(done){ diff --git a/test/helper.js b/test/helper.js index ee1b6f3..7e1f00b 100644 --- a/test/helper.js +++ b/test/helper.js @@ -12,6 +12,14 @@ module.exports = { return new Client("FakeAccountId", "FakeUserName", "FakePassword"); }, + createOAuthClient: function(){ + return new Client("FakeAccountId", null, null, "FakeClientId", "FakeClientSecret"); + }, + + nockToken: function(){ + return nock("https://api.bandwidth.com").post("/api/v1/oauth2/token", "grant_type=client_credentials"); + }, + buildXml: function(obj){ return this.createClient().buildXml(obj); }, diff --git a/test/portIn.js b/test/portIn.js index 449a42a..a158e95 100644 --- a/test/portIn.js +++ b/test/portIn.js @@ -388,6 +388,42 @@ describe("PortIn", function(){ }); }); }); + it("should download file to destination file via callback with OAuth client credentials only", function(done){ + nock.cleanAll(); + helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600}); + helper.nock().matchHeader("authorization", "Bearer fake_token").get("/accounts/FakeAccountId/portins/1/loas/file.txt").reply(200, "12345", {"Content-Type": "text/plain"}); + var order = new PortIn(); + order.id = 1; + order.client = helper.createOAuthClient(); + order.getFile("file.txt", tmpFile, function(err, stream){ + if(err){ + return done(err); + } + stream.on("finish", function(){ + order.client.destroy(); + fs.readFile(tmpFile, "utf8", function(err, text){ + if(err){ + done(err); + } + text.should.equal("12345"); + done(); + }); + }); + }); + }); + it("should fail via callback if no auth method available", function(done){ + fs.writeFileSync(tmpFile, ""); + var order = new PortIn(); + order.id = 1; + order.client = new lib.Client("FakeAccountId", null, null, null, null); + order.getFile("file.txt", function(err){ + if(err){ + err.message.should.equal("No authentication method available"); + return done(); + } + done(new Error("An error was expected")); + }); + }); }); describe("#createFile", function(){ var order, tmpFile = path.join(os.tmpdir(), "dest.txt"); @@ -450,6 +486,40 @@ describe("PortIn", function(){ done(); }); }); + it("should upload file to the server with OAuth client credentials only", function(done){ + nock.cleanAll(); + helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600}); + helper.nock().matchHeader("authorization", "Bearer fake_token").post("/accounts/FakeAccountId/portins/1/loas", "12345").reply(200, helper.xml.fileCreated, {"Content-Type": "application/xml"}); + order.client = helper.createOAuthClient(); + order.createFile(new Buffer.from("12345", "utf8"), "text/plain", function(err, fileName){ + order.client.destroy(); + if(err){ + return done(err); + } + fileName.should.equal("test.txt"); + done(); + }); + }); + it("should fail on invalid data", function(done){ + order.createFile(12345, "text/plain", function(err){ + if(err){ + err.message.should.equal("Invalid data to send"); + return done(); + } + done(new Error("An error was expected")); + }); + }); + it("should fail on network error", function(done){ + nock.cleanAll(); + helper.nock().post("/accounts/FakeAccountId/portins/1/loas").replyWithError({code: "ECONNRESET", message: "socket hang up"}); + order.createFile(new Buffer.from("12345", "utf8"), "text/plain", function(err){ + if(err){ + err.code.should.equal("ECONNRESET"); + return done(); + } + done(new Error("An error was expected")); + }); + }); }); describe("#updateFile", function(){ var order, tmpFile = path.join(os.tmpdir(), "dest.txt"); diff --git a/test/tnOption.js b/test/tnOption.js index 89ded82..6b5d62f 100644 --- a/test/tnOption.js +++ b/test/tnOption.js @@ -221,5 +221,28 @@ describe("TnOption", function(){ done(new Error("An error is expected")); }); }); + + it("should create with OAuth client credentials only", function(done){ + const userInput = { + customerOrderId: 'myOrderId', + tnOptionGroups: [ + { + sms: 'on', + telephoneNumbers: ['2018551020'] + } + ] + } + const client = helper.createOAuthClient(); + helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600}); + helper.nock().matchHeader("authorization", "Bearer fake_token").post("/accounts/FakeAccountId/tnoptions").reply(201, helper.xml.tnOptionOrderResponse, {"Location": "/accounts/FakeAccountId/tnoptions/1"}); + TnOption.create(client, userInput, function(err, item){ + client.destroy(); + if(err){ + return done(err); + } + item.createdByUser.should.equal('user'); + done(); + }); + }); }); });