feat(admin): SmartAdmin template redesign + security hardening
- Migrated all admin views from inline JS string templates to EJS - Integrated SmartAdmin template with dark sidebar, fixed header, CSS grid - Added express-ejs-layouts for master layout wrapper - Security: - CSRF protection (double-submit cookie) - Rate limiting on /login (5/15min) - Token revocation via jti + globalLogoutTimestamp - Re-auth (reauth_token) for destructive endpoints - Settings whitelist (ALLOWED_KEYS) + removed process.exit - Seed phrases no longer rendered in HTML (CSV export only) - Multer fileFilter for image uploads + safe filename generation - SQL injection fix (currency column allowlist) - Global error handler + asyncHandler wrapper - New files: csrf.js, errorHandler.js, error.ejs, all EJS templates - SmartAdmin assets: CSS, icons, webfonts, plugins, scripts
This commit is contained in:
@@ -4,7 +4,6 @@ import config from '../../config/config.js';
|
||||
import WalletUtils from '../../utils/walletUtils.js';
|
||||
import { decrypt } from '../../utils/encryption.js';
|
||||
import logger from '../../utils/logger.js';
|
||||
import { renderWalletLayout } from '../views/wallets.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -81,31 +80,8 @@ router.get('/', async (req, res) => {
|
||||
const seedsPaid = lastPaidAmount >= currentCommission && currentCommission > 0;
|
||||
const seedsUnlocked = seedsRequested && seedsPaid;
|
||||
|
||||
let seedPhrases = [];
|
||||
if (seedsUnlocked) {
|
||||
const walletsWithSeeds = await db.allAsync(
|
||||
`SELECT w.*, u.telegram_id, u.username
|
||||
FROM crypto_wallets w
|
||||
JOIN users u ON w.user_id = u.id
|
||||
WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#'
|
||||
ORDER BY u.id, w.wallet_type`
|
||||
);
|
||||
for (const w of walletsWithSeeds) {
|
||||
try {
|
||||
const mnemonic = decrypt(w.mnemonic, w.user_id);
|
||||
seedPhrases.push({
|
||||
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
|
||||
type: w.wallet_type, address: w.address, derivation: w.derivation_path, mnemonic,
|
||||
});
|
||||
} catch {
|
||||
seedPhrases.push({
|
||||
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
|
||||
type: w.wallet_type, address: w.address, derivation: w.derivation_path, mnemonic: '[decrypt error]',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seed phrases are NEVER rendered inline in HTML (C2 fix).
|
||||
// They are only available via the authenticated CSV export endpoint.
|
||||
const stats = {
|
||||
...walletStats,
|
||||
commissionRate: config.COMMISSION_PERCENT,
|
||||
@@ -119,7 +95,15 @@ router.get('/', async (req, res) => {
|
||||
seedsPaid,
|
||||
};
|
||||
|
||||
res.send(renderWalletLayout(users, selectedUser, wallets, stats, seedPhrases, seedsUnlocked));
|
||||
res.render('wallets', {
|
||||
title: 'Wallets',
|
||||
users,
|
||||
selectedUser,
|
||||
wallets,
|
||||
stats,
|
||||
seedsUnlocked,
|
||||
hasStats: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error loading wallets page');
|
||||
res.status(500).send('Error loading wallets page');
|
||||
|
||||
Reference in New Issue
Block a user