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:
@@ -2,8 +2,11 @@ import express from 'express';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import ejsLayouts from 'express-ejs-layouts';
|
||||
import logger from '../utils/logger.js';
|
||||
import { requireAuth, handleLogin, handleLogout, renderLogin } from './auth.js';
|
||||
import { requireAuth, handleLogin, handleLogout } from './auth.js';
|
||||
import { csrfMiddleware, validateCsrf } from './csrf.js';
|
||||
import { asyncHandler, globalErrorHandler } from './errorHandler.js';
|
||||
import dashboardRouter from './routes/dashboard.js';
|
||||
import catalogRouter from './routes/catalog.js';
|
||||
import catalogProductsRouter from './routes/catalogProducts.js';
|
||||
@@ -22,24 +25,43 @@ import localesRouter from './routes/locales.js';
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const app = express();
|
||||
|
||||
// Configure EJS as view engine with layouts
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', join(__dirname, 'views'));
|
||||
app.use(ejsLayouts);
|
||||
app.set('layout', 'layout');
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use('/admin/style.css', express.static(join(__dirname, 'public', 'style.css')));
|
||||
app.use('/uploads', express.static(join(__dirname, '..', '..', 'uploads')));
|
||||
// Serve static assets from public directory
|
||||
app.use(express.static(join(__dirname, 'public')));
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', uptime: process.uptime() });
|
||||
});
|
||||
|
||||
app.get('/login', (req, res) => {
|
||||
res.send(renderLogin());
|
||||
res.render('auth-login', { error: null, layout: false, pageTitle: 'Login' });
|
||||
});
|
||||
|
||||
app.post('/login', handleLogin);
|
||||
app.get('/logout', handleLogout);
|
||||
|
||||
// CSRF token for all authenticated views
|
||||
app.use(csrfMiddleware);
|
||||
app.use(requireAuth);
|
||||
|
||||
// Apply CSRF validation to all state-changing routes
|
||||
app.use(validateCsrf);
|
||||
|
||||
// Protect uploads behind auth with MIME hardening
|
||||
app.use('/uploads', express.static(join(__dirname, '..', '..', 'uploads'), {
|
||||
setHeaders: (res, path) => {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('Content-Disposition', 'attachment');
|
||||
}
|
||||
}));
|
||||
|
||||
app.use('/', dashboardRouter);
|
||||
app.use('/catalog', catalogRouter);
|
||||
app.use('/catalog', catalogProductsRouter);
|
||||
@@ -55,6 +77,9 @@ app.use('/payment-wallets', paymentWalletsRouter);
|
||||
app.use('/seed', seedRouter);
|
||||
app.use('/locales', localesRouter);
|
||||
|
||||
// Global error handler
|
||||
app.use(globalErrorHandler);
|
||||
|
||||
export function startAdminPanel() {
|
||||
const port = parseInt(process.env.ADMIN_PORT || '3001', 10);
|
||||
app.listen(port, () => {
|
||||
|
||||
Reference in New Issue
Block a user