staging: artefactos de despliegue dockerizado + neutralizacion de datos
- .meteorignore excluye scripts/ del bundle Meteor (mongosh usa global db); docker-compose.staging.yml (mongo7/redis/notif-shadow/mailhog); scripts/neutralize-contacts.mongo.js (contactos + cola de correo); secrets/*.staging.*.example
This commit is contained in:
parent
1ef0012af9
commit
2e298f9ee5
5 changed files with 346 additions and 0 deletions
95
scripts/neutralize-contacts.mongo.js
Normal file
95
scripts/neutralize-contacts.mongo.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Neutraliza los datos de contacto en un volcado REAL de `fuegos` restaurado en el
|
||||
// Mongo 7 de STAGING, para que ninguna notificación pueda alcanzar a un usuario real.
|
||||
// IDEMPOTENTE: se puede ejecutar varias veces sin efectos acumulativos indeseados.
|
||||
//
|
||||
// Uso (desde el host de staging, contra el contenedor del compose):
|
||||
// docker exec -i tcef-staging-mongo mongosh --quiet fuegos < scripts/neutralize-contacts.mongo.js
|
||||
//
|
||||
// SEGURIDAD: ejecutar SOLO contra el Mongo aislado de staging. Aborta si detecta que la
|
||||
// conexión no es la esperada (heurística: nombre de host contiene 'shiva'/'simone'/'rbg'/'rosaparks').
|
||||
|
||||
(function guardAgainstProd() {
|
||||
try {
|
||||
const host = db.getMongo().host || '';
|
||||
const prod = /shiva|simone|rbg|rosaparks|rsmain/i;
|
||||
if (prod.test(host)) {
|
||||
throw new Error('ABORTADO: la conexión (' + host + ') parece de PRODUCCIÓN. Este script solo debe correr contra el Mongo de staging.');
|
||||
}
|
||||
} catch (e) {
|
||||
if (/ABORTADO/.test(e.message)) { print(e.message); quit(1); }
|
||||
}
|
||||
})();
|
||||
|
||||
const dbx = db.getSiblingDB('fuegos');
|
||||
|
||||
// 1) users.emails[].address -> "<_id>@invalid.test" (único por usuario) + verified:false
|
||||
const rEmails = dbx.users.updateMany(
|
||||
{ emails: { $type: 'array', $ne: [] } },
|
||||
[{
|
||||
$set: {
|
||||
emails: {
|
||||
$map: {
|
||||
input: '$emails',
|
||||
as: 'e',
|
||||
in: {
|
||||
address: { $concat: [{ $toString: '$_id' }, '@invalid.test'] },
|
||||
verified: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
);
|
||||
|
||||
// 2) users: purgar tokens/handles de notificación (FCM y Telegram)
|
||||
const rUserTokens = dbx.users.updateMany(
|
||||
{}, { $unset: {
|
||||
fireBaseToken: '',
|
||||
telegramChatId: '',
|
||||
telegramUsername: '',
|
||||
telegramFirstName: '',
|
||||
telegramLanguageCode: ''
|
||||
}}
|
||||
);
|
||||
|
||||
// 3) users.services.google.email -> invalidar (evita PII en OAuth); conservar el resto de services
|
||||
const rGoogle = dbx.users.updateMany(
|
||||
{ 'services.google.email': { $exists: true } },
|
||||
[{ $set: { 'services.google.email': { $concat: [{ $toString: '$_id' }, '@invalid.test'] } } }]
|
||||
);
|
||||
|
||||
// 4) subscriptions: quitar el destino de Telegram (chatId) de las subs de ese tipo
|
||||
const rSubs = dbx.subscriptions.updateMany(
|
||||
{ chatId: { $exists: true } }, { $unset: { chatId: '' } }
|
||||
);
|
||||
|
||||
// 5) Cola de Mail-Time: el volcado de prod trae correos ya encolados con el
|
||||
// DESTINATARIO real "horneado" (p.ej. de 2020). Neutralizar users.emails NO los
|
||||
// toca; la web (isMailServer) los drenaría. Se vacían las colas de la cola/jobs.
|
||||
// (En staging MAIL_URL apunta a Mailhog, así que aunque quedaran, se capturarían;
|
||||
// esto es defensa en profundidad para no arrastrar direcciones reales.)
|
||||
const rMailQueue = dbx.getCollection('__mailTimeQueue__').deleteMany({});
|
||||
const rMailJobs = dbx.getCollection('__JobTasks__mailTimeQueue').deleteMany({});
|
||||
|
||||
printjson({
|
||||
usersEmailsNeutralizados: rEmails.modifiedCount,
|
||||
usersTokensPurgados: rUserTokens.modifiedCount,
|
||||
googleEmailsInvalidados: rGoogle.modifiedCount,
|
||||
subsChatIdEliminados: rSubs.modifiedCount,
|
||||
colaCorreoBorrada: rMailQueue.deletedCount,
|
||||
colaJobsBorrada: rMailJobs.deletedCount
|
||||
});
|
||||
|
||||
// Verificación rápida (debe dar 0 en todas). OJO: filtrar por $exists ANTES del
|
||||
// $not/regex — si no, los usuarios SIN email cuentan como "no invalidados" (falso positivo).
|
||||
const conToken = dbx.users.countDocuments({ fireBaseToken: { $exists: true } });
|
||||
const emailMalo = dbx.users.countDocuments({ 'emails.address': { $exists: true, $not: /@invalid\.test$/ } });
|
||||
const googleMalo = dbx.users.countDocuments({ 'services.google.email': { $exists: true, $not: /@invalid\.test$/ } });
|
||||
const subsConChat = dbx.subscriptions.countDocuments({ chatId: { $exists: true } });
|
||||
const colaCorreo = dbx.getCollection('__mailTimeQueue__').countDocuments({});
|
||||
printjson({ verificacion: { usersConFireBaseToken: conToken, usersConEmailRealVisible: emailMalo, usersConGoogleEmailReal: googleMalo, subsConChatId: subsConChat, correosEnCola: colaCorreo } });
|
||||
if (conToken === 0 && emailMalo === 0 && googleMalo === 0 && subsConChat === 0 && colaCorreo === 0) {
|
||||
print('OK: contactos neutralizados. Ninguna notificación puede alcanzar a un usuario real.');
|
||||
} else {
|
||||
print('AVISO: quedan campos sin neutralizar — revisar antes de exponer el staging.');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue