Files
pagodera/lib/secure-logger.ts
Evandro Fonseca Junior e30d9493ff feat: enhance donation creation logging and adjust amount limits
- Add detailed secure logging throughout the donation creation API route for production debugging, including request receipt, validation results, amount calculations, PIX creation success, and error handling
- Update custom donation amount schema to allow minimum 0.01 and maximum 100,000 for broader donation flexibility
- Modify secure logger to always output data in production for improved monitoring and troubleshooting
2025-10-26 23:24:46 -03:00

211 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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: [email protected] -> 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);
},
};