Files
antonym/server/middleware/adminAuth.ts
Dorian 54500a68e6 feat: scaffold Antonym fashion store
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>
2026-03-17 00:23:21 +00:00

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)
}