'use strict'; require('dotenv').config(); const path = require('path'); const express = require('express'); const session = require('express-session'); const SqliteStore = require('better-sqlite3-session-store')(session); const { db, init, migrate } = require('./db'); const { seedDefaults } = require('./lib/templates'); const { requireAuth, requireAdmin } = require('./middleware/auth'); init(); migrate(); seedDefaults(); const app = express(); app.disable('x-powered-by'); app.set('trust proxy', 1); app.use(express.json({ limit: '2mb' })); app.use(express.urlencoded({ extended: true })); app.use(session({ store: new SqliteStore({ client: db, expired: { clear: true, intervalMs: 15 * 60 * 1000 }, }), secret: process.env.SESSION_SECRET || 'change-me-in-production', resave: false, saveUninitialized: false, cookie: { httpOnly: true, sameSite: 'lax', secure: String(process.env.COOKIE_SECURE || 'false') === 'true', maxAge: 1000 * 60 * 60 * 8, // 8h }, })); // ---- First-boot setup gate ------------------------------------------- // While the instance has no users, every page redirects to the setup wizard // so the first administrator can be created through the browser. const setup = require('./routes/setup'); app.use('/api/setup', setup.router); app.use((req, res, next) => { const url = req.originalUrl || req.url; // Always allow the setup API, the wizard page itself, and static assets // (css/js) — otherwise the wizard could not render. if (url.startsWith('/api/setup') || url.startsWith('/setup.html') || url.startsWith('/css/') || url.startsWith('/js/') || url.startsWith('/assets/')) return next(); if (setup.needsSetup()) { if (url.startsWith('/api/')) { return res.status(503).json({ error: 'Setup required. Open the site in a browser to create the administrator account.' }); } return res.redirect('/setup.html'); } // Setup is done — the wizard must never be reachable again. next(); }); // ---- API routes ------------------------------------------------------- app.use('/api/auth', require('./routes/auth')); app.use('/api/manage', require('./routes/manage')); // admin only (enforced inside) app.use('/api/datacreate', require('./routes/datacreate')); // admin only (enforced inside) app.use('/api/users', require('./routes/users')); // admin only (enforced inside) app.use('/api/versions', require('./routes/versions')); // read: all users, write: admin app.use('/api/mail', require('./routes/mail')); // admin only (enforced inside) app.use('/api/legislation', require('./routes/legislation')); // read: all users, write: admin app.use('/api/storage', require('./routes/storage')); // admin only (enforced inside) app.use('/api/data', require('./routes/data')); app.use('/api/logs', require('./routes/logs')); // admin only (enforced inside) app.use('/settings', require('./routes/settings')); // brand images served here // ---- Page gating ------------------------------------------------------ // Serve the login page and static assets openly; gate the app pages. const PUB = path.join(__dirname, 'public'); app.get('/', (req, res) => res.redirect(req.session.user ? '/view-data.html' : '/login.html')); // The setup wizard is only reachable while no users exist. app.get('/setup.html', (req, res) => { if (!setup.needsSetup()) return res.redirect('/login.html'); res.sendFile(path.join(PUB, 'setup.html')); }); // Protect the admin-only HTML pages at the route level too (defence in depth) app.get('/data-management.html', requireAuth, requireAdmin, (req, res) => res.sendFile(path.join(PUB, 'data-management.html'))); app.get('/storage.html', requireAuth, requireAdmin, (req, res) => res.sendFile(path.join(PUB, 'storage.html'))); app.get('/logs.html', requireAuth, requireAdmin, (req, res) => res.sendFile(path.join(PUB, 'logs.html'))); // Signed-in pages app.get('/view-data.html', requireAuth, (req, res) => res.sendFile(path.join(PUB, 'view-data.html'))); app.get('/calendar-view.html', requireAuth, (req, res) => res.sendFile(path.join(PUB, 'calendar-view.html'))); app.get('/account.html', requireAuth, (req, res) => res.sendFile(path.join(PUB, 'account.html'))); app.get('/version-control.html', requireAuth, requireAdmin, (req, res) => res.sendFile(path.join(PUB, 'version-control.html'))); app.get('/legislation.html', requireAuth, (req, res) => res.sendFile(path.join(PUB, 'legislation.html'))); app.use(express.static(PUB)); // login.html, css, js, assets // ---- 404 / errors ----------------------------------------------------- app.use((req, res) => res.status(404).send('Not found.')); app.use((err, req, res, next) => { console.error(err); if (res.headersSent) return next(err); res.status(500).json({ error: err.message || 'Server error' }); }); const PORT = Number(process.env.PORT || 3000); app.listen(PORT, () => { console.log(`Infosec v2.3 running on http://localhost:${PORT}`); });