116 lines
4.7 KiB
JavaScript
116 lines
4.7 KiB
JavaScript
'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/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('/account.html', requireAuth, (req, res) =>
|
|
res.sendFile(path.join(PUB, 'account.html')));
|
|
app.get('/version-control.html', requireAuth, (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(`Martinhal ISDSS v1.5 Patch 0.2 running on http://localhost:${PORT}`);
|
|
});
|