@diia-inhouse/diia-logger 4.3.30 → 4.4.0

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/dist/config.js CHANGED
@@ -2,6 +2,14 @@ import lodash from "lodash";
2
2
  import { LogLevel } from "@diia-inhouse/types";
3
3
  //#region src/config.ts
4
4
  const { merge } = lodash;
5
+ const freeTextFields = [
6
+ "value",
7
+ "text",
8
+ "textItems",
9
+ "label",
10
+ "shortText",
11
+ "accessibilityDescription"
12
+ ];
5
13
  const defaultOptions = {
6
14
  logLevel: LogLevel.INFO,
7
15
  maxArrayLength: 100,
@@ -28,7 +36,6 @@ const defaultOptions = {
28
36
  "patronymic_name",
29
37
  "passportSeries",
30
38
  "passportNumber",
31
- "email",
32
39
  "addressOfRegistration",
33
40
  "addressOfBirth",
34
41
  "birthDay",
@@ -36,13 +43,10 @@ const defaultOptions = {
36
43
  "date_birth",
37
44
  "fio",
38
45
  "passport",
39
- "phone",
40
46
  "address",
41
47
  "birthplace",
42
48
  "fullName",
43
49
  "full_name",
44
- "phoneNumber",
45
- "extraPhoneNumber",
46
50
  "refreshToken",
47
51
  "token",
48
52
  "fName",
@@ -96,11 +100,8 @@ const defaultOptions = {
96
100
  "partnerFullName",
97
101
  "residenceAddress",
98
102
  "displayName",
99
- "personalPhone",
100
103
  "content",
101
104
  "subjAddress",
102
- "subjPhone",
103
- "subjEmail",
104
105
  "subjDrfoCode",
105
106
  "oldLastName",
106
107
  "oldFirstName",
@@ -140,6 +141,19 @@ const defaultOptions = {
140
141
  "paymentPurpose",
141
142
  "subject",
142
143
  "value"
144
+ ],
145
+ fieldsToRedactEmail: [
146
+ "email",
147
+ "subjEmail",
148
+ ...freeTextFields
149
+ ],
150
+ fieldsToRedactPhone: [
151
+ "phone",
152
+ "phoneNumber",
153
+ "extraPhoneNumber",
154
+ "personalPhone",
155
+ "subjPhone",
156
+ ...freeTextFields
143
157
  ]
144
158
  }
145
159
  };
@@ -152,7 +166,15 @@ const toInternalLoggerOptions = (options = {}) => {
152
166
  fields: new Set(redact.fields),
153
167
  paths: new Set(redact.paths),
154
168
  fieldsToRedactFullname: new Set(redact.fieldsToRedactFullname),
155
- fieldsToRedactItn: new Set(redact.fieldsToRedactItn)
169
+ fieldsToRedactItn: new Set(redact.fieldsToRedactItn),
170
+ fieldsToRedactEmail: new Set(redact.fieldsToRedactEmail),
171
+ fieldsToRedactPhone: new Set(redact.fieldsToRedactPhone),
172
+ fieldsToScan: new Set([
173
+ ...redact.fieldsToRedactFullname || [],
174
+ ...redact.fieldsToRedactItn || [],
175
+ ...redact.fieldsToRedactEmail || [],
176
+ ...redact.fieldsToRedactPhone || []
177
+ ])
156
178
  }
157
179
  };
158
180
  };
@@ -0,0 +1,10 @@
1
+ //#region src/redactors/email.ts
2
+ const EMAIL_PATTERN = /([\w.%+-]+)@([\w.-]+\.[a-z]{2,})/gi;
3
+ function redactEmail(text) {
4
+ if (!text.includes("@")) return text;
5
+ return text.replace(EMAIL_PATTERN, (_match, local, domain) => {
6
+ return `${local.length >= 2 ? local[0] : ""}***@${domain}`;
7
+ });
8
+ }
9
+ //#endregion
10
+ export { redactEmail };
@@ -0,0 +1,17 @@
1
+ //#region src/redactors/phone.ts
2
+ const PHONE_CANDIDATE = /\+?\d[\d\s().-]{7,18}\d/g;
3
+ const nationalDigits = 10;
4
+ const internationalDigits = 12;
5
+ const subscriberDigits = 7;
6
+ function redactPhone(text) {
7
+ return text.replace(PHONE_CANDIDATE, (match) => maskPhone(match));
8
+ }
9
+ function maskPhone(match) {
10
+ const digits = match.replace(/\D/g, "");
11
+ const isNational = digits.length === nationalDigits && digits.startsWith("0");
12
+ const isInternational = digits.length === internationalDigits;
13
+ if (!isNational && !isInternational) return match;
14
+ return `${match.startsWith("+") ? "+" : ""}${digits.slice(0, -subscriberDigits)}${"*".repeat(subscriberDigits - 1)}${digits.slice(-1)}`;
15
+ }
16
+ //#endregion
17
+ export { redactPhone };
package/dist/trimmer.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { toInternalLoggerOptions } from "./config.js";
2
+ import { redactEmail } from "./redactors/email.js";
2
3
  import { redactFullName } from "./redactors/fullName.js";
3
4
  import { redactItn } from "./redactors/itn.js";
5
+ import { redactPhone } from "./redactors/phone.js";
4
6
  import lodash from "lodash";
5
7
  //#region src/trimmer.ts
6
8
  const { isObject } = lodash;
@@ -32,8 +34,7 @@ function trimObject(opts, node, depth) {
32
34
  }
33
35
  output[key] = trimWalker(opts, value, depth + 1);
34
36
  if (typeof value === "string" || Array.isArray(value)) {
35
- if (opts.redact.fieldsToRedactFullname?.has(key)) output[key] = redactionWalker(opts, key, output[key]);
36
- if (opts.redact.fieldsToRedactItn?.has(key)) output[key] = redactionWalker(opts, key, output[key]);
37
+ if (opts.redact.fieldsToScan.has(key)) output[key] = redactionWalker(opts, key, output[key]);
37
38
  continue;
38
39
  }
39
40
  }
@@ -62,6 +63,8 @@ const redactionWalker = (opts, key, value) => {
62
63
  if (typeof value !== "string") return value;
63
64
  if (opts.redact.fieldsToRedactFullname?.has(key)) value = redactFullName(value);
64
65
  if (opts.redact.fieldsToRedactItn?.has(key)) value = redactItn(value);
66
+ if (opts.redact.fieldsToRedactEmail?.has(key)) value = redactEmail(value);
67
+ if (opts.redact.fieldsToRedactPhone?.has(key)) value = redactPhone(value);
65
68
  return value;
66
69
  };
67
70
  const trimmer = (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diia-inhouse/diia-logger",
3
- "version": "4.3.30",
3
+ "version": "4.4.0",
4
4
  "type": "module",
5
5
  "description": "Logger package",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,7 @@
44
44
  "@diia-inhouse/configs": "7.0.1",
45
45
  "@diia-inhouse/eslint-plugin": "1.9.8",
46
46
  "@diia-inhouse/oxc-config": "1.11.0",
47
- "@diia-inhouse/types": "14.0.0",
47
+ "@diia-inhouse/types": "14.1.0",
48
48
  "@diia-inhouse/utils": "6.0.37",
49
49
  "@types/lodash": "4.17.24",
50
50
  "@vitest/coverage-v8": "4.1.5",
package/src/config.ts CHANGED
@@ -7,6 +7,8 @@ import { InternalLoggerOptions } from './interfaces/index.js'
7
7
  // oxlint-disable-next-line typescript/unbound-method
8
8
  const { merge } = lodash
9
9
 
10
+ const freeTextFields = ['value', 'text', 'textItems', 'label', 'shortText', 'accessibilityDescription']
11
+
10
12
  export const defaultOptions: LoggerOptions = {
11
13
  logLevel: LogLevel.INFO,
12
14
  maxArrayLength: 100,
@@ -33,7 +35,6 @@ export const defaultOptions: LoggerOptions = {
33
35
  'patronymic_name',
34
36
  'passportSeries',
35
37
  'passportNumber',
36
- 'email',
37
38
  'addressOfRegistration',
38
39
  'addressOfBirth',
39
40
  'birthDay',
@@ -41,13 +42,10 @@ export const defaultOptions: LoggerOptions = {
41
42
  'date_birth',
42
43
  'fio',
43
44
  'passport',
44
- 'phone',
45
45
  'address',
46
46
  'birthplace',
47
47
  'fullName',
48
48
  'full_name',
49
- 'phoneNumber',
50
- 'extraPhoneNumber',
51
49
  'refreshToken',
52
50
  'token',
53
51
  'fName',
@@ -101,11 +99,8 @@ export const defaultOptions: LoggerOptions = {
101
99
  'partnerFullName',
102
100
  'residenceAddress',
103
101
  'displayName',
104
- 'personalPhone',
105
102
  'content',
106
103
  'subjAddress',
107
- 'subjPhone',
108
- 'subjEmail',
109
104
  'subjDrfoCode',
110
105
  'oldLastName',
111
106
  'oldFirstName',
@@ -141,6 +136,8 @@ export const defaultOptions: LoggerOptions = {
141
136
  'documents',
142
137
  ],
143
138
  fieldsToRedactItn: ['label', 'paymentPurpose', 'subject', 'value'],
139
+ fieldsToRedactEmail: ['email', 'subjEmail', ...freeTextFields],
140
+ fieldsToRedactPhone: ['phone', 'phoneNumber', 'extraPhoneNumber', 'personalPhone', 'subjPhone', ...freeTextFields],
144
141
  },
145
142
  }
146
143
 
@@ -155,6 +152,14 @@ export const toInternalLoggerOptions = (options: LoggerOptions = {}): InternalLo
155
152
  paths: new Set(redact.paths),
156
153
  fieldsToRedactFullname: new Set(redact.fieldsToRedactFullname),
157
154
  fieldsToRedactItn: new Set(redact.fieldsToRedactItn),
155
+ fieldsToRedactEmail: new Set(redact.fieldsToRedactEmail),
156
+ fieldsToRedactPhone: new Set(redact.fieldsToRedactPhone),
157
+ fieldsToScan: new Set([
158
+ ...(redact.fieldsToRedactFullname || []),
159
+ ...(redact.fieldsToRedactItn || []),
160
+ ...(redact.fieldsToRedactEmail || []),
161
+ ...(redact.fieldsToRedactPhone || []),
162
+ ]),
158
163
  },
159
164
  }
160
165
  }
@@ -6,5 +6,8 @@ export interface InternalLoggerOptions extends Required<Omit<LoggerOptions, 'red
6
6
  paths: Set<string>
7
7
  fieldsToRedactFullname: Set<string>
8
8
  fieldsToRedactItn: Set<string>
9
+ fieldsToRedactEmail: Set<string>
10
+ fieldsToRedactPhone: Set<string>
11
+ fieldsToScan: Set<string>
9
12
  }
10
13
  }
@@ -0,0 +1,13 @@
1
+ const EMAIL_PATTERN = /([\w.%+-]+)@([\w.-]+\.[a-z]{2,})/gi
2
+
3
+ export function redactEmail(text: string): string {
4
+ if (!text.includes('@')) {
5
+ return text
6
+ }
7
+
8
+ return text.replace(EMAIL_PATTERN, (_match, local: string, domain: string) => {
9
+ const prefix = local.length >= 2 ? local[0] : ''
10
+
11
+ return `${prefix}***@${domain}`
12
+ })
13
+ }
@@ -0,0 +1,25 @@
1
+ // Liberal candidate: optional '+', then a digit-led run that may contain spaces,
2
+ // parentheses, dots and dashes. Validated by digit count in maskPhone.
3
+ const PHONE_CANDIDATE = /\+?\d[\d\s().-]{7,18}\d/g
4
+
5
+ const nationalDigits = 10
6
+ const internationalDigits = 12
7
+ const subscriberDigits = 7
8
+
9
+ export function redactPhone(text: string): string {
10
+ return text.replace(PHONE_CANDIDATE, (match) => maskPhone(match))
11
+ }
12
+
13
+ function maskPhone(match: string): string {
14
+ const digits = match.replace(/\D/g, '')
15
+ const isNational = digits.length === nationalDigits && digits.startsWith('0')
16
+ const isInternational = digits.length === internationalDigits
17
+ if (!isNational && !isInternational) {
18
+ return match
19
+ }
20
+
21
+ // Keep the country/operator code and the last digit; mask the subscriber number.
22
+ const prefix = match.startsWith('+') ? '+' : ''
23
+
24
+ return `${prefix}${digits.slice(0, -subscriberDigits)}${'*'.repeat(subscriberDigits - 1)}${digits.slice(-1)}`
25
+ }
package/src/trimmer.ts CHANGED
@@ -5,8 +5,10 @@ import { LoggerOptions } from '@diia-inhouse/types'
5
5
 
6
6
  import { toInternalLoggerOptions } from './config.js'
7
7
  import { InternalLoggerOptions } from './interfaces/index.js'
8
+ import { redactEmail } from './redactors/email.js'
8
9
  import { redactFullName } from './redactors/fullName.js'
9
10
  import { redactItn } from './redactors/itn.js'
11
+ import { redactPhone } from './redactors/phone.js'
10
12
 
11
13
  // oxlint-disable-next-line typescript/unbound-method
12
14
  const { isObject } = lodash
@@ -54,11 +56,7 @@ function trimObject(opts: InternalLoggerOptions, node: object, depth: number): o
54
56
  output[key] = trimWalker(opts, value, depth + 1)
55
57
 
56
58
  if (typeof value === 'string' || Array.isArray(value)) {
57
- if (opts.redact.fieldsToRedactFullname?.has(key)) {
58
- output[key] = redactionWalker(opts, key, output[key])
59
- }
60
-
61
- if (opts.redact.fieldsToRedactItn?.has(key)) {
59
+ if (opts.redact.fieldsToScan.has(key)) {
62
60
  output[key] = redactionWalker(opts, key, output[key])
63
61
  }
64
62
 
@@ -135,6 +133,14 @@ const redactionWalker = (opts: InternalLoggerOptions, key: string, value: string
135
133
  value = redactItn(value)
136
134
  }
137
135
 
136
+ if (opts.redact.fieldsToRedactEmail?.has(key)) {
137
+ value = redactEmail(value)
138
+ }
139
+
140
+ if (opts.redact.fieldsToRedactPhone?.has(key)) {
141
+ value = redactPhone(value)
142
+ }
143
+
138
144
  return value
139
145
  }
140
146