Files
telegram-shop/src/migrations/runner.js
NW 49945d9d81 security(csv-export): harden mnemonic export with super admin, audit, watermark (#48)
- Add SUPER_ADMIN_IDS config (fallback to ADMIN_IDS if not set)
- Add isSuperAdmin() to middleware/auth.js
- Create auditService.js for structured audit logging (DB + pino)
- Create migration 005_audit_log.js
- Add confirmation dialog before CSV export (confirm_export_ callback)
- Check isSuperAdmin before export — block non-super admins
- Audit log every export: admin ID, wallet type, wallet count
- Add exported_by watermark column to CSV with admin telegram ID
- Notify all other super admins when export occurs
- Add SUPER_ADMIN_IDS to .env.example

8 files changed, 154 insertions, 39 deletions
2026-06-22 10:07:58 +01:00

60 lines
2.0 KiB
JavaScript

import db from '../config/database.js';
import logger from '../utils/logger.js';
const ALLOWED_TABLES = new Set([
'users', 'crypto_wallets', 'transactions', 'products',
'purchases', 'locations', 'categories'
]);
export const checkColumnExists = async (tableName, columnName) => {
if (!ALLOWED_TABLES.has(tableName)) {
throw new Error(`Invalid table name: ${tableName}`);
}
try {
const result = await db.allAsync(`PRAGMA table_info(${tableName})`);
return result.some(column => column.name === columnName);
} catch (error) {
logger.error({ err: error, tableName, columnName }, 'Error checking column');
return false;
}
};
export const cleanUpInvalidForeignKeys = async () => {
try {
await db.runAsync(`DELETE FROM crypto_wallets WHERE user_id NOT IN (SELECT id FROM users)`);
logger.info('Cleaned up invalid foreign key references in crypto_wallets table');
} catch (error) {
logger.error({ err: error }, 'Error cleaning up invalid foreign key references');
}
};
export async function runMigrations() {
await db.runAsync(`CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT)`);
const row = await db.getAsync(`SELECT value FROM _meta WHERE key = 'schema_version'`);
const currentVersion = row ? parseInt(row.value, 10) : 0;
const migrations = [
(await import('./001_initial_schema.js')).default,
(await import('./002_add_columns.js')).default,
(await import('./003_add_indexes.js')).default,
(await import('./004_user_states.js')).default,
(await import('./005_audit_log.js')).default,
];
for (let i = currentVersion; i < migrations.length; i++) {
logger.info({ migration: i + 1, total: migrations.length }, 'Running migration');
if (i === 1) {
await migrations[i](db, checkColumnExists);
} else {
await migrations[i](db);
}
await db.runAsync(
`INSERT OR REPLACE INTO _meta (key, value) VALUES ('schema_version', ?)`,
[String(i + 1)]
);
}
logger.info({ schemaVersion: migrations.length }, 'Migrations complete');
}