diff --git a/app/views/ipi-demo-auth.ejs b/app/views/ipi-demo-auth.ejs
new file mode 100644
index 0000000..b6d36d4
--- /dev/null
+++ b/app/views/ipi-demo-auth.ejs
@@ -0,0 +1,32 @@
+
+
+
+
+
+ <%= title %>
+
+
+
+
+
IPI Demo Access
+
Enter `IPI_DEMO_KEY` to access demo dashboard.
+ <% if (error) { %>
+
<%= error %>
+ <% } %>
+
+
+
+
+
diff --git a/server.js b/server.js
index a249a0b..fb8abc0 100644
--- a/server.js
+++ b/server.js
@@ -57,6 +57,7 @@ app.set("trust proxy", 1);
* =========================
*/
app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(
@@ -305,11 +306,59 @@ app.post("/deploy", deploymentController.deployment);
* IPI DEMO UI
* =========================
*/
-app.get("/ipi-demo", ipiDemoController.renderPage);
-app.get("/ipi-demo/data", ipiDemoController.getData);
-app.get("/ipi-demo/table-data", ipiDemoController.getTableData);
-app.get("/ipi-demo/run-status", ipiDemoController.getRunStatus);
-app.post("/ipi-demo/run-month", ipiDemoController.runMonthCalculation);
+const requireIpiDemoAuth = (req, res, next) => {
+ const demoKey = process.env.IPI_DEMO_KEY;
+ if (!demoKey) return next();
+
+ if (req.cookies.ipi_demo_auth === "1") return next();
+
+ if (req.path.startsWith("/ipi-demo") && req.method === "GET" && req.headers.accept?.includes("text/html")) {
+ return res.redirect("/ipi-demo-auth");
+ }
+
+ return res.status(401).json({
+ success: false,
+ message: "Unauthorized demo access",
+ });
+};
+
+app.get("/ipi-demo-auth", (req, res) => {
+ if (!process.env.IPI_DEMO_KEY) return res.redirect("/ipi-demo");
+ if (req.cookies.ipi_demo_auth === "1") return res.redirect("/ipi-demo");
+ return res.render("ipi-demo-auth", { title: "IPI Demo Access", error: null });
+});
+
+app.post("/ipi-demo-auth", (req, res) => {
+ const expectedKey = process.env.IPI_DEMO_KEY;
+ if (!expectedKey) return res.redirect("/ipi-demo");
+
+ const providedKey = String(req.body.key || "").trim();
+ if (providedKey !== expectedKey) {
+ return res.status(401).render("ipi-demo-auth", {
+ title: "IPI Demo Access",
+ error: "Invalid demo key",
+ });
+ }
+
+ res.cookie("ipi_demo_auth", "1", {
+ httpOnly: true,
+ sameSite: "lax",
+ secure: isProd,
+ maxAge: 12 * 60 * 60 * 1000,
+ });
+ return res.redirect("/ipi-demo");
+});
+
+app.post("/ipi-demo-logout", (req, res) => {
+ res.clearCookie("ipi_demo_auth");
+ return res.redirect("/ipi-demo-auth");
+});
+
+app.get("/ipi-demo", requireIpiDemoAuth, ipiDemoController.renderPage);
+app.get("/ipi-demo/data", requireIpiDemoAuth, ipiDemoController.getData);
+app.get("/ipi-demo/table-data", requireIpiDemoAuth, ipiDemoController.getTableData);
+app.get("/ipi-demo/run-status", requireIpiDemoAuth, ipiDemoController.getRunStatus);
+app.post("/ipi-demo/run-month", requireIpiDemoAuth, ipiDemoController.runMonthCalculation);
/**