mirror of
https://github.com/solero/houdini.git
synced 2024-11-13 22:28:21 +00:00
Initial login handlers
This commit is contained in:
parent
8412f325ff
commit
fcff0cd405
@ -184,7 +184,7 @@ class VersionChkConverter(IConverter):
|
||||
description = """Used for checking the verChk version number"""
|
||||
|
||||
async def convert(self, ctx):
|
||||
return ctx.argument[0].get('v')
|
||||
return int(ctx.argument[0].get('v'))
|
||||
|
||||
|
||||
class ConnectedPenguinConverter(IConverter):
|
||||
|
@ -9,7 +9,9 @@ class Crypto:
|
||||
|
||||
@staticmethod
|
||||
def hash(undigested):
|
||||
return hashlib.md5(undigested.encode('utf-8')).hexdigest()
|
||||
if type(undigested) == str:
|
||||
undigested = undigested.encode('utf-8')
|
||||
return hashlib.md5(undigested).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def generate_random_key():
|
||||
|
@ -66,6 +66,11 @@ class Penguin(db.Model):
|
||||
RejectionDe = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
|
||||
RejectionRu = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
|
||||
|
||||
@property
|
||||
def approval(self):
|
||||
return int('{}{}0{}{}{}{}'.format(self.ApprovalRu * 1, self.ApprovalDe * 1, self.ApprovalEs * 1,
|
||||
self.ApprovalFr * 1, self.ApprovalPt * 1, self.ApprovalEn * 1), 2)
|
||||
|
||||
|
||||
class ActivationKey(db.Model):
|
||||
__tablename__ = 'activation_key'
|
||||
|
@ -1,14 +1,119 @@
|
||||
from Houdini import Handlers
|
||||
from Houdini.Handlers import XMLPacket
|
||||
from Houdini.Converters import CredentialsConverter, VersionChkConverter
|
||||
from Houdini.Handlers import XMLPacket, Login
|
||||
from Houdini.Converters import CredentialsConverter
|
||||
from Houdini.Data.Penguin import Penguin
|
||||
from Houdini.Data.Buddy import BuddyList
|
||||
from Houdini.Data.Moderator import Ban
|
||||
from Houdini.Crypto import Crypto
|
||||
|
||||
import asyncio
|
||||
import bcrypt
|
||||
import time
|
||||
import os
|
||||
import config
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@Handlers.handler(XMLPacket('login'))
|
||||
async def handle_login(p, credentials: CredentialsConverter):
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
login_timestamp = time.time()
|
||||
username, password = credentials
|
||||
p.logger.info('{}:{} is logging in!'.format(username, password))
|
||||
p.logger.info('{} is logging in!'.format(username))
|
||||
|
||||
data = await Penguin.query.where(Penguin.Username == username).gino.first()
|
||||
|
||||
@Handlers.handler(XMLPacket('verChk'))
|
||||
async def handle_version_check(p, version: VersionChkConverter):
|
||||
p.logger.info('Version: {}'.format(version))
|
||||
if data is None:
|
||||
p.logger.info('{} failed to login: penguin does not exist')
|
||||
await p.send_error_and_disconnect(100)
|
||||
|
||||
password_correct = await loop.run_in_executor(None, bcrypt.checkpw,
|
||||
password.encode('utf-8'), data.Password.encode('utf-8'))
|
||||
|
||||
ip_addr = p.peer_name[0]
|
||||
|
||||
if not password_correct:
|
||||
p.logger.info('{} failed to login: incorrect password'.format(username))
|
||||
|
||||
if ip_addr in p.server.login_attempts:
|
||||
last_failed_attempt, failure_count = p.server.login_attempts[ip_addr]
|
||||
|
||||
failure_count = 1 if login_timestamp - last_failed_attempt >= p.server.server_config['LoginFailureTimer'] \
|
||||
else failure_count + 1
|
||||
|
||||
p.server.login_attempts[ip_addr] = [login_timestamp, failure_count]
|
||||
|
||||
if failure_count >= p.server.server_config['LoginFailureLimit']:
|
||||
return await p.send_error_and_disconnect(150)
|
||||
else:
|
||||
p.server.login_attempts[ip_addr] = [login_timestamp, 1]
|
||||
|
||||
return await p.send_error_and_disconnect(101)
|
||||
|
||||
if ip_addr in p.server.login_attempts:
|
||||
previous_attempt, failure_count = p.server.login_attempts[ip_addr]
|
||||
|
||||
max_attempts_exceeded = failure_count >= p.server.server_config['LoginFailureLimit']
|
||||
timer_surpassed = (login_timestamp - previous_attempt) > p.server.server_config['LoginFailureTimer']
|
||||
|
||||
if max_attempts_exceeded and not timer_surpassed:
|
||||
return await p.send_error_and_disconnect(150)
|
||||
else:
|
||||
del p.server.login_attempts[ip_addr]
|
||||
|
||||
if not data.Active:
|
||||
return await p.send_error_and_disconnect(900)
|
||||
|
||||
if data.Permaban:
|
||||
return await p.send_error_and_disconnect(603)
|
||||
|
||||
active_ban = await Ban.query.where(Ban.PenguinID == data.ID and Ban.Expires >= datetime.now()).gino.first()
|
||||
|
||||
if active_ban is not None:
|
||||
hours_left = round((active_ban.Expires - datetime.now()).total_seconds() / 60 / 60)
|
||||
|
||||
if hours_left == 0:
|
||||
return await p.send_error_and_disconnect(602)
|
||||
else:
|
||||
await p.send_error_and_disconnect(601, hours_left)
|
||||
|
||||
p.logger.info('{} has logged in successfully'.format(username))
|
||||
|
||||
random_key = Crypto.generate_random_key()
|
||||
login_key = Crypto.hash(random_key[::-1])
|
||||
confirmation_hash = Crypto.hash(os.urandom(24))
|
||||
|
||||
await p.server.redis.setex('{}.lkey'.format(data.ID), p.server.server_config['KeyTTL'], login_key)
|
||||
await p.server.redis.setex('{}.ckey'.format(data.ID), p.server.server_config['KeyTTL'], confirmation_hash)
|
||||
|
||||
buddy_worlds = []
|
||||
world_populations = []
|
||||
|
||||
servers_config = config.servers
|
||||
|
||||
for server_name, server_config in servers_config.items():
|
||||
if server_config['World']:
|
||||
server_population = await p.server.redis.get('{}.population'.format(server_name))
|
||||
server_population = int(server_population) / (server_config['Capacity'] / 6) if server_population else 0
|
||||
|
||||
server_players = await p.server.redis.smembers('{}.players'.format(server_name))
|
||||
|
||||
world_populations.append('{},{}'.format(server_config['Id'], server_population))
|
||||
|
||||
if not len(server_players) > 0:
|
||||
p.logger.debug('Skipping buddy iteration for {}'.format(server_name))
|
||||
continue
|
||||
|
||||
buddies = await BuddyList.select('BuddyID').where(BuddyList.PenguinID == data.ID).gino.all()
|
||||
for buddy_id in buddies:
|
||||
if str(buddy_id) in server_players:
|
||||
buddy_worlds.append(server_config['Id'])
|
||||
break
|
||||
|
||||
raw_login_data = '|'.join([str(data.ID), str(data.ID), data.Username, login_key, str(data.approval), '1'])
|
||||
await p.send_xt('l', raw_login_data, confirmation_hash, 'friendsKey', '|'.join(world_populations), data.Email)
|
||||
|
||||
handle_version_check = Login.handle_version_check
|
||||
handle_random_key = Login.handle_random_key
|
||||
|
@ -0,0 +1,17 @@
|
||||
from Houdini import Handlers
|
||||
from Houdini.Handlers import XMLPacket
|
||||
from Houdini.Converters import VersionChkConverter
|
||||
|
||||
|
||||
@Handlers.handler(XMLPacket('verChk'))
|
||||
async def handle_version_check(p, version: VersionChkConverter):
|
||||
if not version == 153:
|
||||
await p.send_xml({'body': {'action': 'apiKO', 'r': '0'}})
|
||||
await p.close()
|
||||
else:
|
||||
await p.send_xml({'body': {'action': 'apiOK', 'r': '0'}})
|
||||
|
||||
|
||||
@Handlers.handler(XMLPacket('rndK'))
|
||||
async def handle_random_key(p, data):
|
||||
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': 'houdini'})
|
@ -68,6 +68,8 @@ class HoudiniFactory:
|
||||
self.penguins_by_id = {}
|
||||
self.penguins_by_username = {}
|
||||
|
||||
self.login_attempts = {}
|
||||
|
||||
self.xt_listeners, self.xml_listeners = {}, {}
|
||||
self.commands = {}
|
||||
self.plugins = {}
|
||||
|
Loading…
Reference in New Issue
Block a user