@nestjs/common 12.0.4 → 12.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/decorators/http/route-params.decorator.d.ts +48 -0
- package/decorators/http/route-params.decorator.js +58 -0
- package/enums/route-paramtypes.enum.d.ts +3 -1
- package/enums/route-paramtypes.enum.js +2 -0
- package/index.d.ts +1 -1
- package/interfaces/http/cookie-options.interface.d.ts +79 -0
- package/interfaces/http/cookie-options.interface.js +1 -0
- package/interfaces/http/csrf-protection-options.interface.d.ts +47 -0
- package/interfaces/http/csrf-protection-options.interface.js +1 -0
- package/interfaces/http/http-server.interface.d.ts +51 -0
- package/interfaces/http/index.d.ts +3 -0
- package/interfaces/http/index.js +3 -0
- package/interfaces/http/security-headers-options.interface.d.ts +142 -0
- package/interfaces/http/security-headers-options.interface.js +1 -0
- package/interfaces/nest-application-options.interface.d.ts +7 -0
- package/interfaces/nest-application.interface.d.ts +43 -0
- package/internal.d.ts +1 -1
- package/package.json +2 -2
- package/pipes/parse-array.pipe.d.ts +1 -0
- package/pipes/parse-array.pipe.js +18 -3
- package/pipes/parse-date.pipe.d.ts +2 -1
- package/pipes/parse-date.pipe.js +8 -2
- package/pipes/parse-enum.pipe.js +3 -1
- package/services/console-logger.service.d.ts +71 -4
- package/services/console-logger.service.js +182 -14
- package/services/log-levels.constant.d.ts +5 -0
- package/services/log-levels.constant.js +8 -0
- package/services/logger.service.d.ts +6 -6
- package/services/logger.service.js +25 -9
- package/services/utils/filter-log-levels.util.d.ts +1 -1
- package/services/utils/filter-log-levels.util.js +1 -1
- package/services/utils/get-env-log-levels.util.d.ts +14 -0
- package/services/utils/get-env-log-levels.util.js +25 -0
- package/services/utils/is-log-level.util.d.ts +1 -1
- package/services/utils/is-log-level.util.js +1 -1
- package/services/utils/redact.util.d.ts +27 -0
- package/services/utils/redact.util.js +225 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { filterLogLevels } from './filter-log-levels.util.js';
|
|
2
|
+
import { isLogLevel } from './is-log-level.util.js';
|
|
3
|
+
/**
|
|
4
|
+
* The environment variable that sets the default log levels of `ConsoleLogger`.
|
|
5
|
+
*/
|
|
6
|
+
export const LOG_LEVEL_ENV_VAR = 'NEST_LOG_LEVEL';
|
|
7
|
+
/**
|
|
8
|
+
* Reads the log levels from the `NEST_LOG_LEVEL` environment variable.
|
|
9
|
+
* Accepts the formats of `filterLogLevels`: a level (`warn`, that level and
|
|
10
|
+
* above), a list (`warn,error`) or a threshold (`>=warn`, `>debug`).
|
|
11
|
+
*
|
|
12
|
+
* @returns `undefined` when the variable is unset or empty, `false` when its
|
|
13
|
+
* value can't be parsed, the log levels otherwise.
|
|
14
|
+
*/
|
|
15
|
+
export function getEnvLogLevels() {
|
|
16
|
+
const value = process.env[LOG_LEVEL_ENV_VAR]?.replaceAll(' ', '');
|
|
17
|
+
if (!value) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const threshold = /^>=?(.*)$/.exec(value.toLowerCase());
|
|
21
|
+
const isValid = threshold
|
|
22
|
+
? isLogLevel(threshold[1])
|
|
23
|
+
: value.toLowerCase().split(',').every(isLogLevel);
|
|
24
|
+
return isValid ? filterLogLevels(value) : false;
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const DEFAULT_REDACT_CENSOR = "[REDACTED]";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a copy of the value in which the properties matching the paths are
|
|
4
|
+
* replaced with the censor, or the value itself when nothing matched.
|
|
5
|
+
*/
|
|
6
|
+
export type Redactor = (value: unknown) => unknown;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a function that masks properties of logged values.
|
|
9
|
+
*
|
|
10
|
+
* - A path without dots (`password`) matches a property with that name at any
|
|
11
|
+
* depth.
|
|
12
|
+
* - A dotted path (`user.password`) matches when the last keys leading to a
|
|
13
|
+
* property are those keys, at any depth. Array indices are not keys, so
|
|
14
|
+
* `users.password` also matches `{ users: [{ password }] }`.
|
|
15
|
+
* - Keys are compared case-insensitively.
|
|
16
|
+
*
|
|
17
|
+
* Plain objects, arrays, class instances, errors (including `cause` and
|
|
18
|
+
* `AggregateError#errors`), `Map`s (string keys) and `Set`s are traversed.
|
|
19
|
+
* Only the objects on the way to a redacted property are copied; the rest is
|
|
20
|
+
* returned as is, and the input is never mutated. A circular reference is
|
|
21
|
+
* replaced with `"[Circular]"` (so the objects on the cycle are copied too). If
|
|
22
|
+
* the value can't be traversed (for example, a getter throws), the whole value
|
|
23
|
+
* is replaced with the censor.
|
|
24
|
+
*
|
|
25
|
+
* @returns `undefined` when there is nothing to redact.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createRedactor(paths: string[], censor?: string): Redactor | undefined;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
export const DEFAULT_REDACT_CENSOR = '[REDACTED]';
|
|
2
|
+
const CIRCULAR = '[Circular]';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a function that masks properties of logged values.
|
|
5
|
+
*
|
|
6
|
+
* - A path without dots (`password`) matches a property with that name at any
|
|
7
|
+
* depth.
|
|
8
|
+
* - A dotted path (`user.password`) matches when the last keys leading to a
|
|
9
|
+
* property are those keys, at any depth. Array indices are not keys, so
|
|
10
|
+
* `users.password` also matches `{ users: [{ password }] }`.
|
|
11
|
+
* - Keys are compared case-insensitively.
|
|
12
|
+
*
|
|
13
|
+
* Plain objects, arrays, class instances, errors (including `cause` and
|
|
14
|
+
* `AggregateError#errors`), `Map`s (string keys) and `Set`s are traversed.
|
|
15
|
+
* Only the objects on the way to a redacted property are copied; the rest is
|
|
16
|
+
* returned as is, and the input is never mutated. A circular reference is
|
|
17
|
+
* replaced with `"[Circular]"` (so the objects on the cycle are copied too). If
|
|
18
|
+
* the value can't be traversed (for example, a getter throws), the whole value
|
|
19
|
+
* is replaced with the censor.
|
|
20
|
+
*
|
|
21
|
+
* @returns `undefined` when there is nothing to redact.
|
|
22
|
+
*/
|
|
23
|
+
export function createRedactor(paths, censor = DEFAULT_REDACT_CENSOR) {
|
|
24
|
+
const keys = new Set();
|
|
25
|
+
const dottedPaths = [];
|
|
26
|
+
for (const path of paths) {
|
|
27
|
+
const segments = path
|
|
28
|
+
.split('.')
|
|
29
|
+
.map(segment => segment.trim().toLowerCase())
|
|
30
|
+
.filter(segment => segment.length > 0);
|
|
31
|
+
if (segments.length === 1) {
|
|
32
|
+
keys.add(segments[0]);
|
|
33
|
+
}
|
|
34
|
+
else if (segments.length > 1) {
|
|
35
|
+
dottedPaths.push(segments);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (keys.size === 0 && dottedPaths.length === 0) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return (value) => {
|
|
42
|
+
// Lower-cased keys from the root to the value being visited.
|
|
43
|
+
const path = [];
|
|
44
|
+
// Objects on that path, to detect circular references.
|
|
45
|
+
const ancestors = [];
|
|
46
|
+
const isRedacted = (key) => {
|
|
47
|
+
if (keys.has(key)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return dottedPaths.some(segments => {
|
|
51
|
+
const last = segments.length - 1;
|
|
52
|
+
if (segments[last] !== key || last > path.length) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
for (let i = 1; i <= last; i++) {
|
|
56
|
+
if (segments[last - i] !== path[path.length - i]) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
// Each call starts with a fresh "path" and "ancestors", so an exception
|
|
64
|
+
// (caught below) doesn't need to restore them.
|
|
65
|
+
const visitProperty = (key, propertyValue) => {
|
|
66
|
+
const normalizedKey = key.toLowerCase();
|
|
67
|
+
if (isRedacted(normalizedKey)) {
|
|
68
|
+
return censor;
|
|
69
|
+
}
|
|
70
|
+
if (typeof propertyValue !== 'object' || propertyValue === null) {
|
|
71
|
+
return propertyValue;
|
|
72
|
+
}
|
|
73
|
+
path.push(normalizedKey);
|
|
74
|
+
const redacted = visit(propertyValue);
|
|
75
|
+
path.pop();
|
|
76
|
+
return redacted;
|
|
77
|
+
};
|
|
78
|
+
const visit = (current) => {
|
|
79
|
+
if (typeof current !== 'object' || current === null) {
|
|
80
|
+
return current;
|
|
81
|
+
}
|
|
82
|
+
if (ancestors.includes(current)) {
|
|
83
|
+
return CIRCULAR;
|
|
84
|
+
}
|
|
85
|
+
let redacted;
|
|
86
|
+
ancestors.push(current);
|
|
87
|
+
if (Array.isArray(current)) {
|
|
88
|
+
redacted = redactArray(current);
|
|
89
|
+
}
|
|
90
|
+
else if (Object.getPrototypeOf(current) === Object.prototype) {
|
|
91
|
+
redacted = redactObject(current);
|
|
92
|
+
}
|
|
93
|
+
else if (current instanceof Map) {
|
|
94
|
+
redacted = redactMap(current);
|
|
95
|
+
}
|
|
96
|
+
else if (current instanceof Set) {
|
|
97
|
+
redacted = redactSet(current);
|
|
98
|
+
}
|
|
99
|
+
else if (isOpaque(current)) {
|
|
100
|
+
redacted = current;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
redacted = redactObject(current);
|
|
104
|
+
}
|
|
105
|
+
ancestors.pop();
|
|
106
|
+
return redacted;
|
|
107
|
+
};
|
|
108
|
+
const redactArray = (array) => {
|
|
109
|
+
let copy;
|
|
110
|
+
for (let i = 0; i < array.length; i++) {
|
|
111
|
+
const redacted = visit(array[i]);
|
|
112
|
+
if (!Object.is(redacted, array[i])) {
|
|
113
|
+
copy ??= array.slice();
|
|
114
|
+
copy[i] = redacted;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return copy ?? array;
|
|
118
|
+
};
|
|
119
|
+
const redactMap = (map) => {
|
|
120
|
+
let changed = false;
|
|
121
|
+
const entries = [];
|
|
122
|
+
for (const [key, entryValue] of map) {
|
|
123
|
+
const redacted = typeof key === 'string'
|
|
124
|
+
? visitProperty(key, entryValue)
|
|
125
|
+
: visit(entryValue);
|
|
126
|
+
changed ||= !Object.is(redacted, entryValue);
|
|
127
|
+
entries.push([key, redacted]);
|
|
128
|
+
}
|
|
129
|
+
return changed ? new Map(entries) : map;
|
|
130
|
+
};
|
|
131
|
+
const redactSet = (set) => {
|
|
132
|
+
let changed = false;
|
|
133
|
+
const values = [];
|
|
134
|
+
for (const setValue of set) {
|
|
135
|
+
const redacted = visit(setValue);
|
|
136
|
+
changed ||= !Object.is(redacted, setValue);
|
|
137
|
+
values.push(redacted);
|
|
138
|
+
}
|
|
139
|
+
return changed ? new Set(values) : set;
|
|
140
|
+
};
|
|
141
|
+
const redactObject = (object) => {
|
|
142
|
+
const source = object;
|
|
143
|
+
const keysToVisit = Object.keys(source);
|
|
144
|
+
if (object instanceof Error) {
|
|
145
|
+
// Non-enumerable, but part of the logged error.
|
|
146
|
+
for (const key of ['cause', 'errors']) {
|
|
147
|
+
if (Object.prototype.hasOwnProperty.call(object, key) &&
|
|
148
|
+
!keysToVisit.includes(key)) {
|
|
149
|
+
keysToVisit.push(key);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let changes;
|
|
154
|
+
for (const key of keysToVisit) {
|
|
155
|
+
const original = source[key];
|
|
156
|
+
const redacted = visitProperty(key, original);
|
|
157
|
+
if (!Object.is(redacted, original)) {
|
|
158
|
+
changes ??= new Map();
|
|
159
|
+
changes.set(key, redacted);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!changes) {
|
|
163
|
+
return object;
|
|
164
|
+
}
|
|
165
|
+
if (Object.getPrototypeOf(object) === Object.prototype) {
|
|
166
|
+
const copy = { ...source };
|
|
167
|
+
for (const [key, redacted] of changes) {
|
|
168
|
+
if (key === '__proto__') {
|
|
169
|
+
// An assignment would call the "__proto__" setter.
|
|
170
|
+
Object.defineProperty(copy, key, {
|
|
171
|
+
value: redacted,
|
|
172
|
+
enumerable: true,
|
|
173
|
+
writable: true,
|
|
174
|
+
configurable: true,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
copy[key] = redacted;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return copy;
|
|
182
|
+
}
|
|
183
|
+
// Keeps the prototype and the other properties (including the
|
|
184
|
+
// non-enumerable "message" and "stack" of errors).
|
|
185
|
+
const descriptors = Object.getOwnPropertyDescriptors(object);
|
|
186
|
+
if (object instanceof Error && descriptors.stack?.get) {
|
|
187
|
+
// V8 may define "stack" as an accessor that only works on the
|
|
188
|
+
// original error.
|
|
189
|
+
descriptors.stack = {
|
|
190
|
+
value: object.stack,
|
|
191
|
+
enumerable: descriptors.stack.enumerable,
|
|
192
|
+
writable: true,
|
|
193
|
+
configurable: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
for (const [key, redacted] of changes) {
|
|
197
|
+
descriptors[key] = {
|
|
198
|
+
value: redacted,
|
|
199
|
+
enumerable: descriptors[key].enumerable,
|
|
200
|
+
writable: true,
|
|
201
|
+
configurable: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return Object.create(Object.getPrototypeOf(object), descriptors);
|
|
205
|
+
};
|
|
206
|
+
try {
|
|
207
|
+
return visit(value);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return censor;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Objects whose content isn't made of properties worth traversing.
|
|
216
|
+
*/
|
|
217
|
+
function isOpaque(value) {
|
|
218
|
+
return (value instanceof Date ||
|
|
219
|
+
value instanceof RegExp ||
|
|
220
|
+
value instanceof Promise ||
|
|
221
|
+
value instanceof WeakMap ||
|
|
222
|
+
value instanceof WeakSet ||
|
|
223
|
+
value instanceof ArrayBuffer ||
|
|
224
|
+
ArrayBuffer.isView(value));
|
|
225
|
+
}
|