/** * Logger Seguro - Conformidade LGPD * Mascara automaticamente dados pessoais sensíveis em logs * Previne vazamento de informações em ambientes de produção */ interface MaskOptions { /** * Mascara parcial de email (mantém parte visível) * Ex: user@domain.com -> u***@domain.com */ maskEmail?: boolean; /** * Mascara parcial de CPF/CNPJ * Ex: 123.456.789-00 -> ***.***.***-00 */ maskDocument?: boolean; /** * Mascara telefone completo * Ex: (11) 98765-4321 -> (11) *****-4321 */ maskPhone?: boolean; /** * Remove completamente dados sensíveis em produção */ removeSensitive?: boolean; } const isProduction = process.env.NODE_ENV === "production"; /** * Mascara email mantendo primeiro caractere e domínio */ function maskEmail(email: string): string { if (!email || !email.includes("@")) return email; const [localPart, domain] = email.split("@"); const maskedLocal = localPart.substring(0, 1) + "***"; return `${maskedLocal}@${domain}`; } /** * Mascara CPF mantendo apenas os dois últimos dígitos */ function maskCPF(cpf: string): string { if (!cpf) return cpf; // Remove formatação const cleaned = cpf.replace(/[^\d]/g, ""); if (cleaned.length === 11) { return `***.***.***-${cleaned.slice(-2)}`; } if (cleaned.length === 14) { // CNPJ return `**.***.***/****-${cleaned.slice(-2)}`; } return "***.***.***-**"; } /** * Mascara telefone mantendo DDD e últimos 4 dígitos */ function maskPhone(phone: string): string { if (!phone) return phone; // Remove formatação const cleaned = phone.replace(/[^\d]/g, ""); if (cleaned.length >= 10) { const ddd = cleaned.substring(0, 2); const lastFour = cleaned.slice(-4); return `(${ddd}) *****-${lastFour}`; } return "**-****-****"; } /** * Mascara objeto recursivamente */ function maskSensitiveData(obj: any, options: MaskOptions = {}): any { if (!obj || typeof obj !== "object") { return obj; } // Se for string, verifica se é dado sensível if (typeof obj === "string") { // Email if (obj.includes("@") && options.maskEmail) { return maskEmail(obj); } // CPF/CNPJ const digitsOnly = obj.replace(/[^\d]/g, ""); if ( (digitsOnly.length === 11 || digitsOnly.length === 14) && options.maskDocument ) { return maskCPF(obj); } // Telefone if (digitsOnly.length >= 10 && options.maskPhone) { return maskPhone(obj); } return obj; } // Se for array if (Array.isArray(obj)) { return obj.map((item) => maskSensitiveData(item, options)); } // Se for objeto const masked: any = {}; for (const [key, value] of Object.entries(obj)) { const lowerKey = key.toLowerCase(); // Identificar campos sensíveis const isEmail = lowerKey.includes("email") || lowerKey === "mail"; const isDocument = lowerKey.includes("cpf") || lowerKey.includes("cnpj") || lowerKey.includes("taxid"); const isPhone = lowerKey.includes("phone") || lowerKey.includes("cellphone") || lowerKey.includes("telefone"); if (isEmail && options.maskEmail) { masked[key] = maskEmail(String(value)); } else if (isDocument && options.maskDocument) { masked[key] = maskCPF(String(value)); } else if (isPhone && options.maskPhone) { masked[key] = maskPhone(String(value)); } else { masked[key] = maskSensitiveData(value, options); } } return masked; } /** * Logger seguro que mascara dados sensíveis */ export const secureLogger = { /** * Log info com mascaramento automático * SEMPRE exibe em produção para debug */ info(message: string, data?: any) { if (data) { const masked = maskSensitiveData(data, { maskEmail: true, maskDocument: true, maskPhone: true, }); console.log(`ℹ️ ${message}`, JSON.stringify(masked, null, 2)); } else { console.log(`ℹ️ ${message}`, ""); } }, /** * Log debug com mascaramento automático * SEMPRE exibe em produção para debug */ debug(message: string, data?: any) { if (data) { const masked = maskSensitiveData(data, { maskEmail: true, maskDocument: true, maskPhone: true, }); console.debug(`🔍 ${message}`, JSON.stringify(masked, null, 2)); } else { console.debug(`🔍 ${message}`, ""); } }, /** * Log warning */ warn(message: string, data?: any) { console.warn(`⚠️ ${message}`, data || ""); }, /** * Log error */ error(message: string, error?: any) { console.error(`❌ ${message}`, error || ""); }, /** * Mascara dados manualmente */ mask(data: any, options?: MaskOptions) { return maskSensitiveData(data, options); }, };