Add rate limiting
This should fix #65, although it may be expanded in the future. Default settings for limits can be changed or disabled in the settings. Default values: - Chat: 1 second - Duplicate chat: 30 seconds - /nick: 5 minutes - /auth: 5 seconds - /color: 1 minute Creation of the chat Client object has been moved from ChatRoom.Join() to NewClient(). This function also handles setting the initial name.
This commit is contained in:
parent
ca72dc28c0
commit
67b3143893
@ -5,6 +5,7 @@ import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
@ -25,6 +26,36 @@ type Client struct {
|
||||
IsColorForced bool
|
||||
IsNameForced bool
|
||||
regexName *regexp.Regexp
|
||||
|
||||
// Times since last event. use time.Duration.Since()
|
||||
nextChat time.Time // rate limit chat messages
|
||||
nextNick time.Time // rate limit nickname changes
|
||||
nextColor time.Time // rate limit color changes
|
||||
nextAuth time.Time // rate limit failed auth attempts. Sould prolly have a backoff policy.
|
||||
authTries int // number of failed auth attempts
|
||||
|
||||
nextDuplicate time.Time
|
||||
lastMsg string
|
||||
}
|
||||
|
||||
func NewClient(connection *chatConnection, room *ChatRoom, name, color string) (*Client, error) {
|
||||
c := &Client{
|
||||
conn: connection,
|
||||
belongsTo: room,
|
||||
color: color,
|
||||
}
|
||||
|
||||
if err := c.setName(name); err != nil {
|
||||
return nil, fmt.Errorf("could not set client name to %#v: %v", name, err)
|
||||
}
|
||||
|
||||
// Set initial vaules to their rate limit duration in the past.
|
||||
c.nextChat = time.Now()
|
||||
c.nextNick = time.Now()
|
||||
c.nextColor = time.Now()
|
||||
c.nextAuth = time.Now()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
//Client has a new message to broadcast
|
||||
@ -85,11 +116,42 @@ func (cl *Client) NewMsg(data common.ClientData) {
|
||||
}
|
||||
|
||||
} else {
|
||||
// Limit the rate of sent chat messages. Ignore mods and admins
|
||||
if time.Now().Before(cl.nextChat) && cl.CmdLevel == common.CmdlUser {
|
||||
err := cl.SendChatData(common.NewChatMessage("", "",
|
||||
"Slow down.",
|
||||
common.CmdlUser,
|
||||
common.MsgCommandResponse))
|
||||
if err != nil {
|
||||
common.LogErrorf("Unable to send slowdown for chat: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Trim long messages
|
||||
if len(msg) > 400 {
|
||||
msg = msg[0:400]
|
||||
}
|
||||
|
||||
// Limit the rate of duplicate messages. Ignore mods and admins.
|
||||
// Only checks the last message.
|
||||
if strings.TrimSpace(strings.ToLower(msg)) == cl.lastMsg &&
|
||||
time.Now().Before(cl.nextDuplicate) &&
|
||||
cl.CmdLevel == common.CmdlUser {
|
||||
err := cl.SendChatData(common.NewChatMessage("", "",
|
||||
common.ParseEmotes("You already sent that PeepoSus"),
|
||||
common.CmdlUser,
|
||||
common.MsgCommandResponse))
|
||||
if err != nil {
|
||||
common.LogErrorf("Unable to send slowdown for chat: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cl.nextChat = time.Now().Add(time.Second * settings.RateLimitChat)
|
||||
cl.nextDuplicate = time.Now().Add(time.Second * settings.RateLimitDuplicate)
|
||||
cl.lastMsg = strings.TrimSpace(strings.ToLower(msg))
|
||||
|
||||
common.LogChatf("[chat] <%s> %q\n", cl.name, msg)
|
||||
|
||||
// Enable links for mods and admins
|
||||
@ -188,6 +250,7 @@ func (cl *Client) setName(s string) error {
|
||||
|
||||
cl.name = s
|
||||
cl.regexName = regex
|
||||
cl.conn.clientName = s
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
)
|
||||
@ -126,6 +127,10 @@ var commands = &CommandControl{
|
||||
return "You are not allowed to change your color."
|
||||
}
|
||||
|
||||
if time.Now().Before(cl.nextColor) && cl.CmdLevel == common.CmdlUser {
|
||||
return fmt.Sprintf("Slow down. You can change your color in %0.0f seconds.", time.Until(cl.nextColor).Seconds())
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
cl.setColor(common.RandomColor())
|
||||
return "Random color chosen: " + cl.color
|
||||
@ -136,6 +141,8 @@ var commands = &CommandControl{
|
||||
return "To choose a specific color use the format <i>/color #c029ce</i>. Hex values expected."
|
||||
}
|
||||
|
||||
cl.nextColor = time.Now().Add(time.Second * settings.RateLimitColor)
|
||||
|
||||
err := cl.setColor(args[0])
|
||||
if err != nil {
|
||||
common.LogErrorf("[color] could not send color update to client: %v\n", err)
|
||||
@ -163,6 +170,14 @@ var commands = &CommandControl{
|
||||
return "You are already authenticated."
|
||||
}
|
||||
|
||||
// TODO: handle backoff policy
|
||||
if time.Now().Before(cl.nextAuth) {
|
||||
cl.nextAuth = time.Now().Add(time.Second * settings.RateLimitAuth)
|
||||
return "Slow down."
|
||||
}
|
||||
cl.authTries += 1 // this isn't used yet
|
||||
cl.nextAuth = time.Now().Add(time.Second * settings.RateLimitAuth)
|
||||
|
||||
pw := html.UnescapeString(strings.Join(args, " "))
|
||||
|
||||
if settings.AdminPassword == pw {
|
||||
@ -196,6 +211,12 @@ var commands = &CommandControl{
|
||||
common.CNNick.String(): Command{
|
||||
HelpText: "Change display name",
|
||||
Function: func(cl *Client, args []string) string {
|
||||
if time.Now().Before(cl.nextNick) {
|
||||
//cl.nextNick = time.Now().Add(time.Second * settings.RateLimitNick)
|
||||
return fmt.Sprintf("Slow down. You can change your nick in %0.0f seconds.", time.Until(cl.nextNick).Seconds())
|
||||
}
|
||||
cl.nextNick = time.Now().Add(time.Second * settings.RateLimitNick)
|
||||
|
||||
if len(args) == 0 {
|
||||
return "Missing name to change to."
|
||||
}
|
||||
|
11
chatroom.go
11
chatroom.go
@ -96,16 +96,9 @@ func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
conn.clientName = name
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
belongsTo: cr,
|
||||
color: common.RandomColor(),
|
||||
}
|
||||
|
||||
err := client.setName(name)
|
||||
client, err := NewClient(conn, cr, name, common.RandomColor())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not set client name to %#v: %v", name, err)
|
||||
return nil, fmt.Errorf("Unable to join client: %v", err)
|
||||
}
|
||||
|
||||
host := client.Host()
|
||||
|
43
settings.go
43
settings.go
@ -33,6 +33,13 @@ type Settings struct {
|
||||
LogLevel common.LogLevel
|
||||
LogFile string
|
||||
|
||||
// Rate limiting stuff, in seconds
|
||||
RateLimitChat time.Duration
|
||||
RateLimitNick time.Duration
|
||||
RateLimitColor time.Duration
|
||||
RateLimitAuth time.Duration
|
||||
RateLimitDuplicate time.Duration // Amount of seconds between allowed duplicate messages
|
||||
|
||||
// Send the NoCache header?
|
||||
NoCache bool
|
||||
}
|
||||
@ -72,6 +79,42 @@ func LoadSettings(filename string) (*Settings, error) {
|
||||
return nil, fmt.Errorf("unable to generate admin password: %s", err)
|
||||
}
|
||||
|
||||
if s.RateLimitChat == -1 {
|
||||
s.RateLimitChat = 0
|
||||
} else if s.RateLimitChat <= 0 {
|
||||
s.RateLimitChat = 1
|
||||
}
|
||||
|
||||
if s.RateLimitNick == -1 {
|
||||
s.RateLimitNick = 0
|
||||
} else if s.RateLimitNick <= 0 {
|
||||
s.RateLimitNick = 300
|
||||
}
|
||||
|
||||
if s.RateLimitColor == -1 {
|
||||
s.RateLimitColor = 0
|
||||
} else if s.RateLimitColor <= 0 {
|
||||
s.RateLimitColor = 60
|
||||
}
|
||||
|
||||
if s.RateLimitAuth == -1 {
|
||||
s.RateLimitAuth = 0
|
||||
} else if s.RateLimitAuth <= 0 {
|
||||
s.RateLimitAuth = 5
|
||||
}
|
||||
|
||||
if s.RateLimitDuplicate == -1 {
|
||||
s.RateLimitDuplicate = 0
|
||||
} else if s.RateLimitDuplicate <= 0 {
|
||||
s.RateLimitDuplicate = 30
|
||||
}
|
||||
|
||||
// Print this stuff before we multiply it by time.Second
|
||||
common.LogInfof("RateLimitChat: %v", s.RateLimitChat)
|
||||
common.LogInfof("RateLimitNick: %v", s.RateLimitNick)
|
||||
common.LogInfof("RateLimitColor: %v", s.RateLimitColor)
|
||||
common.LogInfof("RateLimitAuth: %v", s.RateLimitAuth)
|
||||
|
||||
// Don't use LogInfof() here. Log isn't setup yet when LoadSettings() is called from init().
|
||||
fmt.Printf("Settings reloaded. New admin password: %s\n", s.AdminPassword)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user