import { Router, Request, Response } from 'express'; import crypto from 'crypto'; import { auth } from '../config'; import { discoverTableIds, fetchAllRecords } from '../services/nocodbClient'; const router = Router(); interface UserRecord { Id: number; Name: string; PIN: string; Role: string; AllowedMuseums?: string; AllowedChannels?: string; } interface Session { name: string; role: string; createdAt: number; allowedMuseums: string; allowedChannels: string; } const sessions = new Map(); const SESSION_MAX_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days function generateSessionId(): string { return crypto.randomBytes(32).toString('hex'); } function getSession(sessionId: string): Session | null { const session = sessions.get(sessionId); if (!session) return null; if (Date.now() - session.createdAt > SESSION_MAX_AGE) { sessions.delete(sessionId); return null; } return session; } // POST /auth/login router.post('/login', async (req: Request, res: Response) => { const { pin } = req.body; if (!pin) { res.status(400).json({ error: 'PIN required' }); return; } // Check super admin PIN from env first if (auth.adminPin && pin === auth.adminPin) { const sessionId = generateSessionId(); sessions.set(sessionId, { name: 'Admin', role: 'admin', createdAt: Date.now(), allowedMuseums: '[]', allowedChannels: '[]' }); res.cookie('hihala_session', sessionId, { httpOnly: true, sameSite: 'lax', maxAge: SESSION_MAX_AGE, path: '/' }); res.json({ ok: true, name: 'Admin', role: 'admin', allowedMuseums: '[]', allowedChannels: '[]' }); return; } // Check NocoDB Users table try { const tables = await discoverTableIds(); if (tables['Users']) { const users = await fetchAllRecords(tables['Users']); const user = users.find(u => u.PIN === pin); if (user) { const sessionId = generateSessionId(); sessions.set(sessionId, { name: user.Name, role: user.Role || 'viewer', createdAt: Date.now(), allowedMuseums: user.AllowedMuseums || '[]', allowedChannels: user.AllowedChannels || '[]', }); res.cookie('hihala_session', sessionId, { httpOnly: true, sameSite: 'lax', maxAge: SESSION_MAX_AGE, path: '/' }); res.json({ ok: true, name: user.Name, role: user.Role || 'viewer', allowedMuseums: user.AllowedMuseums || '[]', allowedChannels: user.AllowedChannels || '[]', }); return; } } } catch (err) { console.warn('Failed to check Users table:', (err as Error).message); } res.status(401).json({ error: 'Invalid PIN' }); }); // GET /auth/check router.get('/check', (req: Request, res: Response) => { const sessionId = req.cookies?.hihala_session; const session = sessionId ? getSession(sessionId) : null; res.json({ authenticated: !!session, name: session?.name || null, role: session?.role || null, allowedMuseums: session?.allowedMuseums ?? '[]', allowedChannels: session?.allowedChannels ?? '[]', }); }); // POST /auth/logout router.post('/logout', (req: Request, res: Response) => { const sessionId = req.cookies?.hihala_session; if (sessionId) sessions.delete(sessionId); res.clearCookie('hihala_session', { path: '/' }); res.json({ ok: true }); }); export default router;