v1.2.0: disable CSRF for Tor, fix wallet type validation, add version history modal
- fix(admin/csrf): completely disable CSRF checks for Tor/onion compatibility - fix(validators): add 'main' and 'bonus' to WALLET_TYPES for purchase flow - feat(admin): add clickable version tag with version history modal in sidebar - docs: add VERSION.md with changelog and update instructions
This commit is contained in:
34
VERSION.md
Normal file
34
VERSION.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Telegram Shop — Version History
|
||||||
|
|
||||||
|
## How to update version
|
||||||
|
|
||||||
|
1. Edit this file (`VERSION.md`)
|
||||||
|
2. Add new entry under `## Changelog`
|
||||||
|
3. Bump version in `src/admin/views/partials/app-sidebar.ejs`
|
||||||
|
4. Commit all changes together
|
||||||
|
|
||||||
|
## Current Version
|
||||||
|
|
||||||
|
**v1.2.0** — 2026-07-08
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.2.0 — 2026-07-08
|
||||||
|
- **fix**: Disabled CSRF checks in admin panel for Tor / onion zone compatibility
|
||||||
|
- **fix**: Fixed "Invalid wallet type" error in Telegram bot purchase flow (`main`/`bonus` types added to validator)
|
||||||
|
- **feat**: Added version history modal in admin sidebar
|
||||||
|
|
||||||
|
### v1.1.0 — 2026-07-02
|
||||||
|
- **feat**: Mercuryo gateway integration, crypto QR deposit, mono products, wallet auto-refresh
|
||||||
|
- **fix**: CSRF cookie `sameSite=false` for Tor, auth cookie fix, async handlers
|
||||||
|
- **feat**: Draggable dashboard panels + business KPI redesign
|
||||||
|
- **fix**: SmartAdmin template redesign + security hardening
|
||||||
|
|
||||||
|
### v1.0.0 — 2026-06-24
|
||||||
|
- **feat**: Initial release — Telegram shop bot with admin panel
|
||||||
|
- **feat**: Crypto wallets (BTC, LTC, ETH, USDT, USDC)
|
||||||
|
- **feat**: Product catalog with locations, categories, subcategories
|
||||||
|
- **feat**: Purchase system with hidden content delivery
|
||||||
|
- **feat**: Admin panel with dashboard, wallets, users, purchases, audit log
|
||||||
|
- **feat**: Tor proxy support (.onion access)
|
||||||
|
- **feat**: i18n localization (en/es/de)
|
||||||
@@ -23,53 +23,15 @@ export function csrfMiddleware(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function validateCsrf(req, res, next) {
|
export function validateCsrf(req, res, next) {
|
||||||
// Only validate state-changing methods
|
// CSRF checks disabled for Tor / onion zone compatibility
|
||||||
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
|
return next();
|
||||||
|
|
||||||
// Skip multipart/form-data — multer hasn't parsed the body yet.
|
|
||||||
// Route handlers using multer must validate CSRF themselves after parsing.
|
|
||||||
const ct = req.headers['content-type'] || '';
|
|
||||||
if (ct.startsWith('multipart/form-data')) return next();
|
|
||||||
|
|
||||||
const token = req.body?._csrf || req.headers[CSRF_HEADER];
|
|
||||||
const cookieToken = req.cookies?.[CSRF_COOKIE];
|
|
||||||
|
|
||||||
if (!token || !cookieToken) {
|
|
||||||
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method, hasBodyToken: !!token, hasCookieToken: !!cookieToken, contentType: ct }, 'CSRF token missing');
|
|
||||||
return res.status(403).send('CSRF token missing. Please reload the page.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const provided = Buffer.from(token, 'utf8');
|
|
||||||
const expected = Buffer.from(cookieToken, 'utf8');
|
|
||||||
if (provided.length !== expected.length || !crypto.timingSafeEqual(provided, expected)) {
|
|
||||||
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method }, 'CSRF token mismatch');
|
|
||||||
return res.status(403).send('CSRF token invalid. Please reload the page.');
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate CSRF token from req.body._csrf (for use inside multer handlers).
|
* Validate CSRF token from req.body._csrf (for use inside multer handlers).
|
||||||
* Returns true if valid, false otherwise. Sends 403 response on failure.
|
* Returns true if valid, false otherwise. Sends 403 response on failure.
|
||||||
|
* NOTE: Disabled for Tor / onion zone compatibility.
|
||||||
*/
|
*/
|
||||||
export function validateCsrfFromBody(req, res) {
|
export function validateCsrfFromBody(req, res) {
|
||||||
const token = req.body?._csrf;
|
|
||||||
const cookieToken = req.cookies?.[CSRF_COOKIE];
|
|
||||||
|
|
||||||
if (!token || !cookieToken) {
|
|
||||||
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method }, 'CSRF token missing (multipart)');
|
|
||||||
res.status(403).send('CSRF token missing. Please reload the page.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const provided = Buffer.from(token, 'utf8');
|
|
||||||
const expected = Buffer.from(cookieToken, 'utf8');
|
|
||||||
if (provided.length !== expected.length || !crypto.timingSafeEqual(provided, expected)) {
|
|
||||||
logger.warn({ ip: req.ip, url: req.originalUrl, method: req.method }, 'CSRF token mismatch (multipart)');
|
|
||||||
res.status(403).send('CSRF token invalid. Please reload the page.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,62 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="nav-footer">
|
<div class="nav-footer">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2" role="button" data-bs-toggle="modal" data-bs-target="#versionModal" style="cursor:pointer;" title="View version history">
|
||||||
<svg class="sa-icon sa-thin">
|
<svg class="sa-icon sa-thin">
|
||||||
<use href="/icons/sprite.svg#tag"></use>
|
<use href="/icons/sprite.svg#tag"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="fs-xs opacity-70">v1.0</span>
|
<span class="fs-xs opacity-70">v1.2.0</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
<!-- Version History Modal -->
|
||||||
|
<div class="modal fade" id="versionModal" tabindex="-1" aria-labelledby="versionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="versionModalLabel">Version History</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-info mb-3">
|
||||||
|
<strong>Current:</strong> v1.2.0 · 2026-07-08
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="fw-bold mb-2">v1.2.0 <span class="text-muted fs-sm">— 2026-07-08</span></h6>
|
||||||
|
<ul class="small mb-3">
|
||||||
|
<li><span class="badge bg-warning text-dark">fix</span> Disabled CSRF checks in admin panel for Tor / onion zone compatibility</li>
|
||||||
|
<li><span class="badge bg-warning text-dark">fix</span> Fixed "Invalid wallet type" error in Telegram bot purchase flow</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Added version history modal in admin sidebar</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h6 class="fw-bold mb-2">v1.1.0 <span class="text-muted fs-sm">— 2026-07-02</span></h6>
|
||||||
|
<ul class="small mb-3">
|
||||||
|
<li><span class="badge bg-primary">feat</span> Mercuryo gateway, crypto QR deposit, mono products, wallet auto-refresh</li>
|
||||||
|
<li><span class="badge bg-warning text-dark">fix</span> CSRF cookie sameSite=false for Tor, auth cookie fix, async handlers</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Draggable dashboard panels + business KPI redesign</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> SmartAdmin template redesign + security hardening</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h6 class="fw-bold mb-2">v1.0.0 <span class="text-muted fs-sm">— 2026-06-24</span></h6>
|
||||||
|
<ul class="small mb-0">
|
||||||
|
<li><span class="badge bg-primary">feat</span> Initial release — Telegram shop bot with admin panel</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Crypto wallets (BTC, LTC, ETH, USDT, USDC)</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Product catalog with locations, categories, subcategories</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Purchase system with hidden content delivery</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Admin panel with dashboard, wallets, users, purchases, audit log</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> Tor proxy support (.onion access)</li>
|
||||||
|
<li><span class="badge bg-primary">feat</span> i18n localization (en/es/de)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span class="text-muted small">Update version in <code>VERSION.md</code> + <code>app-sidebar.ejs</code></span>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="backdrop" data-action="toggle-swap" data-toggleclass="app-mobile-menu-open"></div>
|
<div class="backdrop" data-action="toggle-swap" data-toggleclass="app-mobile-menu-open"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const WALLET_TYPES = new Set(['BTC', 'LTC', 'ETH', 'USDT', 'USDC']);
|
const WALLET_TYPES = new Set(['BTC', 'LTC', 'ETH', 'USDT', 'USDC', 'main', 'bonus']);
|
||||||
|
|
||||||
export default class Validators {
|
export default class Validators {
|
||||||
static isValidLocation(country, city, district) {
|
static isValidLocation(country, city, district) {
|
||||||
|
|||||||
Reference in New Issue
Block a user