@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
230 lines • 17.2 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logForwardedRequest = exports.isValidForwardedRequest = exports.handleRequestForward = void 0;
var http_status_codes_1 = require("http-status-codes");
var error_codes_1 = require("../../../../shared/errors/error-codes");
var error_source_1 = require("../../../../shared/errors/error-source");
var status_code_1 = require("../../../../shared/errors/status-code");
var permission_check_1 = require("../../../../shared/utils/permission-check");
var eps_logger_1 = require("../../../logging/eps-logger");
var request_utils_1 = require("../../../utils/request-utils");
function handleRequestForward(networkAdapter, forwardedRequest, apiPermission) {
return __awaiter(this, void 0, void 0, function () {
var method, url, message, fetchOptions, response, status, ajaxResponse;
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
method = forwardedRequest.method, url = forwardedRequest.url;
if (!(0, permission_check_1.checkApiPermission)(apiPermission, url, method)) {
message = "This app does not have permission to make ".concat(method, " request to ").concat(url);
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.PermissionDenied, error_source_1.ErrorSources.Configuration, error_codes_1.ErrorCodes.XQJT9XXC, message);
return [2 /*return*/, {
state: 'error',
statusCode: http_status_codes_1.StatusCodes.UNAUTHORIZED,
error: { message: message },
}];
}
fetchOptions = getFetchOption(forwardedRequest);
return [4 /*yield*/, networkAdapter.fetch(url, fetchOptions)];
case 1:
response = _c.sent();
status = response.status;
if ((0, request_utils_1.isRedirectCode)(status)) {
throw new Error("Forwarded request currently does not support redirect. Status: ".concat(response.status, ", StatusText: ").concat(response.statusText));
}
if (!!response.ok) return [3 /*break*/, 3];
_a = {
state: 'error',
statusCode: status
};
_b = {
statusText: response.statusText
};
return [4 /*yield*/, response.text()];
case 2: return [2 /*return*/, (_a.error = (_b.message = _c.sent(),
_b),
_a)];
case 3: return [4 /*yield*/, composeAjaxResponse(response)];
case 4:
ajaxResponse = _c.sent();
return [2 /*return*/, {
state: 'success',
statusCode: response.status,
response: ajaxResponse,
}];
}
});
});
}
exports.handleRequestForward = handleRequestForward;
function getFetchOption(forwardedRequest) {
var method = forwardedRequest.method, headers = forwardedRequest.headers;
if (method === 'GET') {
return { method: 'GET', headers: headers };
}
var body = forwardedRequest.body;
return (0, request_utils_1.composeFetchOption)(method, headers, body);
}
/**
* Compose AjaxResponse object based on node-fetch response.
* Note: this function is designated to forwarded request, so do not use it anywhere else.
*
* Since the request forwarding adds an abstraction layer on top of the original response of
* the forwarded request, the content needs to be decoded before sent to the client. (The browser
* won't decode it due to the abstraction layer)
*/
function composeAjaxResponse(fetchResponse) {
return __awaiter(this, void 0, void 0, function () {
var contentTypeHeader, contentType, commonProps, _a, text, text, body, _b;
var _c, _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
contentTypeHeader = fetchResponse.headers.get('Content-Type');
contentType = contentTypeHeader ? (0, request_utils_1.getContentType)(contentTypeHeader) : 'text/plain';
commonProps = {
headers: Object.fromEntries(fetchResponse.headers),
ok: fetchResponse.ok,
status: fetchResponse.status,
type: contentType,
};
if (contentType.startsWith('multipart/')) {
throw new Error('Forwarded request does not support multipart content');
}
if (!(contentType.endsWith('/xml') || contentType.startsWith('text/'))) return [3 /*break*/, 2];
_a = [__assign({}, commonProps)];
_c = { body: null };
return [4 /*yield*/, fetchResponse.text()];
case 1: return [2 /*return*/, __assign.apply(void 0, _a.concat([(_c.text = _e.sent(), _c)]))];
case 2:
if (!(contentType === 'application/x-www-form-urlencoded')) return [3 /*break*/, 4];
return [4 /*yield*/, fetchResponse.text()];
case 3:
text = _e.sent();
return [2 /*return*/, __assign(__assign({}, commonProps), { body: (0, request_utils_1.parseFormData)(text), text: text })];
case 4:
if (!(contentType === 'application/json')) return [3 /*break*/, 6];
return [4 /*yield*/, fetchResponse.text()];
case 5:
text = _e.sent();
body = null;
try {
// Note: it is observed that fetchResponse.clone().json() gets stuck in some cases.
// Parse the body syncly with JSON.parse for now.
body = JSON.parse(text);
}
catch (e) {
// no-op, let body be null
}
return [2 /*return*/, __assign(__assign({}, commonProps), { body: body, text: text })];
case 6:
_b = [__assign({}, commonProps)];
_d = { text: '' };
return [4 /*yield*/, fetchResponse.arrayBuffer()];
case 7:
// by default, treats the content as binary
return [2 /*return*/, __assign.apply(void 0, _b.concat([(_d.body = _e.sent(), _d)]))];
}
});
});
}
function isValidForwardedRequest(value) {
if (!value || typeof value !== 'object') {
return false;
}
var forwardRequest = value;
var url = forwardRequest.url, method = forwardRequest.method, headers = forwardRequest.headers;
if (method !== 'GET' && method !== 'POST') {
return false;
}
if (headers !== undefined && typeof headers !== 'object') {
return false;
}
return typeof url === 'string';
}
exports.isValidForwardedRequest = isValidForwardedRequest;
/**
* Log forwarded request infos.
*
* Forwarded requests are used by connectors to talk to external resources, so
* the requests may contain user sensitive infos and credentials. The function
* makes sure we only log insensitive infos for debugging purpose.
*/
function logForwardedRequest(forwardedRequest) {
var method = forwardedRequest.method, url = forwardedRequest.url, headers = forwardedRequest.headers;
var logEntry = {
type: 'forwarded-request',
method: method,
endpoint: getEndpointFromUrl(url),
};
if (headers) {
// only log header names, since values may have creds
logEntry['headers'] = Object.keys(headers);
}
eps_logger_1.Logger.debug(logEntry);
}
exports.logForwardedRequest = logForwardedRequest;
/**
* Get the hostname and path of a URL for logging.
* It strips out query strings which may contains user sensitive data.
*/
function getEndpointFromUrl(urlString) {
try {
var url = new URL(urlString);
url.search = '';
return url.toString();
}
catch (e) {
// no-op, return the message for logging
return 'Invalid URL string';
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yd2FyZC1yZXF1ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2Vwcy9tYWluL2FwaS9lcHMtaGFuZGxlcnMvZm9yd2FyZC1yZXF1ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsdURBQStDO0FBSS9DLHFFQUFrRTtBQUNsRSx1RUFBcUU7QUFDckUscUVBQWtFO0FBR2xFLDhFQUE4RTtBQUM5RSwwREFBb0Q7QUFFcEQsOERBQWdIO0FBZ0JoSCxTQUFzQixvQkFBb0IsQ0FDeEMsY0FBOEIsRUFDOUIsZ0JBQWtDLEVBQ2xDLGFBQTRCOzs7Ozs7O29CQUVwQixNQUFNLEdBQVUsZ0JBQWdCLE9BQTFCLEVBQUUsR0FBRyxHQUFLLGdCQUFnQixJQUFyQixDQUFxQjtvQkFDeEMsSUFBSSxDQUFDLElBQUEscUNBQWtCLEVBQUMsYUFBYSxFQUFFLEdBQUcsRUFBRSxNQUFNLENBQUMsRUFBRTt3QkFFN0MsT0FBTyxHQUFHLG9EQUE2QyxNQUFNLHlCQUFlLEdBQUcsQ0FBRSxDQUFBO3dCQUN2RixtQkFBTSxDQUFDLFdBQVcsQ0FBQyx3QkFBVSxDQUFDLGdCQUFnQixFQUFFLDJCQUFZLENBQUMsYUFBYSxFQUFFLHdCQUFVLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFBO3dCQUV6RyxzQkFBTztnQ0FDTCxLQUFLLEVBQUUsT0FBTztnQ0FDZCxVQUFVLEVBQUUsK0JBQVcsQ0FBQyxZQUFZO2dDQUNwQyxLQUFLLEVBQUUsRUFBRSxPQUFPLFNBQUEsRUFBRTs2QkFDbkIsRUFBQTtxQkFDRjtvQkFFSyxZQUFZLEdBQUcsY0FBYyxDQUFDLGdCQUFnQixDQUFDLENBQUE7b0JBQ3BDLHFCQUFNLGNBQWMsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLFlBQVksQ0FBQyxFQUFBOztvQkFBeEQsUUFBUSxHQUFHLFNBQTZDO29C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