Anonymous Bitcoin-only fashion e-commerce with: - Vue 3 + Tailwind 4 frontend with glassmorphism dark/light design system - Express 5 + SQLite backend with BTCPay Server integration - Nostr identity (NIP-07/keypair) for anonymous purchase tracking - ChaCha20-Poly1305 encrypted shipping addresses - Admin panel with order/product/stock management - SVG logo splash animation with clip-path reveal - 5 seeded products across 4 categories Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import type { Request, Response, NextFunction } from 'express'
|
|
import crypto from 'node:crypto'
|
|
import { getDb } from '../db/connection.js'
|
|
|
|
export function adminAuth(req: Request, res: Response, next: NextFunction): void {
|
|
const token = req.cookies?.admin_session
|
|
if (!token) { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Authentication required' } }); return }
|
|
const db = getDb()
|
|
const session = db.prepare("SELECT token FROM admin_sessions WHERE token = ? AND expires_at > datetime('now')").get(token) as { token: string } | undefined
|
|
if (!session) { res.status(401).json({ error: { code: 'SESSION_EXPIRED', message: 'Session expired' } }); return }
|
|
next()
|
|
}
|
|
|
|
const loginAttempts = new Map<string, { count: number; resetAt: number }>()
|
|
|
|
export function rateLimit(req: Request, res: Response, next: NextFunction): void {
|
|
const ip = req.ip || 'unknown'
|
|
const now = Date.now()
|
|
const entry = loginAttempts.get(ip)
|
|
if (entry) {
|
|
if (now > entry.resetAt) { loginAttempts.set(ip, { count: 1, resetAt: now + 60_000 }) }
|
|
else if (entry.count >= 5) { res.status(429).json({ error: { code: 'RATE_LIMITED', message: 'Too many attempts' } }); return }
|
|
else { entry.count++ }
|
|
} else { loginAttempts.set(ip, { count: 1, resetAt: now + 60_000 }) }
|
|
next()
|
|
}
|
|
|
|
export function createSession(): string {
|
|
const token = crypto.randomBytes(32).toString('hex')
|
|
const db = getDb()
|
|
db.prepare("INSERT INTO admin_sessions (token, expires_at) VALUES (?, datetime('now', '+24 hours'))").run(token)
|
|
db.prepare("DELETE FROM admin_sessions WHERE expires_at < datetime('now')").run()
|
|
return token
|
|
}
|
|
|
|
export function deleteSession(token: string): void {
|
|
const db = getDb()
|
|
db.prepare('DELETE FROM admin_sessions WHERE token = ?').run(token)
|
|
}
|
|
|
|
export function verifyPassword(input: string): boolean {
|
|
const expected = process.env.ADMIN_PASSWORD
|
|
if (!expected) return false
|
|
const inputBuf = Buffer.from(input)
|
|
const expectedBuf = Buffer.from(expected)
|
|
if (inputBuf.length !== expectedBuf.length) return false
|
|
return crypto.timingSafeEqual(inputBuf, expectedBuf)
|
|
}
|