import "reflect-metadata"; import { NestFactory } from "@nestjs/core"; import { ValidationPipe } from "@nestjs/common"; import * as session from "express-session"; import * as passport from "passport"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); // Every request body is validated and stripped of unknown fields before it // reaches a controller — this is the structural replacement for the old // app's complete lack of input validation (src/core/db.php took every // $_POST field straight into a SQL string). app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }) ); const sessionSecret = process.env.SESSION_SECRET; if (!sessionSecret) { throw new Error("SESSION_SECRET must be set (see .env.example)"); } // Whether the session cookie carries the Secure flag. This CANNOT simply // follow NODE_ENV: express-session silently declines to send a Secure cookie // over a plain-HTTP connection, so a production image served over http://ial // issues no cookie at all. Login then returns 200 with a user, no session is // established, every later request 403s, and the UI loops back to /login — // which is exactly what happened on the first galactus deploy. // // Leave it ON wherever the app is reached over TLS. Turn it OFF only for a // deployment that is HTTP but reached over an already-encrypted transport // (the galactus install is Tailscale-only, so WireGuard encrypts the wire). // Behind a TLS-terminating proxy, set trust proxy instead of turning this off. // An EMPTY value counts as unset, not as "false". Compose interpolation turns // an absent `${SESSION_COOKIE_SECURE:-}` into the empty string, so testing // `!== undefined` here would silently drop the Secure flag on any deployment // that merely passes the variable through without setting it. const cookieSecureRaw = process.env.SESSION_COOKIE_SECURE; const cookieSecure = cookieSecureRaw ? cookieSecureRaw === "true" : process.env.NODE_ENV === "production"; app.use( session({ secret: sessionSecret, resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: cookieSecure, maxAge: 1000 * 60 * 60 * 8, // 8-hour session, matches a staff workday }, }) ); app.use(passport.initialize()); app.use(passport.session()); app.enableCors({ credentials: true, origin: process.env.WEB_ORIGIN ?? "http://localhost:3000" }); const port = process.env.PORT ? Number(process.env.PORT) : 3001; await app.listen(port); } bootstrap();