diff --git a/Readme.md b/Readme.md index 7571a07..4091ad0 100644 --- a/Readme.md +++ b/Readme.md @@ -502,7 +502,7 @@ CodeSync/ | `DELETE` | `/:id` | Delete file or folder (recursive) | Editor | | `POST` | `/:fileId/version` | Save version snapshot | Editor | | `GET` | `/:fileId/history` | Get version history | Viewer | -| `POST` | `/restore/:versionId` | Restore a file version | Editor | +| `POST` | `/restore/:fileId/:versionId` | Restore a file version | Editor | --- @@ -666,7 +666,7 @@ Editor clicks "Save Version" icon │ ▼ User clicks "Restore" on a past version - │ POST /api/files/restore/:versionId + │ POST /api/files/restore/:fileId/:versionId ▼ File.content overwritten with snapshot content (Socket will pick up and sync to all clients) @@ -806,4 +806,3 @@ This project is licensed under the **MIT License** — see the [LICENSE](LICENSE Built with ❤️ Alok Kumar Sharma

- diff --git a/backend/controllers/versionController.js b/backend/controllers/versionController.js index 4afbd99..ded4135 100644 --- a/backend/controllers/versionController.js +++ b/backend/controllers/versionController.js @@ -40,7 +40,7 @@ export const getHistory = async (req, res) => { } }; -// ─── POST /api/files/restore/:versionId ────────────────────────────────────── +// ─── POST /api/files/restore/:fileId/:versionId ────────────────────────────── export const restoreVersion = async (req, res) => { try { const { fileId, versionId } = req.params; diff --git a/backend/package-lock.json b/backend/package-lock.json index 76a7599..1617684 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.18.2", + "express-rate-limit": "^8.5.2", "express-session": "^1.19.0", "jsonwebtoken": "^9.0.2", "mongodb": "^7.2.0", @@ -744,6 +745,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/express-session": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", @@ -1134,6 +1153,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/backend/package.json b/backend/package.json index f551677..831be84 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,6 +14,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.18.2", + "express-rate-limit": "^8.5.2", "express-session": "^1.19.0", "jsonwebtoken": "^9.0.2", "mongodb": "^7.2.0", diff --git a/backend/routes/fileRoutes.js b/backend/routes/fileRoutes.js index b07c4ef..b08196d 100644 --- a/backend/routes/fileRoutes.js +++ b/backend/routes/fileRoutes.js @@ -1,4 +1,5 @@ import express from 'express'; +import rateLimit from 'express-rate-limit'; import authMiddleware from '../middleware/authMiddleware.js'; import { isEditor, requireRole, checkRole } from '../middleware/permissionMiddleware.js'; import { verifyFileAccess } from '../middleware/filePermission.js'; @@ -16,6 +17,13 @@ import { } from '../controllers/versionController.js'; const router = express.Router(); +const restoreVersionRateLimit = rateLimit({ + windowMs: 60 * 1000, + max: 30, + standardHeaders: true, + legacyHeaders: false, + message: { message: 'Too many requests. Please try again later.' } +}); router.use(authMiddleware); @@ -38,6 +46,6 @@ router.delete('/:id', verifyFileAccess, checkRole('editor'), deleteFile); // Version history router.post('/:fileId/version', verifyFileAccess, checkRole('editor'), saveVersion); router.get('/:fileId/history', verifyFileAccess, getHistory); -router.post('/restore/:fileId/:versionId', verifyFileAccess, checkRole('editor'), restoreVersion); +router.post('/restore/:fileId/:versionId', restoreVersionRateLimit, verifyFileAccess, checkRole('editor'), restoreVersion); export default router;