unspam

A small little module that prevents spam with 4th grade math.

Usage no npm install needed!

<script type="module">
  import unspam from 'https://cdn.skypack.dev/unspam';
</script>

README

Unspam

Spammers are gross. Get rid of DoS'rs (and maybe even dumb DDoS'rs) with fourth grade math.

What's Unspam?

Unspam is a tiny little Node.js module that keeps track of how many times an IP makes a request. If an IP makes too many requests, Unspam will either ignore the request, or send a 403 error, depending on if the environment is WebSocket or HTTP. (Yes, there is support for both, read on!)

For those who are hungry for information, here's the formula.

{average in milliseconds of time in between requests}/{amount of requests}

I suck at math, I hope you weren't expect calculus or something.

The smaller the number, the spammier the client. Once this number goes below a number that the server has determined, or nothing it put in the default is 0.5, all further requests from that client will be ignored, or sent a 403 error, again depending on the environment.

So, without further ado, here are the only three functions you need to worry about.

unspam.init(options)

Must be called before anything is used. It starts Unspam and will crash without it. The options you can put in it are-

maxRequestRate - a the lowest number the formula above can produce. If a client goes under it, they will be banned.

banTime - How long, in minutes, the client will be banned for. This also doubles for HTTP servers as how long until a user is counted inactive and removed.

cacheInterval - The time in between cache clears, in minutes. When an client's ban expires, it doesn't actually expire. A function must go off periodically and loop through each ban object to check if it has been banTime time since the client was banned. If it has been, it will be removed. If not, nothing will happen.

unspam.expressMiddleware

It's Express middleware. Woo.

const express = require('express')
const app = express()
const unspam = require('unspam')

unspam.init({maxRequestRate:0.5,expiry:60,cacheInterval:30000})

app.use(unspam.expressMiddleware)

app.get('/', function(req, res) {
    res.send('hello!') 
    /* A spammy user would not get a nice hello message, 
    they would get a 403 forbidden error.
    */
})

unspam.attachSocket(socket, callback)

Okay, I tried and I tried to make this not have to have a callback, but it was hard. If anyone can find a better way to do this, please tell me. I hate callbacks as much as the next guy.

I programmed this with the ws module in mind, so I don't know how much it will work with other modules. The first argument is the object returned by the callback in websocketServer.on('connection', function(ws) { } . That ws object is important, and is what will be put in the function.

The second one is, *gasp!*, a callback. Pretty much, if the function finds out that client is banned, the callback will not be called. At all, period. It will also remove all event listeners for that client and that client only.

Here, have an example.

const WebSocket = require('ws').Server
const wss = new Websocket({port:'the port, i guess'})

const unspam = require('unspam')
unspam.init({maxRequestRate:0.5,expiry:60,cacheInterval:30000})

wss.on('connection', function(ws) {
    unspam.attachSocket(ws, function() {
        ws.on('message', function(message) {
            console.log(message)
        })
    })
})


Yes, it looks like a pyramid, but it works for now. It will detect if a user is spamming messages, and if they are, they get the boot. Anytime a user disconnects or crashes, they are removed from the watch list.

unspam.bans

An array of objects. In each object, there are two properties, ip, and time. ip is the amount of innings pitched (or the address of the user who got banned, if you don't have a sense of humor), and time is the time the user got banned, presented in milliseconds since January 1, 1970.

There are more functions, but these should be good for general uses. A lot of them are just manual interaction with banning and accounting for client request speed, and should only be used if a middleware for your server is not provided.

I plan to add more middlewares (Koa, Hapi) sometime soon, and maybe even support other WebSocket modules, but for now, Ciao!

-Flarp

(I've also commented the source code, so give it a look!)