require('dotenv').config(); const express = require('express'); const cors = require('cors'); const session = require('express-session'); const PgSession = require('connect-pg-simple')(session); const { Pool, types } = require('pg'); const { requireAuth } = require('./lib/auth'); // Return bigint (oid 20) and numeric (oid 1700) as JS numbers instead of strings, // so apache-arrow's tableFromJSON infers Int/Float64 rather than Dictionary. types.setTypeParser(20, v => v === null ? null : Number(v)); types.setTypeParser(1700, v => v === null ? null : Number(v)); const app = express(); // Sessions ride on a cookie, so a wildcard CORS origin would let any site make // credentialed calls on behalf of a logged-in user. The UI is served from this // same origin and needs no CORS at all; set CORS_ORIGIN only for a separate // front-end host, and it is then allowed by name, never by wildcard. if (process.env.CORS_ORIGIN) { app.use(cors({ origin: process.env.CORS_ORIGIN.split(',').map(o => o.trim()), credentials: true })); } app.use(express.json()); app.use(express.static('public/app')); const pool = new Pool({ host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT) || 5432, database: process.env.DB_NAME, user: process.env.DB_USER, password: process.env.DB_PASSWORD, ssl: false }); pool.on('error', (err) => { console.error('pg pool error', err); }); // ── Authentication ──────────────────────────────────────────── // Refuse to boot without a secret rather than fall back to a default one: // a predictable secret means forgeable session cookies. const sessionSecret = process.env.SESSION_SECRET; if (!sessionSecret) { console.error('SESSION_SECRET is not set. Run: ./pf.sh config'); process.exit(1); } // TLS terminates at the reverse proxy, so express has to trust its headers for // req.ip (the login throttle) and for secure-cookie detection to be right. app.set('trust proxy', process.env.TRUST_PROXY || 1); const cookieSecure = process.env.COOKIE_SECURE !== 'false'; if (!cookieSecure) { console.warn('COOKIE_SECURE=false — session cookie will be sent over plain HTTP.'); } app.use(session({ name: 'pf.sid', store: new PgSession({ pool, schemaName: 'pf', tableName: 'session', createTableIfMissing: false }), secret: sessionSecret, resave: false, saveUninitialized: false, rolling: true, cookie: { httpOnly: true, sameSite: 'lax', secure: cookieSecure, maxAge: 1000 * 60 * 60 * 12, }, })); app.use('/api', require('./routes/auth')(pool)); // Everything below this line requires a session. app.use('/api', requireAuth); app.use('/api', require('./routes/tables')(pool)); app.use('/api', require('./routes/sources')(pool)); app.use('/api', require('./routes/versions')(pool)); app.use('/api', require('./routes/operations')(pool)); app.use('/api', require('./routes/log')(pool)); const port = process.env.PORT || 3010; app.listen(port, '0.0.0.0', () => console.log(`pf_app started on port ${port}`));