@atlassian/atlassian-connect-express
Version:
Library for building Atlassian Add-ons on top of Express
120 lines (108 loc) • 3.64 kB
JavaScript
const request = require("request");
const _ = require("lodash");
const URI = require("urijs");
const utils = require("./utils");
class ForgeHostRequest {
constructor(addon, apiBaseUrl, token) {
if (!addon) {
throw new Error("addon must be defined");
}
if (!token || typeof token !== "string") {
throw new Error(
"Forge token is not available. Double check your manifest and ensure this route is protected by authenticateForge()."
);
}
if (!apiBaseUrl || typeof apiBaseUrl !== "string") {
throw new Error(
"Forge base URL is missing. Ensure this route is protected by authenticateForge()."
);
}
this.addon = addon;
this.token = token;
this.apiBaseUrl = apiBaseUrl;
}
static _crossProtocolRedirectGuard(response) {
if (!response.headers["location"]) {
return true;
}
if (
!response.request ||
!response.request.uri ||
!response.request.uri.protocol
) {
return true;
}
const locationUri = new URI(response.headers["location"]);
const requestUri = new URI(response.request.uri.href);
if (!locationUri || !locationUri.protocol || !locationUri.protocol()) {
return true;
}
return locationUri.protocol() === requestUri.protocol();
}
_makeVerb(method) {
const safeRequestDefaults = {
followRedirect: ForgeHostRequest._crossProtocolRedirectGuard
};
return function (options, callback) {
const self = this;
const augmentHeaders = function (headers) {
const appId = self.addon.config.appId();
headers["User-Agent"] = self.addon.config.userAgent(
`forgeAppId=${appId}`
);
headers.authorization = `Bearer ${self.token}`;
};
let args;
try {
args = utils.modifyArgs(
self.addon,
options || {},
augmentHeaders,
callback,
self.apiBaseUrl
);
const opts = args[0];
/*For GET method, drop request body: To be compliant with
https://developer.atlassian.com/cloud/jira/platform/changelog/#CHANGE-2328
and https://developer.atlassian.com/cloud/confluence/changelog/#CHANGE-2328 */
const multipartFormData =
method === "get" ? null : opts.multipartFormData;
delete opts.multipartFormData;
if (method === "get") {
delete opts.body;
delete opts.formData;
// If options.json is true and there is no other body, it only adds the "accept: application/json" header. so we keep it.
// When options.json is an object or any type other than boolean, it will be used as the request body, so we need to remove it.
if (opts.json !== true) {
delete opts.json;
}
}
const req = request
.defaults(safeRequestDefaults)
[method].apply(null, args);
if (multipartFormData) {
const form = req.form();
_.forOwn(multipartFormData, (value, key) => {
if (Array.isArray(value)) {
form.append.apply(form, [key].concat(value));
} else {
form.append.apply(form, [key, value]);
}
});
}
return req;
} catch (err) {
self.addon.logger.error(err);
if (callback) {
return callback(err);
}
throw err;
}
};
}
}
["get", "post", "put", "del", "head", "patch"].forEach(method => {
ForgeHostRequest.prototype[method] =
ForgeHostRequest.prototype._makeVerb(method);
});
module.exports = ForgeHostRequest;