directus

Форк
0
/
redact-object.ts 
160 строк · 4.0 Кб
1
import type { UnknownObject } from '@directus/types';
2
import { isObject } from '@directus/utils';
3

4
type Keys = string[][];
5
type Values = Record<string, any>;
6
type Replacement = (key?: string) => string;
7

8
/**
9
 * Redact values in an object.
10
 *
11
 * @param input Input object in which values should be redacted.
12
 * @param redact The key paths at which and values itself which should be redacted.
13
 * @param redact.keys Nested array of key paths at which values should be redacted. (Supports `*` for shallow matching, `**` for deep matching.)
14
 * @param redact.values Value names and the corresponding values that should be redacted.
15
 * @param replacement Replacement function with which the values are redacted.
16
 * @returns Redacted object.
17
 */
18
export function redactObject(
19
	input: UnknownObject,
20
	redact: {
21
		keys?: Keys;
22
		values?: Values;
23
	},
24
	replacement: Replacement,
25
): UnknownObject {
26
	const wildcardChars = ['*', '**'];
27

28
	const clone = JSON.parse(JSON.stringify(input, getReplacer(replacement, redact.values)));
29

30
	if (redact.keys) {
31
		traverse(clone, redact.keys);
32
	}
33

34
	return clone;
35

36
	function traverse(object: UnknownObject, checkKeyPaths: Keys): void {
37
		if (checkKeyPaths.length === 0) {
38
			return;
39
		}
40

41
		const REDACTED_TEXT = replacement();
42
		const globalCheckPaths = [];
43

44
		for (const key of Object.keys(object)) {
45
			const localCheckPaths = [];
46

47
			for (const [index, path] of [...checkKeyPaths].entries()) {
48
				const [current, ...remaining] = path;
49

50
				const escapedKey = wildcardChars.includes(key) ? `\\${key}` : key;
51

52
				switch (current) {
53
					case escapedKey:
54
						if (remaining.length > 0) {
55
							localCheckPaths.push(remaining);
56
						} else {
57
							object[key] = REDACTED_TEXT;
58
							checkKeyPaths.splice(index, 1);
59
						}
60

61
						break;
62
					case '*':
63
						if (remaining.length > 0) {
64
							globalCheckPaths.push(remaining);
65
							checkKeyPaths.splice(index, 1);
66
						} else {
67
							object[key] = REDACTED_TEXT;
68
						}
69

70
						break;
71
					case '**':
72
						if (remaining.length > 0) {
73
							const [next, ...nextRemaining] = remaining;
74

75
							if (next === escapedKey) {
76
								if (nextRemaining.length === 0) {
77
									object[key] = REDACTED_TEXT;
78
								} else {
79
									localCheckPaths.push(nextRemaining);
80
								}
81
							} else if (next !== undefined && wildcardChars.includes(next)) {
82
								localCheckPaths.push(remaining);
83
							} else {
84
								localCheckPaths.push(path);
85
							}
86
						} else {
87
							object[key] = REDACTED_TEXT;
88
						}
89

90
						break;
91
				}
92
			}
93

94
			const value = object[key];
95

96
			if (isObject(value)) {
97
				traverse(value, [...globalCheckPaths, ...localCheckPaths]);
98
			}
99
		}
100
	}
101
}
102

103
/**
104
 * Replace values and extract Error objects for use with JSON.stringify()
105
 */
106
export function getReplacer(replacement: Replacement, values?: Values) {
107
	const filteredValues = values
108
		? Object.entries(values).filter(([_k, v]) => typeof v === 'string' && v.length > 0)
109
		: [];
110

111
	const replacer = (seen: WeakSet<object>) => {
112
		return function (_key: string, value: unknown) {
113
			if (value instanceof Error) {
114
				return {
115
					name: value.name,
116
					message: value.message,
117
					stack: value.stack,
118
					cause: value.cause,
119
				};
120
			}
121

122
			if (value !== null && typeof value === 'object') {
123
				if (seen.has(value)) {
124
					return '[Circular]';
125
				}
126

127
				seen.add(value);
128

129
				const newValue: any = Array.isArray(value) ? [] : {};
130

131
				for (const [key2, value2] of Object.entries(value)) {
132
					if (typeof value2 === 'string') {
133
						newValue[key2] = value2;
134
					} else {
135
						newValue[key2] = replacer(seen)(key2, value2);
136
					}
137
				}
138

139
				seen.delete(value);
140

141
				return newValue;
142
			}
143

144
			if (!values || filteredValues.length === 0 || typeof value !== 'string') return value;
145

146
			let finalValue = value;
147

148
			for (const [redactKey, valueToRedact] of filteredValues) {
149
				if (finalValue.includes(valueToRedact)) {
150
					finalValue = finalValue.replace(new RegExp(valueToRedact, 'g'), replacement(redactKey));
151
				}
152
			}
153

154
			return finalValue;
155
		};
156
	};
157

158
	const seen = new WeakSet();
159
	return replacer(seen);
160
}
161

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.