@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
253 lines • 17.5 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 __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLocalizationHeader = exports.hasLocalizationHeader = exports.notFoundHandler = exports.errorHandler = exports.sendInternalServerError = exports.sendNotFound = exports.sendBadRequest = exports.getOptionalStringHeader = exports.getRequiredStringHeader = exports.getOptionalTacoPathHeader = exports.getRequiredTacoPathHeader = exports.getConnectionData = exports.getOptionalConnectionData = exports.getSingleOptionalHeader = exports.getSingleRequiredHeader = exports.maskURL = exports.getRequestLogger = void 0;
var http_status_codes_1 = require("http-status-codes");
var eps_log_serializer_1 = require("../logging/eps-log-serializer");
var eps_logger_1 = require("../logging/eps-logger");
var extract_1 = require("../main/api/eps-handlers/extract");
var LOG_TPYE = 'received request';
var TACO_PATH_HEADER = 'plugin-path';
function getRequestLogger(listeningServer, replacer) {
return function (req, _res, next) {
var method = req.method, url = req.url, headers = req.headers;
if (replacer) {
var info = replacer(req);
// if replace returns undefined, use the default info
if (info !== undefined) {
eps_logger_1.Logger.info({
listeningServer: listeningServer,
method: method,
type: LOG_TPYE,
request: info,
});
next();
return;
}
}
eps_logger_1.Logger.info({
listeningServer: listeningServer,
method: method,
type: LOG_TPYE,
url: maskURL(url),
headers: maskHeaders(headers),
});
next();
};
}
exports.getRequestLogger = getRequestLogger;
var SECRETS_PARAM = 'secrets';
/**
* Mask secret query params in the url for logging
*/
function maskURL(url) {
if (!url.includes(SECRETS_PARAM)) {
return url;
}
var params = new URLSearchParams(url);
if (params.get('secrets')) {
params.set('secrets', eps_log_serializer_1.MASK_VALUE);
}
return decodeURIComponent(params.toString());
}
exports.maskURL = maskURL;
/**
* Mask secrets in headers for logging.
* If you are adding new fields to this function, check the eps-log-serializer first.
* eps-log-serializer will mask the entire sensitive field value, while this function
* allows us to add logic to partially mask some infos.
*
* It returns the original object if there is sensitive fields exist, while
* it returns a shallow copy if any value is masked.
*/
function maskHeaders(headers) {
var referer = headers.referer, otherHeaders = __rest(headers, ["referer"]);
if (!referer || !referer.includes(SECRETS_PARAM)) {
return headers;
}
// if it has secrets, create a copy for masking fields
var newHeaders = __assign({}, otherHeaders);
if (referer) {
newHeaders.referer = maskURL(referer);
}
return newHeaders;
}
/**
* Ensures that there is exactly one of the header and returns its value.
*/
var getSingleRequiredHeader = function (name, req) {
var val = (0, exports.getSingleOptionalHeader)(name, req);
if (val === undefined) {
var error = new Error("'".concat(name, "' header is undefined"));
eps_logger_1.Logger.error(error);
throw error;
}
return val;
};
exports.getSingleRequiredHeader = getSingleRequiredHeader;
/**
* Ensures that there is at most one of the header and returns its value or undefined.
* Throws if there are multiple entries.
*/
var getSingleOptionalHeader = function (name, req) {
var val = req.header(name);
if (val !== undefined) {
if (val.indexOf(',') !== -1) {
// Count even entries matching name. Evens are the header names; odds are the header values.
var count = req.rawHeaders.filter(function (v, i) { return i % 2 === 0 && v === name; }).length;
if (count !== 1) {
// Duplicates were combined like plugin-path: "File1, File1", converted to array then joined to a string.
var error = new Error("'".concat(name, "' header was provided more than once: got ").concat(count, " instances."));
eps_logger_1.Logger.error(error);
throw error;
}
}
}
return val;
};
exports.getSingleOptionalHeader = getSingleOptionalHeader;
function getOptionalConnectionData(req) {
var body = req.body;
if (!body ||
// API client (e.g. superagent) may add empty object by default for POST
(typeof body === 'object' && Object.keys(body).length === 0)) {
return;
}
return getConnectionData(req);
}
exports.getOptionalConnectionData = getOptionalConnectionData;
function getConnectionData(req) {
var body = req.body;
(0, extract_1.validateConnectionData)(body);
return body;
}
exports.getConnectionData = getConnectionData;
function getRequiredTacoPathHeader(req) {
var value = getOptionalTacoPathHeader(req);
if (value === undefined) {
throw new Error("Header '".concat(TACO_PATH_HEADER, "' must be present."));
}
return value;
}
exports.getRequiredTacoPathHeader = getRequiredTacoPathHeader;
function getOptionalTacoPathHeader(req) {
var value = getOptionalStringHeader(req, TACO_PATH_HEADER, {
allowEmpty: false,
});
if (!value) {
return value;
}
return decodeURIComponent(value);
}
exports.getOptionalTacoPathHeader = getOptionalTacoPathHeader;
/**
* Retrieve a string value from a required request header.
* It validates the value according to the options, and throws an Error when validation fails.
*/
function getRequiredStringHeader(req, headerName, options) {
var value = getOptionalStringHeader(req, headerName, options);
if (value === undefined) {
throw new Error("Header '".concat(headerName, "' must be present."));
}
return value;
}
exports.getRequiredStringHeader = getRequiredStringHeader;
/**
* Retrieve a string value from a required request header.
* It validates the value according to the options, and throws an Error when validation fails.
*/
function getOptionalStringHeader(req, headerName, options) {
var value = req.headers[headerName];
if (value === undefined) {
return value;
}
if (typeof value !== 'string') {
throw new Error("Header '".concat(headerName, "' must be a string, but receieved ").concat(value, "."));
}
var allowEmpty = options.allowEmpty;
if (allowEmpty === false && value.length === 0) {
throw new Error("Header '".concat(headerName, "' must not be a empty string."));
}
return value;
}
exports.getOptionalStringHeader = getOptionalStringHeader;
function sendBadRequest(res, message) {
eps_logger_1.Logger.debug(message);
if (message instanceof Error) {
message = message.toString();
}
return res.status(http_status_codes_1.StatusCodes.BAD_REQUEST).json({ error: { message: message } });
}
exports.sendBadRequest = sendBadRequest;
function sendNotFound(res, message) {
eps_logger_1.Logger.debug(message);
return res.status(http_status_codes_1.StatusCodes.NOT_FOUND).send({ message: message });
}
exports.sendNotFound = sendNotFound;
function sendInternalServerError(res, error) {
eps_logger_1.Logger.error(error);
if (error instanceof Error) {
error = error.toString();
}
return res.status(http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR).json({
error: {
message: "Internal Server Error: ".concat(error),
},
});
}
exports.sendInternalServerError = sendInternalServerError;
function errorHandler(err, _req, res) {
sendInternalServerError(res, err);
}
exports.errorHandler = errorHandler;
function notFoundHandler(_req, res) {
res.status(http_status_codes_1.StatusCodes.NOT_FOUND).send('Not Found');
}
exports.notFoundHandler = notFoundHandler;
/**
* Check if request has certain localization header value.
*/
function hasLocalizationHeader(req) {
return 'language' in req.headers || 'locale' in req.headers;
}
exports.hasLocalizationHeader = hasLocalizationHeader;
/**
* Get i18n header value from the request headers. If the header is absent, it returns the given default value.
*/
function getLocalizationHeader(req, headerName, defaultValue) {
if (!req.headers) {
return defaultValue;
}
var value = req.headers[headerName];
if (!value) {
return defaultValue;
}
if (typeof value !== 'string') {
eps_logger_1.Logger.error("[i18n] localization context header unexpected for ".concat(headerName, ": ").concat(value));
return defaultValue;
}
return value;
}
exports.getLocalizationHeader = getLocalizationHeader;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLXV0aWxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2Vwcy91dGlscy9hcGktdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUNBLHVEQUErQztBQUkvQyxvRUFBMEQ7QUFDMUQsb0RBQThDO0FBQzlDLDREQUF5RTtBQUl6RSxJQUFNLFFBQVEsR0FBRyxrQkFBa0IsQ0FBQTtBQUVuQyxJQUFNLGdCQUFnQixHQUFHLGFBQWEsQ0FBQTtBQUV0QyxTQUFnQixnQkFBZ0IsQ0FBQyxlQUF1QixFQUFFLFFBQWdDO0lBQ3hGLE9BQU8sVUFBQyxHQUFZLEVBQUUsSUFBYyxFQUFFLElBQWtCO1FBQzlDLElBQUEsTUFBTSxHQUFtQixHQUFHLE9BQXRCLEVBQUUsR0FBRyxHQUFjLEdBQUcsSUFBakIsRUFBRSxPQUFPLEdBQUssR0FBRyxRQUFSLENBQVE7UUFFcEMsSUFBSSxRQUFRLEVBQUU7WUFDWixJQUFNLElBQUksR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUE7WUFDMUIscURBQXFEO1lBQ3JELElBQUksSUFBSSxLQUFLLFNBQVMsRUFBRTtnQkFDdEIsbUJBQU0sQ0FBQyxJQUFJLENBQUM7b0JBQ1YsZUFBZSxpQkFBQTtvQkFDZixNQUFNLFFBQUE7b0JBQ04sSUFBSSxFQUFFLFFBQVE7b0JBQ2QsT0FBTyxFQUFFLElBQUk7aUJBQ2QsQ0FBQyxDQUFBO2dCQUNGLElBQUksRUFBRSxDQUFBO2dCQUNOLE9BQU07YUFDUDtTQUNGO1FBRUQsbUJBQU0sQ0FBQyxJQUFJLENBQUM7WUFDVixlQUFlLGlCQUFBO1lBQ2YsTUFBTSxRQUFBO1lBQ04sSUFBSSxFQUFFLFFBQVE7WUFDZCxHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQztZQUNqQixPQUFPLEVBQUUsV0FBVyxDQUFDLE9BQU8sQ0FBQztTQUM5QixDQUFDLENBQUE7UUFFRixJQUFJLEVBQUUsQ0FBQTtJQUNSLENB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