add chat and IP limiting
This commit is contained in:
parent
bad5daf075
commit
78629b9cb7
5 changed files with 93 additions and 6 deletions
|
@ -1,10 +1,15 @@
|
|||
[http]
|
||||
host = "127.0.0.1"
|
||||
port = 3000
|
||||
proxied = true
|
||||
|
||||
[chat]
|
||||
charlimit = 100
|
||||
agentsDir = "agents/"
|
||||
maxConnectionsPerIP = 2
|
||||
|
||||
[chat.ratelimits]
|
||||
chat = {seconds = 10, limit = 8}
|
||||
|
||||
[tts]
|
||||
enabled = true
|
||||
|
@ -62,9 +67,9 @@ filename = "Office_Logo.ACS"
|
|||
friendlyName = "Peedy"
|
||||
filename = "Peedy.acs"
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Question"
|
||||
filename = "question_mark.acs"
|
||||
#[[agents]]
|
||||
#friendlyName = "Question"
|
||||
#filename = "question_mark.acs"
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Robby"
|
||||
|
|
|
@ -3,6 +3,7 @@ import { WebSocket } from "ws";
|
|||
import { MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentTalkMessage } from '@msagent-chat/protocol';
|
||||
import { MSAgentChatRoom } from "./room.js";
|
||||
import * as htmlentities from 'html-entities';
|
||||
import RateLimiter from "./ratelimiter.js";
|
||||
|
||||
// Event types
|
||||
|
||||
|
@ -15,17 +16,25 @@ export interface Client {
|
|||
}
|
||||
|
||||
export class Client extends EventEmitter {
|
||||
ip: string;
|
||||
username: string | null;
|
||||
agent: string | null;
|
||||
|
||||
room: MSAgentChatRoom;
|
||||
socket: WebSocket;
|
||||
constructor(socket: WebSocket, room: MSAgentChatRoom) {
|
||||
|
||||
chatRateLimit: RateLimiter
|
||||
|
||||
constructor(socket: WebSocket, room: MSAgentChatRoom, ip: string) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
this.ip = ip;
|
||||
this.room = room;
|
||||
this.username = null;
|
||||
this.agent = null;
|
||||
|
||||
this.chatRateLimit = new RateLimiter(this.room.config.ratelimits.chat);
|
||||
|
||||
this.socket.on('message', (msg, isBinary) => {
|
||||
if (isBinary) {
|
||||
this.socket.close();
|
||||
|
@ -89,7 +98,7 @@ export class Client extends EventEmitter {
|
|||
}
|
||||
case MSAgentProtocolMessageType.Talk: {
|
||||
let talkMsg = msg as MSAgentTalkMessage;
|
||||
if (!talkMsg.data || !talkMsg.data.msg) {
|
||||
if (!talkMsg.data || !talkMsg.data.msg || !this.chatRateLimit.request()) {
|
||||
return;
|
||||
}
|
||||
if (talkMsg.data.msg.length > this.room.config.charlimit) return;
|
||||
|
|
|
@ -2,6 +2,7 @@ export interface IConfig {
|
|||
http: {
|
||||
host: string;
|
||||
port: number;
|
||||
proxied: boolean;
|
||||
}
|
||||
chat: ChatConfig;
|
||||
tts: TTSConfig;
|
||||
|
@ -19,9 +20,20 @@ export interface TTSConfig {
|
|||
export interface ChatConfig {
|
||||
charlimit: number;
|
||||
agentsDir: string;
|
||||
maxConnectionsPerIP: number;
|
||||
ratelimits: {
|
||||
chat: RateLimitConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AgentConfig {
|
||||
friendlyName: string;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface RateLimitConfig {
|
||||
seconds: number;
|
||||
limit: number;
|
||||
}
|
|
@ -9,6 +9,7 @@ import * as fs from 'fs';
|
|||
import { TTSClient } from './tts.js';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { isIP } from 'net';
|
||||
|
||||
let config: IConfig;
|
||||
let configPath: string;
|
||||
|
@ -80,7 +81,32 @@ let room = new MSAgentChatRoom(config.chat, config.agents, tts);
|
|||
|
||||
app.register(async app => {
|
||||
app.get("/api/socket", {websocket: true}, (socket, req) => {
|
||||
let client = new Client(socket, room);
|
||||
// TODO: Do this pre-upgrade and return the appropriate status codes
|
||||
let ip: string;
|
||||
if (config.http.proxied) {
|
||||
if (req.headers["x-forwarded-for"] === undefined) {
|
||||
console.error(`Warning: X-Forwarded-For not set! This is likely a misconfiguration of your reverse proxy.`);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
let xff = req.headers["x-forwarded-for"];
|
||||
if (xff instanceof Array)
|
||||
ip = xff[0];
|
||||
else
|
||||
ip = xff;
|
||||
if (!isIP(ip)) {
|
||||
console.error(`Warning: X-Forwarded-For malformed! This is likely a misconfiguration of your reverse proxy.`);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ip = req.ip;
|
||||
}
|
||||
let o = room.clients.filter(c => c.ip === ip);
|
||||
if (o.length >= config.chat.maxConnectionsPerIP) {
|
||||
o[0].socket.close();
|
||||
}
|
||||
let client = new Client(socket, room, ip);
|
||||
room.addClient(client);
|
||||
});
|
||||
});
|
||||
|
|
35
server/src/ratelimiter.ts
Normal file
35
server/src/ratelimiter.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { RateLimitConfig } from './config';
|
||||
|
||||
// Class to ratelimit a resource (chatting, logging in, etc)
|
||||
export default class RateLimiter extends EventEmitter {
|
||||
private limit: number;
|
||||
private interval: number;
|
||||
private requestCount: number;
|
||||
private limiter?: NodeJS.Timeout;
|
||||
private limiterSet: boolean;
|
||||
|
||||
constructor(config: RateLimitConfig) {
|
||||
super();
|
||||
this.limit = config.limit;
|
||||
this.interval = config.seconds;
|
||||
this.requestCount = 0;
|
||||
this.limiterSet = false;
|
||||
}
|
||||
// Return value is whether or not the action should be continued
|
||||
request(): boolean {
|
||||
this.requestCount++;
|
||||
if (this.requestCount >= this.limit) {
|
||||
this.emit('limit');
|
||||
return false;
|
||||
}
|
||||
if (!this.limiterSet) {
|
||||
this.limiter = setTimeout(() => {
|
||||
this.limiterSet = false;
|
||||
this.requestCount = 0;
|
||||
}, this.interval * 1000);
|
||||
this.limiterSet = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue