@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
96 lines (94 loc) • 3.67 kB
JavaScript
import { isObject } from "@directus/utils";
//#region src/utils/redact-object.ts
/**
* Redact values in an object.
*
* @param input Input object in which values should be redacted.
* @param redact The key paths at which and values itself which should be redacted.
* @param redact.keys Nested array of key paths at which values should be redacted. (Supports `*` for shallow matching, `**` for deep matching.)
* @param redact.values Value names and the corresponding values that should be redacted.
* @param replacement Replacement function with which the values are redacted.
* @returns Redacted object.
*/
function redactObject(input, redact, replacement) {
const wildcardChars = ["*", "**"];
const clone = JSON.parse(JSON.stringify(input, getReplacer(replacement, redact.values)));
if (redact.keys) traverse(clone, redact.keys);
return clone;
function traverse(object, checkKeyPaths) {
if (checkKeyPaths.length === 0) return;
const REDACTED_TEXT$1 = replacement();
const globalCheckPaths = [];
for (const key of Object.keys(object)) {
const localCheckPaths = [];
for (const [index, path] of [...checkKeyPaths].entries()) {
const [current, ...remaining] = path;
const escapedKey = wildcardChars.includes(key) ? `\\${key}` : key;
switch (current) {
case escapedKey:
if (remaining.length > 0) localCheckPaths.push(remaining);
else {
object[key] = REDACTED_TEXT$1;
checkKeyPaths.splice(index, 1);
}
break;
case "*":
if (remaining.length > 0) {
globalCheckPaths.push(remaining);
checkKeyPaths.splice(index, 1);
} else object[key] = REDACTED_TEXT$1;
break;
case "**":
if (remaining.length > 0) {
const [next, ...nextRemaining] = remaining;
if (next === escapedKey) if (nextRemaining.length === 0) object[key] = REDACTED_TEXT$1;
else localCheckPaths.push(nextRemaining);
else if (next !== void 0 && wildcardChars.includes(next)) localCheckPaths.push(remaining);
else localCheckPaths.push(path);
} else object[key] = REDACTED_TEXT$1;
break;
}
}
const value = object[key];
if (isObject(value)) traverse(value, [...globalCheckPaths, ...localCheckPaths]);
}
}
}
/**
* Replace values and extract Error objects for use with JSON.stringify()
*/
function getReplacer(replacement, values) {
const filteredValues = values ? Object.entries(values).filter(([_k, v]) => typeof v === "string" && v.length > 0) : [];
const replacer = (seen) => {
return function(_key, value) {
if (value instanceof Error) return {
name: value.name,
message: value.message,
stack: value.stack,
cause: value.cause
};
if (value !== null && typeof value === "object") {
if (seen.has(value)) return "[Circular]";
seen.add(value);
const newValue = Array.isArray(value) ? [] : {};
for (const [key2, value2] of Object.entries(value)) if (typeof value2 === "string") newValue[key2] = value2;
else newValue[key2] = replacer(seen)(key2, value2);
seen.delete(value);
return newValue;
}
if (!values || filteredValues.length === 0 || typeof value !== "string") return value;
let finalValue = value;
for (const [redactKey, valueToRedact] of filteredValues) if (finalValue.includes(valueToRedact)) {
const regexp = new RegExp(escapeRegexString(valueToRedact), "g");
finalValue = finalValue.replace(regexp, replacement(redactKey));
}
return finalValue;
};
};
return replacer(/* @__PURE__ */ new WeakSet());
}
function escapeRegexString(string) {
return string.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
}
//#endregion
export { getReplacer, redactObject };