obscure authorization header value when logging
[squeep-api-dingus] / lib / common.js
index 284427a7bd769742eafda2838b66275951a25de3..21070b8782918f267bf32e3adf96537df08e615a 100644 (file)
@@ -158,19 +158,51 @@ const pick = (obj, props) => {
 
 /**
  * Return a subset of a request object, suitable for logging.
+ * Obscures sensitive header values.
  * @param {http.ClientRequest} req 
  */
 const requestLogData = (req) => {
-  return pick(req, [
+  const data = pick(req, [
     'method',
     'url',
     'httpVersion',
     'headers',
     'trailers',
   ]);
+  scrubHeaderObject(data);
+  return data;
 };
 
 
+/**
+ * Remove sensitive header data.
+ * @param {Object} data
+ * @param {Object} data.headers
+ */
+const scrubHeaderObject = (data) => {
+  if (data && data.headers && 'authorization' in data.headers) {
+    data.headers = Object.assign({}, data.headers, {
+      authorization: obscureAuthorizationHeader(data.headers['authorization']),
+    });
+  }
+}
+
+
+/**
+ * Hide sensitive part of an Authorization header.
+ * @param {String} authHeader
+ * @returns {String}
+ */
+const obscureAuthorizationHeader = (authHeader) => {
+  if (!authHeader) {
+    return authHeader;
+  }
+  const space = authHeader.indexOf(' ');
+  // This blurs entire string if no space found, because -1.
+  return authHeader.slice(0, space + 1) + '*'.repeat(authHeader.length - (space + 1));
+}
+
+
 /**
  * Return a subset of a response object, suitable for logging.
  * @param {http.ServerResponse} res 
@@ -227,6 +259,10 @@ const nullLogger = {
   debug: nop,
 };
 
+/**
+ * Populates any absent logger levels.
+ * @param {Object} logger
+ */
 const ensureLoggerLevels = (logger = {}) => {
   for (const level in nullLogger) {
     if (! (level in logger)) {
@@ -236,22 +272,45 @@ const ensureLoggerLevels = (logger = {}) => {
   return logger;
 };
 
+/**
+ * Merges folded header lines
+ * @param {String[]} lines
+ */
+const unfoldHeaderLines = (lines) => {
+  const foldedLineRE = /^(\t| +)(.*)$/;
+  if (lines) {
+    lines.reduceRight((_, line, idx) => {
+      const result = foldedLineRE.exec(line);
+      if (result && idx) {
+        const prevIdx = idx - 1;
+        const mergedLine = `${lines[prevIdx]} ${result[2]}`;
+        lines.splice(prevIdx, 2, mergedLine);
+        return mergedLine;
+      }
+    }, null);
+  }
+  return lines;
+};
+
 module.exports = {
+  ensureLoggerLevels,
   fileScope,
   generateETag,
   get,
   handlerLogData,
-  isClientCached,
   httpStatusCodeClass,
+  isClientCached,
   mergeDeep,
   mergeEnum,
   nop,
   nullLogger,
-  ensureLoggerLevels,
+  obscureAuthorizationHeader,
   pick,
   requestId,
   requestLogData,
   responseLogData,
+  scrubHeaderObject,
   setOptions,
   splitFirst,
+  unfoldHeaderLines,
 };