feat: broadcast_ratelimit & incomming_ratelimit

Signed-off-by: Yonle <yonle@lecturify.net>
This commit is contained in:
Yonle 2024-01-04 22:01:08 +07:00
parent 829974b893
commit 5ba3980a9e
3 changed files with 32 additions and 2 deletions

View File

@ -3,7 +3,7 @@ const { verifySignature, validateEvent, nip19 } = require("nostr-tools");
const auth = require("./auth.js");
const nip42 = require("./nip42.js");
let { relays, approved_publishers, log_about_relays, authorized_keys, private_keys, reconnect_time, wait_eose, pause_on_limit, eose_timeout, max_eose_score, cache_relays, max_orphan_sess } = require("./config");
let { relays, approved_publishers, log_about_relays, authorized_keys, private_keys, reconnect_time, wait_eose, pause_on_limit, eose_timeout, max_eose_score, cache_relays, max_orphan_sess, broadcast_ratelimit } = require("./config");
const socks = new Set();
const csess = new Map();
@ -22,6 +22,7 @@ module.exports = (ws, req) => {
let authKey = null;
let authorized = true;
let orphan = getOrphanSess(); // if available
let lastEvent = Date.now();
ws.id = orphan || (process.pid + Math.floor(Math.random() * 1000) + "_" + csess.size);
ws.subs = new Map(); // contains filter submitted by clients. per subID
@ -68,6 +69,13 @@ module.exports = (ws, req) => {
!approved_publishers?.includes(data[1].pubkey)
) return ws.send(JSON.stringify(["OK", data[1]?.id, false, "rejected: unauthorized"]));
if (broadcast_ratelimit && (broadcast_ratelimit > (Date.now() - lastEvent))) {
lastEvent = Date.now();
return ws.send(JSON.stringify(["OK", data[1]?.id, false, "rate-limited: request too fast."]));
}
lastEvent = Date.now();
ws.my_events.add(data[1]);
direct_bc(data, ws.id);
cache_bc(data, ws.id);
@ -78,7 +86,10 @@ module.exports = (ws, req) => {
if (data.length < 3) return ws.send(JSON.stringify(["NOTICE", "error: bad request."]));
if (typeof(data[1]) !== "string") return ws.send(JSON.stringify(["NOTICE", "error: expected subID a string. but got the otherwise."]));
if (typeof(data[2]) !== "object") return ws.send(JSON.stringify(["CLOSED", data[1], "error: expected filter to be obj, instead gives the otherwise."]));
if (ws.subs.has(data[1])) return ws.send(JSON.stringify(["CLOSED", data[1], "duplicate: this sub is already opened."]));
if (ws.subs.has(data[1])) {
direct_bc(["CLOSE", data[1]], ws.id);
cache_bc(["CLOSE", data[1]], ws.id);
}
ws.subs.set(data[1], data.slice(2));
ws.events.set(data[1], new Set());
ws.pause_subs.delete(data[1]);
@ -108,6 +119,7 @@ module.exports = (ws, req) => {
csess.set(ws.id, ws);
if (!orphan) newsess(ws.id);
authorized = true;
lastEvent = Date.now();
}
break;
default:

View File

@ -44,6 +44,13 @@ module.exports = {
// Tip : The bigger = The more accurate EOSE, The less = EOSE sent way earlier.
max_eose_score: 0,
// Client event broadcast ratelimit in milisecond.
// Client only able to broadcast new event to this bouncer after <broadcast_ratelimit>.
broadcast_ratelimit: 700,
// Incomming websocket connection ratelimit in milisecond.
incomming_ratelimit: 3300,
// A whitelist of users public keys who could use this bouncer.
// Leaving this empty will allow everyone to use this bouncer.
// NOTE: - Require NIP-42 compatible nostr client.

11
http.js
View File

@ -11,6 +11,7 @@ const log = _ => console.log(process.pid, curD(), "-", _);
// Server
const server = http.createServer({ noDelay: true })
const wss = new WebSocket.WebSocketServer({ noServer: true });
const lastConn = new Map();
server.on('request', (req, res) => {
log(`${req.headers["x-forwarded-for"]?.split(",")[0] || req.socket.address()?.address} - ${req.method} ${req.url}`)
@ -40,6 +41,16 @@ server.on('request', (req, res) => {
});
server.on('upgrade', (req, sock, head) => {
const ip = req.headers["x-forwarded-for"]?.split(",")[0] || sock.address()?.address;
const lv = lastConn.get(ip) // last visit
if (config.incomming_ratelimit && (config.incomming_ratelimit > (Date.now() - lv))) {
lastConn.set(ip, Date.now());
return sock.destroy(); // destroy.
}
lastConn.set(ip, Date.now());
req.on('close', _ => lastConn.set(ip, Date.now()));
wss.handleUpgrade(req, sock, head, _ => bouncer(_, req));
});