more limits

This commit is contained in:
Lamp 2025-03-26 23:09:53 -07:00
parent 5df9a65ecf
commit bb85b00b9c
7 changed files with 85 additions and 18 deletions

@ -6,5 +6,4 @@ todo:
- better replies
- mousing not in react?
- fix scrollback issues
- more limits
- fix scrollback issues

@ -9,6 +9,7 @@
"busboy": "^1.6.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"hash-through": "^0.1.16",
"mime": "^3.0.0",
"mongodb": "^6.3.0",
@ -396,6 +397,15 @@
"node": ">= 0.10.0"
}
},
"node_modules/express-async-errors": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz",
"integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==",
"license": "ISC",
"peerDependencies": {
"express": "^4.16.2"
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
@ -1684,6 +1694,12 @@
}
}
},
"express-async-errors": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz",
"integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==",
"requires": {}
},
"finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",

@ -4,6 +4,7 @@
"busboy": "^1.6.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"hash-through": "^0.1.16",
"mime": "^3.0.0",
"mongodb": "^6.3.0",

@ -1 +1,2 @@
export const DATA_DIR = process.env.DATA_DIR || "data";
export const DATA_DIR = process.env.DATA_DIR || "data";
export const MIN_FREE_GB = process.env.MIN_FREE_GB ? Number(process.env.MIN_FREE_GB) : 10;

@ -1,4 +1,5 @@
import express from "express";
import "express-async-errors";
import busboy from "busboy";
import HashThrough from "hash-through";
import { to36 } from "1636";
@ -9,8 +10,8 @@ import mime from "mime";
import Message from "./models/Message.js";
import Emoji from "./models/Emoji.js";
import { DATA_DIR } from "./constants.js";
import { shuffleArray } from "./util.js";
import { DATA_DIR, MIN_FREE_GB } from "./constants.js";
import { df } from "./util.js";
export var router = express.Router();
@ -32,7 +33,12 @@ router.get("/messages", (req, res, next) => {
Message.getMessages({query, secret: req.query.secret}).then(messages => res.send(messages)).catch(next);
});
router.post("/upload", (req, res, next) => {
router.post("/upload", async (req, res, next) => {
var free = await df(DATA_DIR);
if (free < MIN_FREE_GB) {
res.sendStatus(507);
return;
}
var boi = busboy({headers: req.headers});
var codes = [];
boi.on("file", (name, file, info) => {
@ -98,16 +104,19 @@ router.get("/emojis", (req, res, next) => {
});
router.put("/emoji/:name", express.raw({limit: "1mb", type: ()=>true}), async (req, res, next) => {
try {
var emoji = new Emoji({
name: req.params.name,
type: req.query.type,
data: req.body
});
await emoji.save();
res.sendStatus(201);
} catch (error) {next(error)}
router.put("/emoji/:name", express.raw({limit: "1mb", type: ()=>true}), async (req, res) => {
var free = await df(DATA_DIR);
if (free < MIN_FREE_GB) {
res.sendStatus(507);
return;
}
var emoji = new Emoji({
name: req.params.name,
type: req.query.type,
data: req.body
});
await emoji.save();
res.sendStatus(201);
});

@ -2,6 +2,7 @@ import { Server as SocketIOServer } from "socket.io";
import { randomUUID } from "crypto";
import server from "./server.js";
import Message from "./models/Message.js";
import { Quota } from "./util.js";
export var io = new SocketIOServer(server, {
cors: {origin: "*"},
@ -11,8 +12,19 @@ export var io = new SocketIOServer(server, {
io.on("connection", socket => {
socket.ip = socket.handshake.headers["x-forwarded-for"]?.split(',')[0] || socket.handshake.address;
console.debug("connection from", socket.ip, "socket id", socket.id);
console.log("connect", socket.ip, socket.id);
socket.quotas = {
user: new Quota(1000, 1000*60*60),
message: new Quota(30, 60000)
};
socket.on("disconnect", reason => {
console.log("disconnect", socket.id, reason);
for (var quota in socket.quotas) {
socket.quotas[quota].destroy();
}
})
socket.on("user", user => {
if (socket.quotas.user.spend()) return;
user = {
name: user.name?.toString().trim().substring(0,32),
color: user.color?.toString().trim().substring(0,32),
@ -23,7 +35,7 @@ io.on("connection", socket => {
ip: socket.ip,
agent: socket.handshake.headers["user-agent"]
};
console.debug("user", user);
console.debug("user", JSON.stringify(user));
socket.data.user = user;
broadcastUsers();
});
@ -34,6 +46,7 @@ io.on("connection", socket => {
broadcastUsers();
});
socket.on("message", message => {
if (socket.quotas.message.spend()) return;
newMessage({
content: message.content?.toString().substring(0,10000),
user: {...socket.data.user}

@ -6,3 +6,31 @@ export function shuffleArray(array) {
array[j] = temp;
}
}
export class Quota {
constructor(limit, resetInterval) {
this.limit = limit;
this.count = limit;
this.interval = setInterval(() => {
this.count = this.limit;
}, resetInterval);
}
destroy () {
clearInterval(this.interval);
}
spend() {
return --this.count > 0;
}
}
import { execFile } from "child_process";
import { promisify } from "util";
var exec = promisify(execFile);
var free, ldft;
export async function df(path) {
if (Date.now() - ldft < 1000*60) return free;
ldft = Date.now();
var {stdout} = await exec("df", ["--output=avail", "--block-size=1G", path]);
return free = Number(stdout.toString().match(/\d+/)[0]);
}