Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spam Filtering/Prevention #24

Open
Glacier150 opened this issue Jan 26, 2024 · 8 comments
Open

Spam Filtering/Prevention #24

Glacier150 opened this issue Jan 26, 2024 · 8 comments
Labels
backend Anything pertaining to the backend enhancement New feature or request help wanted Extra attention is needed

Comments

@Glacier150
Copy link
Collaborator

We need to figure out a way to somehow limit the number of messages from a single user. There's several ways we could implement this, but we definitely want to avoid putting it in the frontend.

@Glacier150 Glacier150 added enhancement New feature or request help wanted Extra attention is needed backend Anything pertaining to the backend labels Jan 26, 2024
@ItsCbass
Copy link
Collaborator

Honestly, I feel like we could mod the WebSocket message handler to include some rate limiting checks. As for rate limiting, I'm thinking we do limiting by:

  • Whenever a message gets from the user to the server, we track the users message count in a counter. Or, we keep track of a time window, and limit the amount per that time.
  • When the count gets exceeded, we can drop the message and send a warn the user. For example, we could use the std lib in Rust while using deque and time:
use std::collections::VecDeque;
use std::time::{Duration, Instant};

const TIME_WINDOW: Duration = Duration::from_secs(60); // 1 minute window
const MESSAGE_LIMIT: usize = 10; // 10 messages per window

Obv we should maybe change the values but this is just maybe how we'd get started on it. Thoughts??

@mariano-f-r
Copy link
Owner

I noticed you've added VecDeque. How would we use that? Am curious as to exact implementation details.

@ItsCbass
Copy link
Collaborator

Hey sorry for the late reply, this took a little bit of thinking so here we go. This whole idea is borrowed from a sliding window algo I found recently: https://www.geeksforgeeks.org/window-sliding-technique/

The thought process is to give each user will have their own VecDeque to track the timestamps of their messages. VecDeque will store the timestamps of each message. We'll have to store it in a message_timestamps variable. We can prob just create a struct for users to keep track of the timestamps.

struct User {
    message_timestamps: VecDeque<Instant>,
}

When a user sends a message, the current time (Instant) is added to the end of the VecDeque. Using a push_back method to achieve this like this:

fn add_message(&mut self, timestamp: Instant) {
    self.message_timestamps.push_back(timestamp);
}

To maintain the data structure, we'll have to remove timestamps eventually that get outside of the current time window. (60 seconds or whatever we choose, given by the TIME_WINDOW const). We can prob achieve this by removing elements from the beginning of the Deque. We'll use pop_front until all timestamps are within our time window. Something like this:

fn remove_old_messages(&mut self, current_time: Instant) {
    while let Some(&old_timestamp) = self.message_timestamps.front() {
        if current_time.duration_since(old_timestamp) > TIME_WINDOW {
            self.message_timestamps.pop_front();
        } else {
            break;
        }
    }
}

For rate limiting checks, we'll count the number of timestamps in our Dequeue and see if it goes over the limit. Obv if the limit is gone over, we'll have to temporarily stop messages. We can figure out consequences for spamming. As for implementation,

fn can_send_message(&self) -> bool {
    self.message_timestamps.len() < MESSAGE_LIMIT
}

Before a message finally gets processed, we want to call remove_old_messages to update the tracking algo. Then we'll have to check can_send_message if they can send a message or needs to be limited.

fn handle_new_message(&mut self, timestamp: Instant) -> bool {
    self.remove_old_messages(timestamp);
    if self.can_send_message() {
        self.add_message(timestamp);
        true // Allow message. YAY
    } else {
        false // Reject message and drop it :(
    }
}

@mariano-f-r
Copy link
Owner

Genius! I assume we'll have to create a Vec of open connections, probably using the User struct to hold that connection. I think that's a good idea, and it should work in practice. I'll have to see what we can do with ws-rs to see if there is a type corresponding to an individual connection. Given how tricky this might be, we may have to hold a little meeting to discuss this though.

@1ctinus
Copy link

1ctinus commented Feb 1, 2024

To prevent sockpuppeting, a user should only be allowed to have one session per device.

@mariano-f-r
Copy link
Owner

To prevent sockpuppeting, a user should only be allowed to have one session per device.

This will be doable in the future once #44 and #38 are complete. Should be as simple as giving each session a cookie to store in local storage, and then checking for that cookie on each tab. If user has cookie, then don't open a new session. Am not sure about how secure that would be though.

@mariano-f-r
Copy link
Owner

Sliding window algo will be very easy to do once #38 is complete.

@mariano-f-r
Copy link
Owner

This algorithm is now possible, and should be written in the call to for_each that handles the stream of messages here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend Anything pertaining to the backend enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants