initial commit
[squeep-indie-auther] / src / logger / data-sanitizers.js
diff --git a/src/logger/data-sanitizers.js b/src/logger/data-sanitizers.js
new file mode 100644 (file)
index 0000000..450842e
--- /dev/null
@@ -0,0 +1,116 @@
+'use strict';
+
+/**
+ * Scrub credential from POST login body data.
+ * @param {Object} data
+ * @param {Boolean} sanitize
+ * @returns {Boolean}
+ */
+function sanitizePostCredential(data, sanitize = true) {
+  let unclean = false;
+
+  const credentialLength = data?.ctx?.parsedBody?.credential?.length;
+  if (credentialLength) {
+    unclean = true;
+  }
+  if (unclean && sanitize) {
+    data.ctx.parsedBody.credential = '*'.repeat(credentialLength);
+  }
+
+  return unclean;
+}
+
+
+/**
+ * Reduce logged data about scopes from profilesScopes.
+ * For all referenced scopes, only include profiles list.
+ * Remove scopes without profile references from scopeIndex.
+ * @param {Object} data
+ * @param {Boolean} sanitize
+ */
+function reduceScopeVerbosity(data, sanitize = true) {
+  let unclean = false;
+
+  const {
+    scopesEntries: ctxScopesEntries,
+    profilesEntries: ctxProfilesEntries,
+    needsSanitize: ctxNeedsSanitize,
+  } = _scopesFrom(data?.ctx?.profilesScopes);
+
+  const {
+    scopesEntries: sessionScopesEntries,
+    profilesEntries: sessionProfilesEntries,
+    needsSanitize: sessionNeedsSanitize,
+  } = _scopesFrom(data?.ctx?.session);
+
+  if (ctxNeedsSanitize || sessionNeedsSanitize) {
+    unclean = true;
+  }
+  if (unclean && sanitize) {
+    if (ctxNeedsSanitize) {
+      Object.assign(data.ctx.profilesScopes, _sanitizeProfilesScopes(ctxScopesEntries, ctxProfilesEntries));
+    }
+    if (sessionNeedsSanitize) {
+      Object.assign(data.ctx.session, _sanitizeProfilesScopes(sessionScopesEntries, sessionProfilesEntries));
+    }
+  }
+
+  return unclean;
+}
+
+
+/**
+ * Return any scope entries on an object, and whether sanitization is needed.
+ * @param {Object=} obj
+ * @returns {Object}
+ */
+const _scopesFrom = (obj) => {
+  const scopesEntries = Object.entries(obj?.scopeIndex || {});
+  const profilesEntries = Object.entries(obj?.profileScopes || {});
+  const needsSanitize = scopesEntries.length || profilesEntries.length;
+  return {
+    scopesEntries,
+    profilesEntries,
+    needsSanitize,
+  };
+};
+
+
+/**
+ * @typedef {[String, Object]} ScopeEntry
+ */
+/**
+ * Return new list of entries with scrubbed scopeDetails.
+ * @param {ScopeEntry[]} entries
+ * @returns {ScopeEntry[]}
+ */
+const _scopeEntriesScrubber = (entries) => entries.map(([scopeName, scopeDetails]) => ([scopeName, { profiles: scopeDetails.profiles }]));
+
+
+/**
+ * Create a new profilesScopes type object with scrubbed scope details.
+ * @param {ScopeEntry[]} scopesEntries
+ * @param {ScopeEntry[]} profilesEntries
+ * @returns {Object}
+ */
+const _sanitizeProfilesScopes = (scopesEntries, profilesEntries) => {
+  const referencedScopesEntries = scopesEntries.filter(([_scopeName, scopeDetails]) => scopeDetails?.profiles?.length); // eslint-disable-line no-unused-vars
+  const scrubbedScopesEntries = _scopeEntriesScrubber(referencedScopesEntries);
+
+  const scrubbedProfilesEntries = profilesEntries.map(([profileName, profileScopes]) => {
+    const profileScopeEntries = Object.entries(profileScopes);
+    const scrubbedProfileScopeEntries = _scopeEntriesScrubber(profileScopeEntries);
+    const scrubbedProfileScopes = Object.fromEntries(scrubbedProfileScopeEntries);
+    return [profileName, scrubbedProfileScopes];
+  });
+
+  return {
+    scopeIndex: Object.fromEntries(scrubbedScopesEntries),
+    profileScopes: Object.fromEntries(scrubbedProfilesEntries),
+  };
+};
+
+module.exports = {
+  sanitizePostCredential,
+  reduceScopeVerbosity,
+};
\ No newline at end of file