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:
NW
2026-07-06 17:42:54 +01:00
parent 8d85776135
commit d4c476002c
1010 changed files with 256079 additions and 1625 deletions

View File

@@ -0,0 +1,57 @@
<div class="panel panel-icon mb-3">
<div class="panel-hdr">
<h2 class="d-flex align-items-center gap-2">
<button class="btn btn-link p-0 text-decoration-none" type="button" data-bs-toggle="collapse" data-bs-target="#addLocationCollapse" aria-expanded="false" aria-controls="addLocationCollapse">Add Location</button>
</h2>
</div>
<div class="collapse" id="addLocationCollapse">
<div class="panel-content">
<form method="POST" action="/locations" class="d-flex flex-wrap gap-2 align-items-end">
<div>
<label class="form-label">Country</label>
<input name="country" class="form-control form-control-sm" placeholder="Country" required>
</div>
<div>
<label class="form-label">City</label>
<input name="city" class="form-control form-control-sm" placeholder="City" required>
</div>
<div>
<label class="form-label">District</label>
<input name="district" class="form-control form-control-sm" placeholder="District">
</div>
<button type="submit" class="btn btn-primary btn-sm">Add</button>
</form>
</div>
</div>
</div>
<div class="panel panel-icon">
<div class="panel-container show">
<div class="panel-content">
<table class="table table-striped table-bordered table-hover mb-0">
<thead><tr><th>ID</th><th>Country</th><th>City</th><th>District</th><th>Categories</th><th>Products</th><th>Actions</th></tr></thead>
<tbody>
<% if (locations.length === 0) { %>
<tr><td colspan="7" class="text-muted">No locations</td></tr>
<% } else { %>
<% locations.forEach(function(l) { %>
<tr>
<td><%= l.id %></td>
<td><%= l.country %></td>
<td><%= l.city %></td>
<td><%= l.district || '' %></td>
<td><%= l.category_count || 0 %></td>
<td><%= l.product_count || 0 %></td>
<td>
<form method="POST" action="/locations/<%= l.id %>/delete" class="d-inline" onsubmit="return confirm('Delete location?')">
<button class="btn btn-sm btn-danger">Delete</button>
</form>
</td>
</tr>
<% }); %>
<% } %>
</tbody>
</table>
</div>
</div>
</div>