diff --git a/.gitignore b/.gitignore index 2f59327..ecf875d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ commiter.py __pycache__ *.pyc .idea -*.pem \ No newline at end of file +*.pem +__pycache__ \ No newline at end of file diff --git a/README.MD b/README.MD index 19bab53..94346f9 100644 --- a/README.MD +++ b/README.MD @@ -9,6 +9,10 @@ ![Alt Text](example.gif) +# How it works? + +All you need it's to run web-server and connect to them via client + # Server run ## Linux @@ -38,7 +42,7 @@ pip install -r requirements.txt ``` ``` -sanic server.app -H 0.0.0.0 -p +sanic server.server.app -H 0.0.0.0 -p ``` # Client run @@ -69,7 +73,7 @@ pip install -r requirements.txt ``` ``` -python client.py +python client/client.py ``` ## How crypting works? diff --git a/client/client.py b/client/client.py index fa33c0a..456b67f 100644 --- a/client/client.py +++ b/client/client.py @@ -6,6 +6,9 @@ from colorama import init from colorama import Fore from websocket import create_connection from core.crypto import RSAService +from config import ( + COLORS +) init() @@ -25,13 +28,21 @@ class Client(RSAService): self.info_url = f"{self.base_url}/update" self.key_url = f"{self.base_url}/get_key" self.ws_url = f"ws://{self.server}:{self.port}" + self.close_response = str({ + "action": "close", + "username": self.username + }) def __get_os(self) -> str: + """ checking what kind of platform you need + """ if "Linux" in str(platform.platform()): return "Linux" return "Windows" def send_info(self): + """ sending message to websocket + """ ws = create_connection(f"{self.ws_url}/talk") while True: try: @@ -41,20 +52,26 @@ class Client(RSAService): "text": self._encrypt(message), "username": self.username }) - ws.send(payload=socket_message.encode()) + ws.send( + payload=socket_message.encode() + ) except KeyboardInterrupt: + ws.send(self.close_response) ws.close() quit() except Exception as exc: + ws.send(self.close_response) ws.close() print("Something went wrong! ", exc) quit() - def print_message(self, message: str) -> str: + def __print_message(self, message: str) -> str: + """ generating string with message in required format + """ message = message.split(":") if message[0] == self.username: - return Fore.MAGENTA + message[0] + ": " + message[1] + Fore.WHITE - return message[0] + ": " + message[1] + Fore.WHITE + return COLORS["my_username_color"] + message[0] + ": " + message[1] + COLORS["text_color"] + return message[0] + ": " + message[1] + COLORS["text_color"] def __clear_console(self): # For windows clear command its cls @@ -64,31 +81,54 @@ class Client(RSAService): else: os.system("cls") + def __print_ip( + self, + ip: str + ) -> str: + return f"IP: " + COLORS["ip_color"] + ip + COLORS["text_color"] + + def __print_username( + self, + username: str + ) -> str: + return f"USERNAME: " + COLORS["ip_color"] + username + COLORS["username_color"] + + def __print_chat(self, response: list[str]) -> str: + for i, msg in enumerate(response["messages"]): + actual_message = self._decrypt(msg) + if i == 0: + for user in response["users_in_chat"]: + print(self.__print_ip(user.split(",")[0])) + print(self.__print_username(user.split(",")[1])) + print(f"\n{self.__print_message(actual_message)}") + else: + print(f"{self.__print_message(actual_message)}") + def update_info(self): + """ connecting to websocket, + wating for updates, + updating every 0.05 seconds + """ ws = create_connection(f"{self.ws_url}/update") last_try = None while True: try: time.sleep(0.05) - r = eval(ws.recv()) - if last_try == r: + response = eval(ws.recv()) + if last_try == response: continue - last_try = r + last_try = response self.__clear_console() - if len(last_try['status']) > 0: - for i, msg in enumerate(last_try["status"]): - actual_message = self._decrypt(msg) - if i == 0: - for user in last_try["users_in_chat"]: - print("IP:", Fore.MAGENTA + user.split(",")[0] + Fore.WHITE) - print("USERNAME: ", Fore.GREEN + user.split(",")[1] + Fore.WHITE) - print(f"\n{self.print_message(actual_message)}") - else: - print(f"{self.print_message(actual_message)}") + if len(last_try["messages"]) > 0: + self.__print_chat( + response = last_try + ) except KeyboardInterrupt: + ws.send(self.close_response) ws.close() quit() except Exception as exc: + ws.send(self.close_response) ws.close() print("Something went wrong! ", exc) quit() diff --git a/client/config.py b/client/config.py new file mode 100644 index 0000000..c12aa11 --- /dev/null +++ b/client/config.py @@ -0,0 +1,8 @@ +from colorama import Fore + +COLORS = { + "text_color": Fore.WHITE, + "my_username_color": Fore.MAGENTA, + "ip_color": Fore.MAGENTA, + "username_color": Fore.GREEN +} \ No newline at end of file diff --git a/server/server.py b/server/server.py index a6e8da9..6921be8 100644 --- a/server/server.py +++ b/server/server.py @@ -1,53 +1,68 @@ -import rsa import asyncio -from server.models import Message + +import rsa from cryptography.fernet import Fernet + from sanic.response import HTTPResponse from sanic import Sanic, Request, response, Websocket +from server.models import Message +from server.services import ( + _get_bytes_and_serialize, + _check_ws_for_close_status, + _generate_new_message, + _generate_update_payload +) + + app = Sanic("app") app.config.OAS = False + # Message structure is: # [username: message, ...] -actual_messages: list[Message] = [] +MESSAGES_MEMORY_DB: list[Message] = [] + + # Users structure is # {Ip, Username: Public key} -users: dict[str, str] = {} -key = Fernet.generate_key() +USERS: dict[str, str] = {} +PUBLIC_KEY = Fernet.generate_key() @app.websocket("/talk") -async def talking(request: Request, ws: Websocket) -> HTTPResponse: +async def talk_ws_view(request: Request, ws: Websocket) -> HTTPResponse: while True: - data: str = await ws.recv() - serialized_message: dict = eval(data) - new_message = Message( - message=serialized_message.get("text") + serialized_message: dict = await _get_bytes_and_serialize(ws) + await _check_ws_for_close_status( + serialized_message, + ws + ) + new_message = await _generate_new_message( + serialized_message.get("text") + ) + MESSAGES_MEMORY_DB.append(new_message) + await ws.send( + str({"status": "ok"}) ) - actual_messages.append(new_message) - await ws.send("{'status': 'ok'}") await asyncio.sleep(0.2) @app.websocket("/update") -async def talking(request: Request, ws: Websocket) -> HTTPResponse: +async def update_ws_view(request: Request, ws: Websocket) -> HTTPResponse: while True: - payload = str({ - "status": [i.message for i in actual_messages], - "users_in_chat": list(users.keys()) - }) + payload = await _generate_update_payload( + MESSAGES_MEMORY_DB, + USERS + ) await ws.send(payload.encode()) await asyncio.sleep(0.2) @app.route('/get_key', methods=['GET', 'POST']) -async def get_key(request: Request) -> HTTPResponse: - - pubkey = rsa.PublicKey.load_pkcs1(request.form.get('pubkey')) - data = rsa.encrypt(key, pubkey) - - if request.ip not in users: - users[f"{request.ip}, {request.form.get('username')}"] = key - - return response.raw(data) \ No newline at end of file +async def get_key_view(request: Request) -> HTTPResponse: + public_key = rsa.PublicKey.load_pkcs1(request.form.get('pubkey')) + encrypted_data = rsa.encrypt(PUBLIC_KEY, public_key) + if request.ip not in USERS: + USERS[f"{request.ip}, {request.form.get('username')}"] = PUBLIC_KEY + return response.raw(encrypted_data) \ No newline at end of file diff --git a/server/services.py b/server/services.py new file mode 100644 index 0000000..bab8931 --- /dev/null +++ b/server/services.py @@ -0,0 +1,35 @@ +from sanic import Sanic, Request, response, Websocket +from server.models import Message + + +async def _get_bytes_and_serialize( + ws: Websocket +) -> dict: + return eval(await ws.recv()) + + +async def _check_ws_for_close_status( + response: dict, + ws: Websocket +) -> None: + if "action" in response.keys(): + if response["action"] == "close": + await ws.close() + + +async def _generate_new_message( + message: str +) -> Message: + return Message(message = message) + + +async def _generate_update_payload( + memory_msgs: list[str], + users_structure: dict +) -> str: + return str({ + "messages": [i.message for i in memory_msgs], + "users_in_chat": list(users_structure.keys()) + }) + +