App Users
All shops that have installed Data4Autos. Toggle free access directly from this list.
// routes/adminPanel.js — password-protected admin panel for free-access shop management const express = require('express'); const crypto = require('crypto'); const { isShopAllowed, addShop, removeShop, listShops } = require('../freeAccessStore'); const { listTokens } = require('../tokenStore'); const { addMessage, readChat, markRead, listChats } = require('../chatStore'); const router = express.Router(); const ADMIN_USER = 'd4a-admin'; const ADMIN_PASS = 'Data4autos@2026.'; const COOKIE_NAME = 'd4a_admin_tok'; const COOKIE_MAX_AGE = 8 * 60 * 60 * 1000; // 8 hours // In-memory valid tokens (cleared on server restart) const validTokens = new Set(); // ── auth middleware ───────────────────────────────────────────────────────── function requireAuth(req, res, next) { const tok = req.cookies?.[COOKIE_NAME] || req.headers['x-admin-token']; if (tok && validTokens.has(tok)) return next(); if (req.path.startsWith('/api/')) return res.status(401).json({ error: 'Unauthorised' }); res.redirect('/d4a-admin'); } // ── cookie parser (lightweight, no dependency) ────────────────────────────── router.use((req, _res, next) => { const raw = req.headers.cookie || ''; req.cookies = Object.fromEntries( raw.split(';').map(c => c.trim().split('=').map(decodeURIComponent)) .filter(([k]) => k).map(([k, ...v]) => [k, v.join('=')]) ); next(); }); router.use(express.urlencoded({ extended: false })); router.use(express.json()); // ── login ──────────────────────────────────────────────────────────────────── router.post('/login', (req, res) => { const { username, password } = req.body; if (username === ADMIN_USER && password === ADMIN_PASS) { const token = crypto.randomBytes(32).toString('hex'); validTokens.add(token); setTimeout(() => validTokens.delete(token), COOKIE_MAX_AGE); res.setHeader('Set-Cookie', `${COOKIE_NAME}=${encodeURIComponent(token)}; HttpOnly; Path=/; Max-Age=${COOKIE_MAX_AGE / 1000}; SameSite=Lax`); return res.json({ ok: true }); } res.status(401).json({ error: 'Invalid credentials' }); }); // ── logout ─────────────────────────────────────────────────────────────────── router.post('/logout', (req, res) => { const tok = req.cookies?.[COOKIE_NAME]; if (tok) validTokens.delete(tok); res.setHeader('Set-Cookie', `${COOKIE_NAME}=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax`); res.json({ ok: true }); }); // ── check-auth ─────────────────────────────────────────────────────────────── router.get('/api/check-auth', (req, res) => { const tok = req.cookies?.[COOKIE_NAME]; if (tok && validTokens.has(tok)) return res.json({ ok: true }); res.status(401).json({ ok: false }); }); // ── shops API (protected) ──────────────────────────────────────────────────── router.get('/api/shops', requireAuth, (_req, res) => { res.json({ shops: listShops() }); }); router.post('/api/shops', requireAuth, (req, res) => { const { shop, expiresAt, note } = req.body; if (!shop) return res.status(400).json({ error: 'shop is required' }); const entry = addShop(shop.trim(), expiresAt || null, note || ''); res.json({ ok: true, shop: shop.trim(), entry }); }); router.delete('/api/shops/:shop', requireAuth, (req, res) => { removeShop(decodeURIComponent(req.params.shop)); res.json({ ok: true }); }); // ── all installed users (from tokens.json) merged with free-access status ──── router.get('/api/users', requireAuth, (_req, res) => { const tokens = listTokens(); const freeList = listShops(); const freeMap = Object.fromEntries(freeList.map(s => [s.shop, s])); const users = Object.entries(tokens).map(([shop, t]) => { const fa = freeMap[shop] || null; return { shop, savedAt: t.savedAt || null, hasToken: !!t.accessToken, freeAccess: !!fa && !fa.expired, freeEntry: fa || null, }; }); users.sort((a, b) => (b.savedAt || '').localeCompare(a.savedAt || '')); res.json({ users }); }); // ── chat API (admin-protected) ─────────────────────────────────────────────── router.get('/api/chats', requireAuth, (_req, res) => { res.json({ chats: listChats() }); }); router.get('/api/chats/:shop', requireAuth, (req, res) => { const shop = decodeURIComponent(req.params.shop); markRead(shop); res.json(readChat(shop)); }); router.post('/api/chats/:shop/reply', requireAuth, (req, res) => { const shop = decodeURIComponent(req.params.shop); const { text } = req.body; if (!text) return res.status(400).json({ error: 'text required' }); const msg = addMessage(shop, 'admin', text); res.json({ ok: true, message: msg }); }); // ── serve admin HTML (always — login gate is client-side) ──────────────────── router.get('/', (_req, res) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send(adminHtml()); }); // ── HTML ───────────────────────────────────────────────────────────────────── function adminHtml() { return `
All shops that have installed Data4Autos. Toggle free access directly from this list.
Select a conversation
Choose a shop from the left to view messages