From 5ba3980a9eb96ed1f714de658e88fa57ddf324d3 Mon Sep 17 00:00:00 2001 From: Yonle Date: Thu, 4 Jan 2024 22:01:08 +0700 Subject: [PATCH] feat: broadcast_ratelimit & incomming_ratelimit Signed-off-by: Yonle --- bouncer.js | 16 ++++++++++++++-- config.js.example | 7 +++++++ http.js | 11 +++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/bouncer.js b/bouncer.js index ad05689..72bf126 100644 --- a/bouncer.js +++ b/bouncer.js @@ -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: diff --git a/config.js.example b/config.js.example index 77f16cb..ae66e46 100644 --- a/config.js.example +++ b/config.js.example @@ -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: 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. diff --git a/http.js b/http.js index 26aa10a..a4a27da 100644 --- a/http.js +++ b/http.js @@ -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)); });