From ef23d468a5209b98013a4270cf2447e4ff5f8d14 Mon Sep 17 00:00:00 2001 From: Ashutosh Sanjay More Date: Sun, 9 Aug 2026 11:38:17 +0530 Subject: [PATCH] Applied Arcjet Protection for rate limiting, spoofed bot detection , SQL injection , etc to all auth routes --- backend/package-lock.json | 257 ++++++++++++++++++++ backend/package.json | 2 + backend/src/lib/arcjet.js | 31 +++ backend/src/lib/env.js | 2 + backend/src/middleware/arcjet.middleware.js | 33 +++ backend/src/routes/auth.routes.js | 3 + 6 files changed, 328 insertions(+) create mode 100644 backend/src/lib/arcjet.js create mode 100644 backend/src/middleware/arcjet.middleware.js diff --git a/backend/package-lock.json b/backend/package-lock.json index 033b7d7..a8e3bd6 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@arcjet/inspect": "^1.5.0", + "@arcjet/node": "^1.5.0", "bcryptjs": "^3.0.3", "body-parser": "^2.3.0", "cookie-parser": "^1.4.7", @@ -22,6 +24,220 @@ "nodemon": "^3.1.14" } }, + "node_modules/@arcjet/analyze": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/analyze/-/analyze-1.5.0.tgz", + "integrity": "sha512-KK2PJyImXMaOKAcjugEr9AYdjsHRBGbsLg+YbnleOnVsexlMclFXAv/s79Zb5r0zqf7xhb3byiRQpgypgLOCGQ==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/analyze-wasm": "1.5.0", + "@arcjet/protocol": "1.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/analyze-wasm": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/analyze-wasm/-/analyze-wasm-1.5.0.tgz", + "integrity": "sha512-BypwVULn+AJxNXTLrXLwOHmunPGLDJfVrnCYK5tgWsFtEGxmCVxtSIQ1zwrNtya3tabNp15SZFcNW3ElkcMSdg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/body": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/body/-/body-1.5.0.tgz", + "integrity": "sha512-ALE/kpo5mlJtpKxB1ey+LiJn6m5+IuE5LmYJQS+z2QKqgcMCsv9r+mmHYb5mpG0RSbY7habjisbVZC10VxoevQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/cache": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/cache/-/cache-1.5.0.tgz", + "integrity": "sha512-VbyRa6nWgG8eDJEtmM/St9okRgp+oG+9e07mymv4tbB2sy86oaX07ibdfHxGLb6T0/TpZKcxnVGWdV7TW/Yv4g==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/duration": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/duration/-/duration-1.5.0.tgz", + "integrity": "sha512-La8i6tWHYZlATqApmIcrZmA3hm6PYams8UGU0nsJul4iQpKNdjuspLbfITZqx60eYe80IaH1mH47j4YIYgkSQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/env": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/env/-/env-1.5.0.tgz", + "integrity": "sha512-XLmFSKHD1XNqFjmihcHxlfvxkVjftAu1aQ+Ev4wpWpS5nGLCMByfGS/Xei76TKKsZaJRhEHkiIOtBl4DzpNnLA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/headers": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/headers/-/headers-1.5.0.tgz", + "integrity": "sha512-Ac7PMuTl2WuWFqlSsWGCKLdfYwu6afUrxKCdYCJyl2wp84f5C64NbcxKq/czgL4WX7LE7imgrPkcRUqOCIXERQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/inspect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/inspect/-/inspect-1.5.0.tgz", + "integrity": "sha512-pQEP/kp5JlcOMQ6dl2EU6MJdrCOzy/DM0LPTZD5FqCdfxZW/cvXaq7hYOtuN6/+ikHo82uA8F9BfEX2cmZ5vVw==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/protocol": "1.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/ip": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/ip/-/ip-1.5.0.tgz", + "integrity": "sha512-S6Wv8u3fv+iy7esGCLZlb+fGCWOKSZS2JDy62JAspbWokigd/GgyfHoQ4R3guFcPzrUz5WJ4A7KzsMq22SMy/Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/logger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/logger/-/logger-1.5.0.tgz", + "integrity": "sha512-AiW1IT9ka86H6dLLgqzvnJzRtp7e0QplFrdUgdzcM9DbIbV3ho5LUSxGdmRVVLytTOW59HVCCsJqTRKPiNdAPA==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/sprintf": "1.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/node/-/node-1.5.0.tgz", + "integrity": "sha512-LmSksV4qfDsmwhoGnQlL84ejyKzfv+4q6wwC04U7J5X2fjcdpfR5PM1R144wZq2UvYFWtvD05P+ST54/ochqPQ==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/body": "1.5.0", + "@arcjet/env": "1.5.0", + "@arcjet/headers": "1.5.0", + "@arcjet/ip": "1.5.0", + "@arcjet/logger": "1.5.0", + "@arcjet/protocol": "1.5.0", + "@arcjet/transport": "1.5.0", + "arcjet": "1.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/protocol/-/protocol-1.5.0.tgz", + "integrity": "sha512-NAtvNhgPI2jTybvxO5Qi5Bk5I14Vf4ruiLqIaid4QHDGmfyyvxj62qebIyRuPEi4Oss+ii/DKrbf5kTdOJ0i1g==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/cache": "1.5.0", + "@bufbuild/protobuf": "2.12.0", + "@connectrpc/connect": "2.1.1", + "typeid-js": "1.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-QXNOrrl/gFGmdXVAatAXUMc2bxqopCDjLu32zzH6enWpTWO6mK3sc/k8Uf+foPiAMiRzLUYMBc0u3gKY0S/Seg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/sprintf": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/sprintf/-/sprintf-1.5.0.tgz", + "integrity": "sha512-uDE2/xdIkDlJqu0eXZk2pTVVkEwoxD6f5aGF+toekU8b3XRuzLSuMZ+3F7BUv/9hExp060LmcQD9h9HXqWqr0A==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/stable-hash": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/stable-hash/-/stable-hash-1.5.0.tgz", + "integrity": "sha512-woNvuBu2vFEedGV8jsU7bFpOuWXxjAByTKXBguqxe90HLyIHHM+70ts/B0C/ukqZoL4uu6r0RMWkFljeVQregA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@arcjet/transport": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@arcjet/transport/-/transport-1.5.0.tgz", + "integrity": "sha512-wKn+A7TaMzoj/PjLKNowxp8VLk6GslRvWdkORZAPmOP7uQYn0RwSVvQufIxZ7+sec6BnD+vzXqztmQS15/y81Q==", + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "2.12.0", + "@connectrpc/connect": "2.1.1", + "@connectrpc/connect-node": "2.1.1", + "@connectrpc/connect-web": "2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", + "integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@connectrpc/connect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz", + "integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.1.1.tgz", + "integrity": "sha512-s3TfsI1XF+n+1z6MBS9rTnFsxxR4Rw5wmdEnkQINli81ESGxcsfaEet8duzq8LVuuCupmhUsgpRo0Nv9pZkufg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.1" + } + }, + "node_modules/@connectrpc/connect-web": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.1.1.tgz", + "integrity": "sha512-J8317Q2MaFRCT1jzVR1o06bZhDIBmU0UAzWx6xOIXzOq8+k71/+k7MUF7AwcBUX+34WIvbm5syRgC5HXQA8fOg==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.1" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.4.13", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.13.tgz", @@ -79,6 +295,24 @@ "node": ">= 8" } }, + "node_modules/arcjet": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/arcjet/-/arcjet-1.5.0.tgz", + "integrity": "sha512-UHd2XIHIphbBamkgN76Yrudx3+apClzEyK2FVquGN6W1k5Ug1gK9i3liNZNP5/XFzovwTHxi3WbRixfsQ0Ws1Q==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/analyze": "1.5.0", + "@arcjet/cache": "1.5.0", + "@arcjet/duration": "1.5.0", + "@arcjet/headers": "1.5.0", + "@arcjet/protocol": "1.5.0", + "@arcjet/runtime": "1.5.0", + "@arcjet/stable-hash": "1.5.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1564,6 +1798,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/typeid-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typeid-js/-/typeid-js-1.2.0.tgz", + "integrity": "sha512-t76ZucAnvGC60ea/HjVsB0TSoB0cw9yjnfurUgtInXQWUI/VcrlZGpO23KN3iSe8yOGUgb1zr7W7uEzJ3hSljA==", + "license": "Apache-2.0", + "dependencies": { + "uuid": "^10.0.0" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -1580,6 +1823,20 @@ "node": ">= 0.8" } }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index c037157..bb2f6b3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,6 +12,8 @@ "license": "ISC", "type": "module", "dependencies": { + "@arcjet/inspect": "^1.5.0", + "@arcjet/node": "^1.5.0", "bcryptjs": "^3.0.3", "body-parser": "^2.3.0", "cookie-parser": "^1.4.7", diff --git a/backend/src/lib/arcjet.js b/backend/src/lib/arcjet.js new file mode 100644 index 0000000..8780902 --- /dev/null +++ b/backend/src/lib/arcjet.js @@ -0,0 +1,31 @@ +import arcjet, { shield, detectBot, slidingWindow } from "@arcjet/node"; + +import ENV from "../lib/env.js"; + +const aj = arcjet({ + key: ENV.ARCJET_KEY, + rules: [ + // Shield protects your app from common attacks e.g. SQL injection + shield({ mode: "LIVE" }), + // Create a bot detection rule + detectBot({ + mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only + // Block all bots except the following + allow: [ + "CATEGORY:SEARCH_ENGINE", // Google, Bing, etc + // Uncomment to allow these other common bot categories + // See the full list at https://arcjet.com/bot-list + //"CATEGORY:MONITOR", // Uptime monitoring services + //"CATEGORY:PREVIEW", // Link previews e.g. Slack, Discord + ], + }), + // Create a token bucket rate limit. Other algorithms are supported. + slidingWindow({ + mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only + max: 100, // Max 100 requests + interval: 60, + }), + ], +}); + +export default aj; \ No newline at end of file diff --git a/backend/src/lib/env.js b/backend/src/lib/env.js index 9da1101..6ea7c20 100644 --- a/backend/src/lib/env.js +++ b/backend/src/lib/env.js @@ -5,6 +5,8 @@ const ENV = { NODE_ENV: process.env.NODE_ENV, MONGO_URI: process.env.MONGO_URI, JWT_SECRET: process.env.JWT_SECRET, + ARCJET_KEY: process.env.ARCJET_KEY, + ARCJET_ENV: process.env.ARCJET_ENV, } diff --git a/backend/src/middleware/arcjet.middleware.js b/backend/src/middleware/arcjet.middleware.js new file mode 100644 index 0000000..f3d2a68 --- /dev/null +++ b/backend/src/middleware/arcjet.middleware.js @@ -0,0 +1,33 @@ +import aj from "../lib/arcjet.js"; +import { isSpoofedBot } from "@arcjet/inspect"; + +export const arcjetProtection = async (req, res, next) => { + try { + const decision = await aj.protect(req); + + if (decision.isDenied()) { + if (decision.reason.isRateLimit()) { + return res.status(429).json({ message: "Rate limit exceeded. Please try again later." }); + } else if (decision.reason.isBot()) { + return res.status(403).json({ message: "Bot access denied." }); + } else { + return res.status(403).json({ + message: "Access denied by security policy.", + }); + } + } + + // check for spoofed bots + if (decision.results.some(isSpoofedBot)) { + return res.status(403).json({ + error: "Spoofed bot detected", + message: "Malicious bot activity detected.", + }); + } + + next(); + } catch (error) { + console.log("Arcjet Protection Error:", error); + next(); + } +}; \ No newline at end of file diff --git a/backend/src/routes/auth.routes.js b/backend/src/routes/auth.routes.js index 45d5f0c..09e55fc 100644 --- a/backend/src/routes/auth.routes.js +++ b/backend/src/routes/auth.routes.js @@ -1,9 +1,12 @@ import express from "express"; import { login, signup, logout , userProfile } from "../controllers/auth.controller.js"; import { protectRoute } from "../middleware/auth.middleware.js"; +import { arcjetProtection } from "../middleware/arcjet.middleware.js"; const router = express.Router(); +router.use(arcjetProtection); // Apply Arcjet protection to all routes + router.post("/login", login); router.post("/signup", signup); router.post("/logout", logout);