import createError from "http-errors"; import express from "express"; import compression from "compression"; import path from "path"; import cookieParser from "cookie-parser"; import csurf from "csurf"; import rateLimit from "express-rate-limit"; import session from "express-session"; import FileStore from "session-file-store"; import routerLogin from "./routes/login.js"; import routerDashboard from "./routes/dashboard.js"; import routerApi from "./routes/api.js"; import routerLots from "./routes/lots.js"; import routerMaps from "./routes/maps.js"; import routerLotOccupancies from "./routes/lotOccupancies.js"; import routerWorkOrders from "./routes/workOrders.js"; import routerReports from "./routes/reports.js"; import routerAdmin from "./routes/admin.js"; import * as configFunctions from "./helpers/functions.config.js"; import * as dateTimeFns from "@cityssm/expressjs-server-js/dateTimeFns.js"; import * as stringFns from "@cityssm/expressjs-server-js/stringFns.js"; import * as htmlFns from "@cityssm/expressjs-server-js/htmlFns.js"; import { version } from "./version.js"; import * as databaseInitializer from "./helpers/initializer.database.js"; import debug from "debug"; import { apiGetHandler } from "./handlers/permissions.js"; import { getSafeRedirectURL } from "./helpers/functions.authentication.js"; const debugApp = debug("lot-occupancy-system:app"); databaseInitializer.initializeDatabase(); const __dirname = "."; export const app = express(); if (!configFunctions.getProperty("reverseProxy.disableEtag")) { app.set("etag", false); } app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs"); if (!configFunctions.getProperty("reverseProxy.disableCompression")) { app.use(compression()); } app.use((request, _response, next) => { debugApp(`${request.method} ${request.url}`); next(); }); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(csurf({ cookie: true })); const limiter = rateLimit({ windowMs: 1000, max: 25 * Math.max(3, configFunctions.getProperty("users.canLogin").length) }); app.use(limiter); const urlPrefix = configFunctions.getProperty("reverseProxy.urlPrefix"); if (urlPrefix !== "") { debugApp("urlPrefix = " + urlPrefix); } app.use(urlPrefix, express.static(path.join("public"))); app.use(urlPrefix + "/lib/fa", express.static(path.join("node_modules", "@fortawesome", "fontawesome-free"))); app.use(urlPrefix + "/lib/cityssm-bulma-webapp-js", express.static(path.join("node_modules", "@cityssm", "bulma-webapp-js"))); app.use(urlPrefix + "/lib/cityssm-bulma-js", express.static(path.join("node_modules", "@cityssm", "bulma-js", "dist"))); app.use(urlPrefix + "/lib/leaflet", express.static(path.join("node_modules", "leaflet", "dist"))); app.use(urlPrefix + "/lib/randomcolor", express.static(path.join("node_modules", "randomcolor"))); const sessionCookieName = configFunctions.getProperty("session.cookieName"); const FileStoreSession = FileStore(session); app.use(session({ store: new FileStoreSession({ path: "./data/sessions", logFn: debug("general-licence-manager:session"), retries: 10 }), name: sessionCookieName, secret: configFunctions.getProperty("session.secret"), resave: true, saveUninitialized: false, rolling: true, cookie: { maxAge: configFunctions.getProperty("session.maxAgeMillis"), sameSite: "strict" } })); app.use((request, response, next) => { if (request.cookies[sessionCookieName] && !request.session.user) { response.clearCookie(sessionCookieName); } next(); }); const sessionChecker = (request, response, next) => { if (request.session.user && request.cookies[sessionCookieName]) { return next(); } const redirectUrl = getSafeRedirectURL(request.originalUrl); return response.redirect(`${urlPrefix}/login?redirect=${redirectUrl}`); }; app.use((request, response, next) => { response.locals.buildNumber = version; response.locals.user = request.session.user; response.locals.csrfToken = request.csrfToken(); response.locals.configFunctions = configFunctions; response.locals.dateTimeFunctions = dateTimeFns; response.locals.stringFunctions = stringFns; response.locals.htmlFunctions = htmlFns; response.locals.urlPrefix = configFunctions.getProperty("reverseProxy.urlPrefix"); next(); }); app.get(urlPrefix + "/", sessionChecker, (_request, response) => { response.redirect(urlPrefix + "/dashboard"); }); app.use(urlPrefix + "/dashboard", sessionChecker, routerDashboard); app.use(urlPrefix + "/api/:apiKey", apiGetHandler, routerApi); app.use(urlPrefix + "/lots", sessionChecker, routerLots); app.use(urlPrefix + "/maps", sessionChecker, routerMaps); app.use(urlPrefix + "/lotOccupancies", sessionChecker, routerLotOccupancies); app.use(urlPrefix + "/workOrders", sessionChecker, routerWorkOrders); app.use(urlPrefix + "/reports", sessionChecker, routerReports); app.use(urlPrefix + "/admin", sessionChecker, routerAdmin); app.all(urlPrefix + "/keepAlive", (_request, response) => { response.json(true); }); app.use(urlPrefix + "/login", routerLogin); app.get(urlPrefix + "/logout", (request, response) => { if (request.session.user && request.cookies[sessionCookieName]) { request.session.destroy(null); request.session = undefined; response.clearCookie(sessionCookieName); response.redirect(urlPrefix + "/"); } else { response.redirect(urlPrefix + "/login"); } }); app.use((_request, _response, next) => { next(createError(404)); }); app.use((error, request, response) => { response.locals.message = error.message; response.locals.error = request.app.get("env") === "development" ? error : {}; response.status(error.status || 500); response.render("error"); }); export default app;