Files
goodgo-platform/apps/api/src/modules/shared/infrastructure/pii-masker.ts
Ho Ngoc Hai c981bff771 feat(shared): add error handling & structured logging strategy
- Global exception filter with consistent error response format
- Domain exceptions (NotFoundException, ValidationException, etc.)
- Error codes enum for domain-specific error identification
- Correlation ID middleware for request tracing
- Request/response logging middleware with structured JSON
- PII masking in logs (emails, phone numbers, sensitive fields)
- Enhanced LoggerService with pino formatters and ISO timestamps
- Tests for exception filter, domain exceptions, and PII masker

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-08 00:18:21 +07:00

54 lines
1.3 KiB
TypeScript

/**
* PII masking utility for structured logs.
* Redacts sensitive fields in log data before they reach the transport.
*/
const SENSITIVE_KEYS = new Set([
'password',
'token',
'accessToken',
'refreshToken',
'secret',
'authorization',
'cookie',
'creditCard',
'cardNumber',
'cvv',
'ssn',
'cmnd',
'cccd',
]);
const EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
const PHONE_REGEX = /(?:\+84|0)\d{9,10}/g;
export function maskPii(obj: unknown): unknown {
if (obj === null || obj === undefined) return obj;
if (typeof obj === 'string') return maskString(obj);
if (Array.isArray(obj)) return obj.map(maskPii);
if (typeof obj === 'object') {
const masked: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
if (SENSITIVE_KEYS.has(key)) {
masked[key] = '[REDACTED]';
} else {
masked[key] = maskPii(value);
}
}
return masked;
}
return obj;
}
function maskString(value: string): string {
let result = value;
result = result.replace(EMAIL_REGEX, (match) => {
const [local, domain] = match.split('@');
return `${local![0]}***@${domain}`;
});
result = result.replace(PHONE_REGEX, (match) => {
return match.slice(0, 3) + '****' + match.slice(-3);
});
return result;
}