add basic ratelimiting
This commit is contained in:
@ -9,6 +9,7 @@ import { and, eq } from "drizzle-orm";
|
||||
|
||||
export interface Env {
|
||||
DB: D1Database;
|
||||
RL: any;
|
||||
}
|
||||
|
||||
// https://github.com/drizzle-team/drizzle-orm/tree/main/examples/cloudflare-d1
|
||||
@ -78,6 +79,11 @@ export default {
|
||||
const body = await request.json();
|
||||
const { type, name, userId, visibility } = initSchema.parse(body);
|
||||
|
||||
const allSandboxes = await db.select().from(sandbox).all();
|
||||
if (allSandboxes.length >= 8) {
|
||||
return new Response("You reached the maximum # of sandboxes.", { status: 400 });
|
||||
}
|
||||
|
||||
const sb = await db.insert(sandbox).values({ type, name, userId, visibility }).returning().get();
|
||||
|
||||
// console.log("sb:", sb);
|
||||
|
6
backend/server/package-lock.json
generated
6
backend/server/package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"node-pty": "^1.0.0",
|
||||
"rate-limiter-flexible": "^5.0.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
@ -1167,6 +1168,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limiter-flexible": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-5.0.3.tgz",
|
||||
"integrity": "sha512-lWx2y8NBVlTOLPyqs+6y7dxfEpT6YFqKy3MzWbCy95sTTOhOuxufP2QvRyOHpfXpB9OUJPbVLybw3z3AVAS5fA=="
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"node-pty": "^1.0.0",
|
||||
"rate-limiter-flexible": "^5.0.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
@ -16,6 +16,12 @@ import {
|
||||
saveFile,
|
||||
} from "./utils"
|
||||
import { IDisposable, IPty, spawn } from "node-pty"
|
||||
import {
|
||||
createFileRL,
|
||||
deleteFileRL,
|
||||
renameFileRL,
|
||||
saveFileRL,
|
||||
} from "./ratelimit"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
@ -107,71 +113,107 @@ io.on("connection", async (socket) => {
|
||||
|
||||
// todo: send diffs + debounce for efficiency
|
||||
socket.on("saveFile", async (fileId: string, body: string) => {
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
file.data = body
|
||||
try {
|
||||
await saveFileRL.consume(data.userId, 1)
|
||||
|
||||
fs.writeFile(path.join(dirName, file.id), body, function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
await saveFile(fileId, body)
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
file.data = body
|
||||
|
||||
fs.writeFile(path.join(dirName, file.id), body, function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
await saveFile(fileId, body)
|
||||
} catch (e) {
|
||||
socket.emit("rateLimit", "Rate limited: file saving. Please slow down.")
|
||||
}
|
||||
})
|
||||
|
||||
socket.on("createFile", async (name: string) => {
|
||||
const id = `projects/${data.id}/${name}`
|
||||
try {
|
||||
await createFileRL.consume(data.userId, 1)
|
||||
|
||||
fs.writeFile(path.join(dirName, id), "", function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
const id = `projects/${data.id}/${name}`
|
||||
|
||||
sandboxFiles.files.push({
|
||||
id,
|
||||
name,
|
||||
type: "file",
|
||||
})
|
||||
fs.writeFile(path.join(dirName, id), "", function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
|
||||
sandboxFiles.fileData.push({
|
||||
id,
|
||||
data: "",
|
||||
})
|
||||
sandboxFiles.files.push({
|
||||
id,
|
||||
name,
|
||||
type: "file",
|
||||
})
|
||||
|
||||
await createFile(id)
|
||||
sandboxFiles.fileData.push({
|
||||
id,
|
||||
data: "",
|
||||
})
|
||||
|
||||
await createFile(id)
|
||||
} catch (e) {
|
||||
socket.emit("rateLimit", "Rate limited: file creation. Please slow down.")
|
||||
}
|
||||
})
|
||||
|
||||
socket.on("renameFile", async (fileId: string, newName: string) => {
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
file.id = newName
|
||||
try {
|
||||
await renameFileRL.consume(data.userId, 1)
|
||||
|
||||
const parts = fileId.split("/")
|
||||
const newFileId = parts.slice(0, parts.length - 1).join("/") + "/" + newName
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
file.id = newName
|
||||
|
||||
fs.rename(
|
||||
path.join(dirName, fileId),
|
||||
path.join(dirName, newFileId),
|
||||
function (err) {
|
||||
if (err) throw err
|
||||
}
|
||||
)
|
||||
await renameFile(fileId, newFileId, file.data)
|
||||
const parts = fileId.split("/")
|
||||
const newFileId =
|
||||
parts.slice(0, parts.length - 1).join("/") + "/" + newName
|
||||
|
||||
fs.rename(
|
||||
path.join(dirName, fileId),
|
||||
path.join(dirName, newFileId),
|
||||
function (err) {
|
||||
if (err) throw err
|
||||
}
|
||||
)
|
||||
await renameFile(fileId, newFileId, file.data)
|
||||
} catch (e) {
|
||||
socket.emit("rateLimit", "Rate limited: file renaming. Please slow down.")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
socket.on("deleteFile", async (fileId: string, callback) => {
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
try {
|
||||
await deleteFileRL.consume(data.userId, 1)
|
||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||
if (!file) return
|
||||
|
||||
fs.unlink(path.join(dirName, fileId), function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
sandboxFiles.fileData = sandboxFiles.fileData.filter((f) => f.id !== fileId)
|
||||
fs.unlink(path.join(dirName, fileId), function (err) {
|
||||
if (err) throw err
|
||||
})
|
||||
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
||||
(f) => f.id !== fileId
|
||||
)
|
||||
|
||||
await deleteFile(fileId)
|
||||
await deleteFile(fileId)
|
||||
|
||||
const newFiles = await getSandboxFiles(data.id)
|
||||
callback(newFiles.files)
|
||||
const newFiles = await getSandboxFiles(data.id)
|
||||
callback(newFiles.files)
|
||||
} catch (e) {
|
||||
socket.emit("rateLimit", "Rate limited: file deletion. Please slow down.")
|
||||
}
|
||||
})
|
||||
|
||||
socket.on("createTerminal", ({ id }: { id: string }) => {
|
||||
if (terminals[id]) {
|
||||
console.log("Terminal already exists.")
|
||||
return
|
||||
}
|
||||
if (Object.keys(terminals).length >= 4) {
|
||||
console.log("Too many terminals.")
|
||||
return
|
||||
}
|
||||
|
||||
const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], {
|
||||
name: "xterm",
|
||||
cols: 100,
|
||||
|
21
backend/server/src/ratelimit.ts
Normal file
21
backend/server/src/ratelimit.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { RateLimiterMemory } from "rate-limiter-flexible"
|
||||
|
||||
export const saveFileRL = new RateLimiterMemory({
|
||||
points: 3,
|
||||
duration: 1,
|
||||
})
|
||||
|
||||
export const createFileRL = new RateLimiterMemory({
|
||||
points: 3,
|
||||
duration: 1,
|
||||
})
|
||||
|
||||
export const renameFileRL = new RateLimiterMemory({
|
||||
points: 3,
|
||||
duration: 1,
|
||||
})
|
||||
|
||||
export const deleteFileRL = new RateLimiterMemory({
|
||||
points: 3,
|
||||
duration: 1,
|
||||
})
|
Reference in New Issue
Block a user