resetea.net/api/routes/erase.js

75 lines
2.6 KiB
JavaScript

'use strict';
const crypto = require('crypto');
const { sendErasureMail, PROVIDER_DATA } = require('../services/mailer');
const stats = require('../services/stats');
const ALLOWED_PROVIDERS = new Set(Object.keys(PROVIDER_DATA));
/* Elimina CRLF y chars de control — previene header injection en el cuerpo del email */
function sanitizeField(raw, maxLen) {
return String(raw || '')
.replace(/[\r\n\t\x00-\x1F\x7F]/g, ' ') // CRLF → espacio, no rompe el texto
.replace(/\s{2,}/g, ' ') // colapsa espacios múltiples
.trim()
.slice(0, maxLen);
}
module.exports = async (req, res) => {
try {
const { provider, email,
nickname: rawNick, phone: rawPhone,
address: rawAddr, extra: rawExtra } = req.body;
// Validación mínima
if (!provider || !email) {
return res.status(400).json({ error: 'provider y email son obligatorios' });
}
// Validar proveedor conocido (previene abusos de relay)
if (!ALLOWED_PROVIDERS.has(provider)) {
return res.status(400).json({ error: 'Proveedor no soportado. Usa el formulario oficial.' });
}
// Validación básica de email
if (typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return res.status(400).json({ error: 'Email inválido' });
}
// Sanitizar campos opcionales: longitud + strip CRLF (header injection)
const nickname = sanitizeField(rawNick, 100);
const phone = sanitizeField(rawPhone, 30);
const address = sanitizeField(rawAddr, 300);
const extra = sanitizeField(rawExtra, 500);
// Hash irreversible para referencia (auditoría sin almacenar PII)
const hash = crypto
.createHash('sha256')
.update(email + (process.env.SALT || 'resetea-default-salt'))
.digest('hex');
const result = await sendErasureMail({ provider, email, nickname, phone, address, extra });
if (result.skipped) {
stats.record({ type: 'redirected', provider });
return res.json({
status: 'use_form',
message: 'Este proveedor no acepta solicitudes por email. Usa su formulario oficial.',
formUrl: result.formUrl,
reference: hash.substring(0, 12),
});
}
stats.record({ type: 'sent', provider });
res.json({
status: 'ok',
message: 'Solicitud enviada. Guarda el código de referencia.',
reference: hash.substring(0, 12),
});
} catch (e) {
stats.record({ type: 'error' });
console.error('erase route error:', e.message);
res.status(500).json({ error: 'Error interno. Inténtalo de nuevo o usa el formulario oficial del proveedor.' });
}
};