better auth checking
This commit is contained in:
parent
d8dc04e663
commit
699536fd78
2 changed files with 31 additions and 14 deletions
|
@ -8,7 +8,7 @@ from typing import Dict, List, Callable, Type, Optional, Tuple
|
||||||
from .dispatcher import Dispatcher, ConnectionState
|
from .dispatcher import Dispatcher, ConnectionState
|
||||||
from .mc.mctypes import VarInt
|
from .mc.mctypes import VarInt
|
||||||
from .mc.packet import Packet
|
from .mc.packet import Packet
|
||||||
from .mc.identity import Token
|
from .mc.identity import Token, AuthException
|
||||||
from .mc import proto, encryption
|
from .mc import proto, encryption
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -34,6 +34,7 @@ _STATE_REGS = {
|
||||||
class Client:
|
class Client:
|
||||||
host:str
|
host:str
|
||||||
port:int
|
port:int
|
||||||
|
options:dict
|
||||||
|
|
||||||
username:Optional[str]
|
username:Optional[str]
|
||||||
password:Optional[str]
|
password:Optional[str]
|
||||||
|
@ -41,6 +42,7 @@ class Client:
|
||||||
|
|
||||||
dispatcher : Dispatcher
|
dispatcher : Dispatcher
|
||||||
_processing : bool
|
_processing : bool
|
||||||
|
_authenticated : bool
|
||||||
_worker : Task
|
_worker : Task
|
||||||
|
|
||||||
_packet_callbacks : Dict[ConnectionState, Dict[Packet, List[Callable]]]
|
_packet_callbacks : Dict[ConnectionState, Dict[Packet, List[Callable]]]
|
||||||
|
@ -50,12 +52,14 @@ class Client:
|
||||||
self,
|
self,
|
||||||
host:str,
|
host:str,
|
||||||
port:int,
|
port:int,
|
||||||
|
options:dict = None,
|
||||||
username:Optional[str] = None,
|
username:Optional[str] = None,
|
||||||
password:Optional[str] = None,
|
password:Optional[str] = None,
|
||||||
token:Optional[Token] = None,
|
token:Optional[Token] = None,
|
||||||
):
|
):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.options = options or {}
|
||||||
|
|
||||||
self.token = token
|
self.token = token
|
||||||
self.username = username
|
self.username = username
|
||||||
|
@ -63,6 +67,7 @@ class Client:
|
||||||
|
|
||||||
self.dispatcher = Dispatcher()
|
self.dispatcher = Dispatcher()
|
||||||
self._processing = False
|
self._processing = False
|
||||||
|
self._authenticated = False
|
||||||
|
|
||||||
self._packet_callbacks = {}
|
self._packet_callbacks = {}
|
||||||
|
|
||||||
|
@ -97,15 +102,12 @@ class Client:
|
||||||
self.token = await Token.authenticate(self.username, self.password)
|
self.token = await Token.authenticate(self.username, self.password)
|
||||||
self._logger.info("Authenticated from credentials")
|
self._logger.info("Authenticated from credentials")
|
||||||
return True
|
return True
|
||||||
return False
|
raise AuthException("No token or credentials provided")
|
||||||
try:
|
try:
|
||||||
await self.token.validate() # will raise an exc if token is invalid
|
await self.token.validate() # will raise an exc if token is invalid
|
||||||
except Exception: # idk TODO
|
except AuthException:
|
||||||
try:
|
await self.token.refresh()
|
||||||
await self.token.refresh()
|
self._logger.warning("Refreshed Token")
|
||||||
self._logger.warning("Refreshed Token")
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def change_server(self, server:str):
|
async def change_server(self, server:str):
|
||||||
|
@ -129,10 +131,12 @@ class Client:
|
||||||
await self.start()
|
await self.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True: # TODO don't busywait even if it doesn't matter much
|
while self._processing: # TODO don't busywait even if it doesn't matter much
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self._logger.info("Received SIGINT, stopping...")
|
self._logger.info("Received SIGINT, stopping...")
|
||||||
|
else:
|
||||||
|
self._logger.warning("Client terminating...")
|
||||||
|
|
||||||
await self.stop()
|
await self.stop()
|
||||||
|
|
||||||
|
@ -150,19 +154,25 @@ class Client:
|
||||||
|
|
||||||
async def _client_worker(self):
|
async def _client_worker(self):
|
||||||
while self._processing:
|
while self._processing:
|
||||||
if not await self.authenticate():
|
try:
|
||||||
raise Exception("Token not refreshable or credentials invalid") # TODO!
|
await self.authenticate()
|
||||||
|
except AuthException:
|
||||||
|
self._logger.error("Token not refreshable or credentials invalid")
|
||||||
|
await self.stop(block=False)
|
||||||
try:
|
try:
|
||||||
await self.dispatcher.connect(self.host, self.port)
|
await self.dispatcher.connect(self.host, self.port)
|
||||||
for packet in self._handshake():
|
for packet in self._handshake():
|
||||||
await self.dispatcher.write(packet)
|
await self.dispatcher.write(packet)
|
||||||
self.dispatcher.state = ConnectionState.LOGIN
|
self.dispatcher.state = ConnectionState.LOGIN
|
||||||
await self._process_packets()
|
await self._process_packets()
|
||||||
|
except AuthException as e: # TODO maybe tell what went wrong
|
||||||
|
self._authenticated = False
|
||||||
|
self._logger.error("Authentication exception")
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
self._logger.error("Server rejected connection")
|
self._logger.error("Server rejected connection")
|
||||||
except Exception:
|
except Exception:
|
||||||
self._logger.exception("Exception in Client connection")
|
self._logger.exception("Exception in Client connection")
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(self.options["rctime"])
|
||||||
|
|
||||||
def _handshake(self, force:bool=False) -> Tuple[Packet, Packet]: # TODO make this fancier! poll for version and status first
|
def _handshake(self, force:bool=False) -> Tuple[Packet, Packet]: # TODO make this fancier! poll for version and status first
|
||||||
return ( proto.handshaking.serverbound.PacketSetProtocol(
|
return ( proto.handshaking.serverbound.PacketSetProtocol(
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
"""Minecraft identity utilities."""
|
"""Minecraft identity utilities."""
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
class AuthException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Profile:
|
class Profile:
|
||||||
id : str
|
id : str
|
||||||
|
@ -96,6 +100,9 @@ class Token:
|
||||||
async def _post(cls, endpoint:str, data:dict) -> dict:
|
async def _post(cls, endpoint:str, data:dict) -> dict:
|
||||||
async with aiohttp.ClientSession() as sess:
|
async with aiohttp.ClientSession() as sess:
|
||||||
async with sess.post(endpoint, headers=cls.HEADERS, data=json.dumps(data).encode('utf-8')) as res:
|
async with sess.post(endpoint, headers=cls.HEADERS, data=json.dumps(data).encode('utf-8')) as res:
|
||||||
# TODO parse and raise exceptions
|
data = await res.json(content_type=None)
|
||||||
return await res.json(content_type=None)
|
logging.info(f"Auth request | {data}")
|
||||||
|
if res.status != 200:
|
||||||
|
raise AuthException(f"Action '{endpoint.rsplit('/',1)[1]}' did not succeed")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue