UNPKG

@tableau/taco-toolkit

Version:
253 lines 17.5 kB
"use strict"; 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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