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:", ""); };