From 91c37d7c9a5c54906f91112079b3be20974fefb7 Mon Sep 17 00:00:00 2001
From: Debora Serra <debora.r.serra@gmail.com>
Date: Tue, 17 Dec 2024 07:56:08 -0800
Subject: [PATCH 1/5] feat: create ip filter middleware

---
 backend/src/middleware/ipFilter.middleware.js | 50 +++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 backend/src/middleware/ipFilter.middleware.js

diff --git a/backend/src/middleware/ipFilter.middleware.js b/backend/src/middleware/ipFilter.middleware.js
new file mode 100644
index 00000000..01f5f192
--- /dev/null
+++ b/backend/src/middleware/ipFilter.middleware.js
@@ -0,0 +1,50 @@
+const parseIpRange = (allowedIpsEnv) => {
+  if (!allowedIpsEnv) return [];
+
+  const ranges = allowedIpsEnv.split(",").map((range) => range.trim());
+  // IP range expected format is "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
+  return ranges.map((range) => {
+    const [baseIp, endRange] = range.split("/");
+    if (!baseIp || !endRange) throw new Error("Invalid IP range format");
+    const rangeParts = endRange.split("-");
+    return {
+      baseIp,
+      rangeStart: parseInt(rangeParts[0]),
+      rangeEnd: parseInt(rangeParts[1]),
+    };
+  });
+};
+
+const isIpAllowed = (currentIp, allowedRanges) => {
+  const ipParts = currentIp.split(".");
+  const startIp = ipParts.slice(0, 3).join(".");
+  const lastOctet = parseInt(ipParts[3], 10);
+
+  for (const range of allowedRanges) {
+    if (
+      startIp === range.baseIp &&
+      lastOctet >= range.rangeStart &&
+      lastOctet <= range.rangeEnd
+    ) {
+      return true;
+    }
+  }
+  return false;
+};
+
+const ipFilter = async (req, res, next) => {
+  try {
+    const currentIp =
+      req.headers["x-forwarded-for"] || req.connection.remoteAddress;
+    const allowedRanges = parseIpRange(process.env.ALLOWED_IPS_RANGE);
+
+    if (!isIpAllowed(currentIp, allowedRanges)) {
+      return res.status(401).json({ error: "Unauthorized" });
+    }
+
+    next();
+  } catch (err) {
+    console.error("IP Filter Error:", err.message);
+    return res.status(500).json({ error: "Internal Server Error" });
+  }
+};

From b85f14c87ea80b8f298c9a4aac684e973ca2f596 Mon Sep 17 00:00:00 2001
From: Debora Serra <debora.r.serra@gmail.com>
Date: Tue, 17 Dec 2024 07:58:26 -0800
Subject: [PATCH 2/5] feat: add IP variable to env files

---
 backend/.env                                  | 3 +++
 backend/.env.production                       | 3 +++
 backend/.env.test                             | 3 +++
 backend/src/middleware/ipFilter.middleware.js | 1 -
 4 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/backend/.env b/backend/.env
index f8928b53..ccdc9865 100644
--- a/backend/.env
+++ b/backend/.env
@@ -18,3 +18,6 @@ TEST_DB_PASSWORD=password123
 TEST_DB_NAME=onboarding_db_test
 TEST_DB_HOST=localhost
 TEST_DB_PORT=5432
+
+# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
+ALLOWED_IPS_RANGE=127.0.0/1-1
diff --git a/backend/.env.production b/backend/.env.production
index c2268fbd..d3b11e05 100644
--- a/backend/.env.production
+++ b/backend/.env.production
@@ -9,3 +9,6 @@ PROD_DB_PORT=5432
 
 # JWT Secret Key
 JWT_SECRET=your_prod_jwt_secret_key_here
+
+# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
+ALLOWED_IPS_RANGE=
\ No newline at end of file
diff --git a/backend/.env.test b/backend/.env.test
index d965cb36..22f17c40 100644
--- a/backend/.env.test
+++ b/backend/.env.test
@@ -13,3 +13,6 @@ POSTGRES_DB=onboarding_db_test
 
 # JWT Secret Key
 JWT_SECRET=your_test_jwt_secret_key_here
+
+# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
+ALLOWED_IPS_RANGE=127.0.0/1-1
\ No newline at end of file
diff --git a/backend/src/middleware/ipFilter.middleware.js b/backend/src/middleware/ipFilter.middleware.js
index 01f5f192..81986d1c 100644
--- a/backend/src/middleware/ipFilter.middleware.js
+++ b/backend/src/middleware/ipFilter.middleware.js
@@ -2,7 +2,6 @@ const parseIpRange = (allowedIpsEnv) => {
   if (!allowedIpsEnv) return [];
 
   const ranges = allowedIpsEnv.split(",").map((range) => range.trim());
-  // IP range expected format is "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
   return ranges.map((range) => {
     const [baseIp, endRange] = range.split("/");
     if (!baseIp || !endRange) throw new Error("Invalid IP range format");

From 97e498360da652d6c0578e2f4e6f2be96d0b26f9 Mon Sep 17 00:00:00 2001
From: Debora Serra <debora.r.serra@gmail.com>
Date: Tue, 17 Dec 2024 08:00:00 -0800
Subject: [PATCH 3/5] feat: add ip filter middleware to server

---
 backend/src/middleware/ipFilter.middleware.js |  2 +
 backend/src/server.js                         | 74 ++++++++++---------
 2 files changed, 40 insertions(+), 36 deletions(-)

diff --git a/backend/src/middleware/ipFilter.middleware.js b/backend/src/middleware/ipFilter.middleware.js
index 81986d1c..c783e0ef 100644
--- a/backend/src/middleware/ipFilter.middleware.js
+++ b/backend/src/middleware/ipFilter.middleware.js
@@ -47,3 +47,5 @@ const ipFilter = async (req, res, next) => {
     return res.status(500).json({ error: "Internal Server Error" });
   }
 };
+
+module.exports = ipFilter;
\ No newline at end of file
diff --git a/backend/src/server.js b/backend/src/server.js
index 5a8d530b..e85dfe51 100644
--- a/backend/src/server.js
+++ b/backend/src/server.js
@@ -1,27 +1,28 @@
-const express = require('express');
-const cors = require('cors');
-const helmet = require('helmet');
-const dotenv = require('dotenv');
-const bodyParser = require('body-parser');
-const jsonErrorMiddleware = require('./middleware/jsonError.middleware');
-const fileSizeValidator = require('./middleware/fileSizeValidator.middleware');
-const { MAX_FILE_SIZE } = require('./utils/constants.helper');
+const express = require("express");
+const cors = require("cors");
+const helmet = require("helmet");
+const dotenv = require("dotenv");
+const bodyParser = require("body-parser");
+const jsonErrorMiddleware = require("./middleware/jsonError.middleware");
+const fileSizeValidator = require("./middleware/fileSizeValidator.middleware");
+const { MAX_FILE_SIZE } = require("./utils/constants.helper");
+const ipFilter = require("./middleware/ipFilter.middleware");
 
 // Load environment variables from .env file
 dotenv.config();
 
-const authRoutes = require('./routes/auth.routes');
-const userRoutes = require('./routes/user.routes');
-const mocks = require('./routes/mocks.routes');
-const popup = require('./routes/popup.routes');
-const guide_log = require('./routes/guidelog.routes');
-const banner = require('./routes/banner.routes');
-const teamRoutes = require('./routes/team.routes');
-const hint = require('./routes/hint.routes');
-const tourRoutes = require('./routes/tour.routes');
-const linkRoutes = require('./routes/link.routes');
-const helperLinkRoutes = require('./routes/helperLink.routes');
-const guideRoutes = require('./routes/guide.routes');
+const authRoutes = require("./routes/auth.routes");
+const userRoutes = require("./routes/user.routes");
+const mocks = require("./routes/mocks.routes");
+const popup = require("./routes/popup.routes");
+const guide_log = require("./routes/guidelog.routes");
+const banner = require("./routes/banner.routes");
+const teamRoutes = require("./routes/team.routes");
+const hint = require("./routes/hint.routes");
+const tourRoutes = require("./routes/tour.routes");
+const linkRoutes = require("./routes/link.routes");
+const helperLinkRoutes = require("./routes/helperLink.routes");
+const guideRoutes = require("./routes/guide.routes");
 
 const app = express();
 
@@ -29,36 +30,37 @@ app.use(cors());
 app.use(helmet());
 app.use(bodyParser.json({ limit: MAX_FILE_SIZE }));
 app.use(jsonErrorMiddleware);
+app.use(ipFilter);
 // app.use(fileSizeValidator);
 
 const { sequelize } = require("./models");
 
 sequelize
   .authenticate()
-  .then(() => console.log('Database connected...'))
-  .catch((err) => console.log('Error: ' + err));
+  .then(() => console.log("Database connected..."))
+  .catch((err) => console.log("Error: " + err));
 
 sequelize
   .sync({ force: false })
   .then(() => console.log("Models synced with the database..."))
   .catch((err) => console.log("Error syncing models: " + err));
 
-app.use('/api/auth', authRoutes);
-app.use('/api/users', userRoutes);
-app.use('/api/mock/', mocks);
-app.use('/api/popup', popup);
-app.use('/api/guide_log', guide_log);
-app.use('/api/banner', banner);
-app.use('/api/team', teamRoutes);
-app.use('/api/guide', guideRoutes);
-app.use('/api/hint', hint);
-app.use('/api/tour', tourRoutes);
-app.use('/api/link', linkRoutes);
-app.use('/api/helper-link', helperLinkRoutes);
+app.use("/api/auth", authRoutes);
+app.use("/api/users", userRoutes);
+app.use("/api/mock/", mocks);
+app.use("/api/popup", popup);
+app.use("/api/guide_log", guide_log);
+app.use("/api/banner", banner);
+app.use("/api/team", teamRoutes);
+app.use("/api/guide", guideRoutes);
+app.use("/api/hint", hint);
+app.use("/api/tour", tourRoutes);
+app.use("/api/link", linkRoutes);
+app.use("/api/helper-link", helperLinkRoutes);
 
 app.use((err, req, res, next) => {
   console.error(err.stack);
-  res.status(500).json({ message: 'Internal Server Error' });
+  res.status(500).json({ message: "Internal Server Error" });
 });
 
-module.exports = app;
\ No newline at end of file
+module.exports = app;

From 3240a8f841529f4e44142db2cce20af3538923a1 Mon Sep 17 00:00:00 2001
From: Debora Serra <debora.r.serra@gmail.com>
Date: Wed, 18 Dec 2024 10:17:35 -0800
Subject: [PATCH 4/5] chore: update ip middleware with new logic

---
 backend/.env                                  |  6 ++--
 backend/.env.production                       |  6 ++--
 backend/.env.test                             |  6 ++--
 backend/src/middleware/ipFilter.middleware.js | 31 +++++++++++++++----
 4 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/backend/.env b/backend/.env
index ccdc9865..3cdfaaac 100644
--- a/backend/.env
+++ b/backend/.env
@@ -19,5 +19,7 @@ TEST_DB_NAME=onboarding_db_test
 TEST_DB_HOST=localhost
 TEST_DB_PORT=5432
 
-# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
-ALLOWED_IPS_RANGE=127.0.0/1-1
+# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255) separated by comma
+ALLOWED_IP_RANGE=11.22.33/10-200, 192.168.65/1-255
+# Allowed IP addresses for the API separated by comma
+ALLOWED_IPS=127.0.0.1, 11.22.33.44, 11.22.33.45, 11.22.33.46, 192.168.65.1
diff --git a/backend/.env.production b/backend/.env.production
index d3b11e05..ec404c01 100644
--- a/backend/.env.production
+++ b/backend/.env.production
@@ -10,5 +10,7 @@ PROD_DB_PORT=5432
 # JWT Secret Key
 JWT_SECRET=your_prod_jwt_secret_key_here
 
-# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
-ALLOWED_IPS_RANGE=
\ No newline at end of file
+# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255) separated by comma
+ALLOWED_IP_RANGE=11.22.33/10-200
+# Allowed IP addresses for the API separated by comma
+ALLOWED_IPS=11.22.33.44, 11.22.33.45, 11.22.33.46
\ No newline at end of file
diff --git a/backend/.env.test b/backend/.env.test
index 22f17c40..09be3c11 100644
--- a/backend/.env.test
+++ b/backend/.env.test
@@ -14,5 +14,7 @@ POSTGRES_DB=onboarding_db_test
 # JWT Secret Key
 JWT_SECRET=your_test_jwt_secret_key_here
 
-# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255)
-ALLOWED_IPS_RANGE=127.0.0/1-1
\ No newline at end of file
+# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255) separated by comma
+ALLOWED_IP_RANGE=11.22.33/10-200, 192.168.65/1-255
+# Allowed IP addresses for the API separated by comma
+ALLOWED_IPS=127.0.0.1, 11.22.33.44, 11.22.33.45, 11.22.33.46, 192.168.65.1
\ No newline at end of file
diff --git a/backend/src/middleware/ipFilter.middleware.js b/backend/src/middleware/ipFilter.middleware.js
index c783e0ef..07327bb7 100644
--- a/backend/src/middleware/ipFilter.middleware.js
+++ b/backend/src/middleware/ipFilter.middleware.js
@@ -1,7 +1,14 @@
+const getIpFromRequest = (req) => {
+  const forwardedFor = req.headers["x-forwarded-for"];
+  const remoteAddress = req.connection.remoteAddress;
+  console.log({ forwardedFor, remoteAddress });
+  return (forwardedFor || remoteAddress).replace("::ffff:", "");
+};
+
 const parseIpRange = (allowedIpsEnv) => {
   if (!allowedIpsEnv) return [];
 
-  const ranges = allowedIpsEnv.split(",").map((range) => range.trim());
+  const ranges = allowedIpsEnv.split(", ").map((range) => range.trim());
   return ranges.map((range) => {
     const [baseIp, endRange] = range.split("/");
     if (!baseIp || !endRange) throw new Error("Invalid IP range format");
@@ -33,11 +40,23 @@ const isIpAllowed = (currentIp, allowedRanges) => {
 
 const ipFilter = async (req, res, next) => {
   try {
-    const currentIp =
-      req.headers["x-forwarded-for"] || req.connection.remoteAddress;
-    const allowedRanges = parseIpRange(process.env.ALLOWED_IPS_RANGE);
+    const currentIp = getIpFromRequest(req);
+    const allowedIpsEnv = process.env.ALLOWED_IPS;
+    const allowedRanges = parseIpRange(process.env.ALLOWED_IP_RANGE);
+    if (!allowedIpsEnv && !allowedRanges.length) {
+      next();
+      return;
+    } else if (!allowedIpsEnv) {
+      if (!isIpAllowed(currentIp, allowedRanges)) {
+        return res.status(401).json({ error: "Unauthorized" });
+      }
+      next();
+      return;
+    }
+
+    const allowedIps = allowedIpsEnv.split(", ").map((ip) => ip.trim());
 
-    if (!isIpAllowed(currentIp, allowedRanges)) {
+    if (!allowedIps.includes(currentIp)) {
       return res.status(401).json({ error: "Unauthorized" });
     }
 
@@ -48,4 +67,4 @@ const ipFilter = async (req, res, next) => {
   }
 };
 
-module.exports = ipFilter;
\ No newline at end of file
+module.exports = ipFilter;

From f1cf1599d4fa68e0369f00c4a6855c1ee86f80e9 Mon Sep 17 00:00:00 2001
From: Debora Serra <debora.r.serra@gmail.com>
Date: Wed, 18 Dec 2024 10:21:50 -0800
Subject: [PATCH 5/5] chore: remove console log

---
 backend/src/middleware/ipFilter.middleware.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/backend/src/middleware/ipFilter.middleware.js b/backend/src/middleware/ipFilter.middleware.js
index 07327bb7..c6877881 100644
--- a/backend/src/middleware/ipFilter.middleware.js
+++ b/backend/src/middleware/ipFilter.middleware.js
@@ -1,7 +1,6 @@
 const getIpFromRequest = (req) => {
   const forwardedFor = req.headers["x-forwarded-for"];
   const remoteAddress = req.connection.remoteAddress;
-  console.log({ forwardedFor, remoteAddress });
   return (forwardedFor || remoteAddress).replace("::ffff:", "");
 };