mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
Integrates new Throttle with unconnected Commands; rate limits new account creation (partial fix for #1523).
This commit is contained in:
parent
791ace73bc
commit
a2cccd7326
1 changed files with 31 additions and 59 deletions
|
|
@ -4,13 +4,14 @@ Commands that are available from the connect screen.
|
|||
import re
|
||||
import time
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, deque
|
||||
from random import getrandbits
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.server.models import ServerConfig
|
||||
from evennia.server.throttle import Throttle
|
||||
from evennia.comms.models import ChannelDB
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
|
||||
|
|
@ -26,58 +27,11 @@ __all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate",
|
|||
MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||
|
||||
# Helper function to throttle failed connection attempts.
|
||||
# This can easily be used to limit account creation too,
|
||||
# (just supply a different storage dictionary), but this
|
||||
# would also block dummyrunner, so it's not added as default.
|
||||
|
||||
_LATEST_FAILED_LOGINS = defaultdict(list)
|
||||
|
||||
|
||||
def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS):
|
||||
"""
|
||||
This will check the session's address against the
|
||||
_LATEST_LOGINS dictionary to check they haven't
|
||||
spammed too many fails recently.
|
||||
|
||||
Args:
|
||||
session (Session): Session failing
|
||||
maxlim (int): max number of attempts to allow
|
||||
timeout (int): number of timeout seconds after
|
||||
max number of tries has been reached.
|
||||
|
||||
Returns:
|
||||
throttles (bool): True if throttling is active,
|
||||
False otherwise.
|
||||
|
||||
Notes:
|
||||
If maxlim and/or timeout are set, the function will
|
||||
just do the comparison, not append a new datapoint.
|
||||
|
||||
"""
|
||||
address = session.address
|
||||
if isinstance(address, tuple):
|
||||
address = address[0]
|
||||
now = time.time()
|
||||
if maxlim and timeout:
|
||||
# checking mode
|
||||
latest_fails = storage[address]
|
||||
if latest_fails and len(latest_fails) >= maxlim:
|
||||
# too many fails recently
|
||||
if now - latest_fails[-1] < timeout:
|
||||
# too soon - timeout in play
|
||||
return True
|
||||
else:
|
||||
# timeout has passed. Reset faillist
|
||||
storage[address] = []
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
# store the time of the latest fail
|
||||
storage[address].append(time.time())
|
||||
return False
|
||||
# Create an object to store account creation attempts per IP
|
||||
CREATION_THROTTLE = Throttle(limit=2, timeout=10 * 60)
|
||||
|
||||
# Create an object to store failed login attempts per IP
|
||||
LOGIN_THROTTLE = Throttle(limit=5, timeout=5 * 60)
|
||||
|
||||
def create_guest_account(session):
|
||||
"""
|
||||
|
|
@ -134,7 +88,7 @@ def create_guest_account(session):
|
|||
session.msg("An error occurred. Please e-mail an admin if the problem persists.")
|
||||
logger.log_trace()
|
||||
raise
|
||||
|
||||
|
||||
|
||||
def create_normal_account(session, name, password):
|
||||
"""
|
||||
|
|
@ -149,8 +103,11 @@ def create_normal_account(session, name, password):
|
|||
account (Account): the account which was created from the name and password.
|
||||
"""
|
||||
# check for too many login errors too quick.
|
||||
if _throttle(session, maxlim=5, timeout=5 * 60):
|
||||
# timeout is 5 minutes.
|
||||
address = session.address
|
||||
if isinstance(address, tuple):
|
||||
address = address[0]
|
||||
|
||||
if LOGIN_THROTTLE.check(address):
|
||||
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
||||
return None
|
||||
|
||||
|
|
@ -161,7 +118,7 @@ def create_normal_account(session, name, password):
|
|||
# No accountname or password match
|
||||
session.msg("Incorrect login information given.")
|
||||
# this just updates the throttle
|
||||
_throttle(session)
|
||||
LOGIN_THROTTLE.update(address)
|
||||
# calls account hook for a failed login if possible.
|
||||
account = AccountDB.objects.get_account_from_name(name)
|
||||
if account:
|
||||
|
|
@ -171,7 +128,6 @@ def create_normal_account(session, name, password):
|
|||
# Check IP and/or name bans
|
||||
bans = ServerConfig.objects.conf("server_bans")
|
||||
if bans and (any(tup[0] == account.name.lower() for tup in bans) or
|
||||
|
||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||
# this is a banned IP or name!
|
||||
string = "|rYou have been banned and cannot continue from here." \
|
||||
|
|
@ -182,7 +138,6 @@ def create_normal_account(session, name, password):
|
|||
|
||||
return account
|
||||
|
||||
|
||||
class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
connect to the game
|
||||
|
|
@ -211,7 +166,10 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
|||
session = self.caller
|
||||
|
||||
# check for too many login errors too quick.
|
||||
if _throttle(session, maxlim=5, timeout=5 * 60, storage=_LATEST_FAILED_LOGINS):
|
||||
address = session.address
|
||||
if isinstance(address, tuple):
|
||||
address = address[0]
|
||||
if CONNECTION_THROTTLE.check(address):
|
||||
# timeout is 5 minutes.
|
||||
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
||||
return
|
||||
|
|
@ -234,6 +192,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
|||
session.msg("\n\r Usage (without <>): connect <name> <password>")
|
||||
return
|
||||
|
||||
CONNECTION_THROTTLE.update(address)
|
||||
name, password = parts
|
||||
account = create_normal_account(session, name, password)
|
||||
if account:
|
||||
|
|
@ -262,6 +221,15 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
session = self.caller
|
||||
args = self.args.strip()
|
||||
|
||||
# Rate-limit account creation.
|
||||
address = session.address
|
||||
|
||||
if isinstance(address, tuple):
|
||||
address = address[0]
|
||||
if CREATION_THROTTLE.check(address):
|
||||
session.msg("|RYou are creating too many accounts. Try again in a few minutes.|n")
|
||||
return
|
||||
|
||||
# extract double quoted parts
|
||||
parts = [part.strip() for part in re.split(r"\"", args) if part.strip()]
|
||||
|
|
@ -322,6 +290,10 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
if MULTISESSION_MODE < 2:
|
||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||
_create_character(session, new_account, typeclass, default_home, permissions)
|
||||
|
||||
# Update the throttle to indicate a new account was created from this IP
|
||||
CREATION_THROTTLE.update(address)
|
||||
|
||||
# tell the caller everything went well.
|
||||
string = "A new account '%s' was created. Welcome!"
|
||||
if " " in accountname:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue