diff --git a/CLIclient_main.py b/CLIclient_main.py index ba31b8d..7a8fc7a 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -1,10 +1,9 @@ import asyncio import websockets import os -from cryptography.hazmat.primitives import serialization as s from client_modules import encryption as en from client_modules import packet_handler as p -from client_modules import db_handler as db +from client_modules import cli_menus as cli from client_modules import first_run from uuid import uuid4 import i18n @@ -13,60 +12,74 @@ import pickle from sys import exit + async def send_message(websocket, message): - outpacket = en.encrypt_data( - message, SERVER_CREDS['server_epbkey'] - ) - await websocket.send(outpacket) - response = await websocket.recv() - return await recv_message(websocket, response) - -async def handle_resp(websocket, response): - inpacket = en.decrypt_data(response, CLIENT_CREDS['client_eprkey']) - print(inpacket) - handled = await p.handle(SERVER_CREDS, CLIENT_CREDS, websocket, inpacket) # handle here using type and data - print('resposne handaled') - return handled + if message: + await websocket.send(message) + async def recv_message(websocket, message): inpacket = en.decrypt_data(message, CLIENT_CREDS['client_eprkey']) # handle check - await p.handle(SERVER_CREDS, CLIENT_CREDS, websocket, inpacket) + outpacket = await p.handle(SERVER_CREDS, CLIENT_CREDS, websocket, inpacket) + if not outpacket: + await websocket.send(outpacket) + else: + pass + def execute_firstrun(): first_run.main() - print(i18n.firstrun.security) - db.decrypt_creds(en.fermat_gen(first_run.working_dir.workingdir), first_run.working_dir.workingdir) - print(i18n.firstrun.initialize_db) - db.initialize_schemas() - db.close() print(i18n.firstrun.exit) exit() -def check_missing_config(f, yaml, config): - try: - if yaml[config] is None: - print(i18n.firstrun.prompt1 + config) - if config == 'working_directory': - print(i18n.firstrun.prompt2) - while True: - choice = input("(Y / N) > ") - if choice.lower() == 'y': - print(i18n.firstrun.exec) - f.close() - os.remove(f'{rootdir}/config.yml') - first_run.main() - print(i18n.firstrun.exit) - exit() - elif choice.lower() == 'n': - fill_missing_config(f, yaml, 'working_directory') - break - else: - fill_missing_config(f, yaml, config) - except KeyError: - print(i18n.firstrun.prompt1 + config) - fill_missing_config(f, yaml, config) +# def check_missing_config(f, yaml, config): +# try: +# if yaml[config] is None: +# print(i18n.firstrun.prompt1 + config) +# if config == 'working_directory': +# print(i18n.firstrun.prompt2) +# while True: +# choice = input("(Y / N) > ") +# if choice.lower() == 'y': +# print(i18n.firstrun.exec) +# f.close() +# os.remove(f'{rootdir}/config.yml') +# first_run.main() +# print(i18n.firstrun.exit) +# exit() +# elif choice.lower() == 'n': +# fill_missing_config(f, yaml, 'working_directory') +# break +# else: +# fill_missing_config(f, yaml, config) +# except KeyError: +# print(i18n.firstrun.prompt1 + config) +# fill_missing_config(f, yaml, config) + +def check_missing_config(file, yaml_config, config_key): + if yaml_config.get(config_key) is not None: + return + print(i18n.firstrun.setting_not_found.format(config_key)) + + if config_key != 'working_directory': + fill_missing_config(file, yaml_config, config_key) + return + print(i18n.firstrun.ft_question) + + while True: + choice = input("(Y / N) > ").lower() + if choice == 'y': + print(i18n.firstrun.exec) + file.close() + os.remove(f'{rootdir}/config.yml') + first_run.main() + print(i18n.firstrun.exit) + exit() + elif choice == 'n': + fill_missing_config(file, yaml_config, 'working_directory') + break def fill_missing_config(f, yaml, config): @@ -77,32 +90,30 @@ def fill_missing_config(f, yaml, config): f.seek(0) f.write(dumpyaml(yaml)) + async def main(host, port): uri = f"ws://{host}:{port}" - async with websockets.connect(uri, ping_interval=30) as websocket: + async with websockets.connect(uri, ping_interval=120) as websocket: con_id = str(uuid4()) - await websocket.send(pickle.dumps({'type':'CONN_INIT', 'data':con_id})) + await websocket.send(pickle.dumps({'type': 'CONN_INIT', 'data': con_id})) try: key = await websocket.recv() key = pickle.loads(key) - pubkey = s.load_pem_public_key(key['data']) + pubkey = en.deser_pem(key['data'], 'public') SERVER_CREDS['server_epbkey'] = pubkey print("RECEIVED SERVER PUBLIC KEY") - await websocket.send(pickle.dumps({'type':'CONN_ENCRYPT_C','data':CLIENT_CREDS['client_epbkey']})) + await websocket.send(pickle.dumps({'type': 'CONN_ENCRYPT_C', 'data': CLIENT_CREDS['client_epbkey']})) print(en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey'])) - except websockets.exceptions.ConnectionClosedError as err: - print("Disconected from Server! Error:\n",err) + except websockets.exceptions.ConnectionClosedError as error: + print("Disconnected from Server! Error:\n", error) exit() - + while True: try: - await asyncio.gather( - send_message(websocket), - recv_message(websocket, await websocket.recv()), - ) - except Exception as eroa: - print("Disconected from Server! Error:\n",eroa) + await send_message(websocket, await cli.main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, workingdir)) + except Exception as e: + print("Disconnected from Server! Error:\n", e) exit() @@ -132,64 +143,7 @@ async def main(host, port): execute_firstrun() workingdir = yaml['working_directory'] + CLIENT_CREDS['workingdir'] = workingdir host, port = yaml['homeserver_address'], yaml['homeserver_port'] - try: - fkey = en.fermat_gen(workingdir) - db.decrypt_creds(fkey, workingdir) - except Exception as w: - print("Error while decrypting database credentials. Check your password\n", w) - print(i18n.firstrun.exit) - exit() - - try: - with open(f'{workingdir}/creds/chat_publickey', 'rb') as f: - pem_pubkey = f.read() - pubkey = en.deser_pem(pem_pubkey, 'public') - except FileNotFoundError: - print("Could not find chat encryption public key. Client will now generate it from the private key.") - try: - with open(f'{workingdir}/creds/chat_privatekey', 'rb') as f: - en_pem_prkey = f.read() - pem_prkey = fkey.decrypt(en_pem_prkey) - prkey = en.deser_pem(pem_prkey, 'private') - pubkey = prkey.public_key() - - with open(f'{workingdir}/creds/chat_publickey', 'wb') as f: - f.write(pem_pubkey) - except FileNotFoundError: - print("Could not find chat encryption keypair. Client will now generate it again.") - ch = input("THIS OPERATION IS NOT SUPPORTED YET. Continue? (y/n) > ") - if ch.lower() == 'y': - db.clear_queue(user=None) - first_run.save_chat_keypair(fkey, workingdir) - print(i18n.firstrun.exit) - exit() - if ch.lower() == 'n': - print("Key ah kaanume enna panradhu ippo?") - exit() - else: - with open(f'{workingdir}/creds/chat_privatekey', 'rb') as f: - en_pem_prkey = f.read() - pem_prkey = fkey.decrypt(en_pem_prkey) - prkey = en.deser_pem(pem_prkey, 'private') - - asyncio.get_event_loop().run_until_complete(main(host,port)) - asyncio.get_event_loop().run_forever() - -""" -import asyncio -import websockets - -# Main function to connect to the server and start the message handlers -async def main(): - async with websockets.connect(uri) as websocket: - # Start the message handlers - await asyncio.gather( - handle_incoming_messages(websocket), - handle_outgoing_messages(websocket), - ) - -# Run the main function -asyncio.run(main()) -""" \ No newline at end of file + asyncio.run(main(host, port)) diff --git a/README.md b/README.md index bed252b..54760f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ -# client-framework - -This repo provides all the necessary functions to interact with the [pesupy-chat server software](https://github.com/pesupy-chat/server). -It also includes an example CLI client which can also be used regularly. (CURRENTLY NOT FINISHED) +# README PENDING diff --git a/captcha.png b/captcha.png deleted file mode 100644 index 15f268d..0000000 Binary files a/captcha.png and /dev/null differ diff --git a/client_modules/cli_menus.py b/client_modules/cli_menus.py index 991b95c..36e7fb2 100644 --- a/client_modules/cli_menus.py +++ b/client_modules/cli_menus.py @@ -1,7 +1,11 @@ import os, sys from . import packet_handler as p +import asyncio +from i18n import demo +from i18n import menu def cls(): + # function return value is assigned to _ to prevent it from printing to the console # for windows if os.name == 'nt': _ = os.system('cls') @@ -9,36 +13,40 @@ def cls(): else: _ = os.system('clear') -def main_menu(): + +async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, wdir): + print(menu.the_menu) while True: - cls() - print( -""" ...... . .. - -======+++**##%+ %*---%::% @- - +@#+++*@@+=--:-%%. %. .% *= .+% - *@- :@# .%%. :@+++*- -+==#= - #@: -@#..:::-%%. =* .%. %. - .%%. :%%%%#####= :*- =- :%%###%%%%%+ *: +%+++*= - .@% *@- :@# ..-@@=:-@%:.. - -@* .=++++***:. %@: :@# .#@%%@@%%%@@###***= - =@+ .%@+===-=%%- %%. -@* :@# -@* -@#:*@*--. - +@= .%%. *@+ @# :@# :@# =@+ -@* =@+ - #@: .:%%======#@* @#-----==*@* -@%+#@#+=#@= +@= -.#@%%%%%%##*+++++=:. =******+++=. :===+==++- +@= CHAT""") - print("Welcome!") - print("Choose an operation:") - print("1. Sign up") - print("2. Log in") - print("3. Exit") - c = int(input("> ")) + print(menu.input_p) + c = input("> ").lower() match c: - case 1: - signup() - case 2: - login() - case 3: - print("Goodbye!") + case 'signup': + cls() + return await p.signup(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) + case 'login': + cls() + return await p.login(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) + case 'auth': + cls() + return await p.auth(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) + case 'logout': + cls() + if input(demo.logout_p + '\n(y/N) > ').lower() == 'y': + flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) + return flag + else: + print(demo.logout_cancel) + case 'del': + cls() + print() + c = input("(y/N) > ") + if c.lower() == 'y': + flag = await p.delete(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) + return flag + else: + print() + case 'exit': + print(menu.exit_msg) sys.exit() - -def signup(): - p.signup() \ No newline at end of file + case other: + print(menu.invalid_op) diff --git a/client_modules/conn_handler.py b/client_modules/conn_handler.py deleted file mode 100644 index e69de29..0000000 diff --git a/client_modules/db_handler.py b/client_modules/db_handler.py deleted file mode 100644 index 6154049..0000000 --- a/client_modules/db_handler.py +++ /dev/null @@ -1,78 +0,0 @@ -import sqlite3 -import os -import pickle - -# for now, all the data lies in a single db file. this must be changed in the future. -initialize = """ -CREATE TABLE IF NOT EXISTS users ( - UUID TEXT NOT NULL, - USERNAME TEXT UNIQUE NOT NULL, - FULL_NAME TEXT NOT NULL, - DOB TEXT NOT NULL, - EMAIL TEXT, - CREATION DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (UUID) -); -CREATE TABLE IF NOT EXISTS rooms ( - ID INTEGER PRIMARY KEY AUTOINCREMENT, - CREATOR_UUID TEXT NOT NULL, - ROOM_TYPE INTEGER NOT NULL, - MEMBERS BLOB NOT NULL, - CHAT_TABLE TEXT NOT NULL, - FOREIGN KEY(CREATOR_UUID) REFERENCES chatapp_accounts.users(UUID) -); -""" -createroom = """CREATE TABLE ? ( - messageID INTEGER PRIMARY KEY AUTOINCREMENT, - messageUUID TEXT UNIQUE NOT NULL, - sender TEXT NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - message BLOB NOT NULL, - type TEXT NOT NULL, - pinned INTEGER NOT NULL DEFAULT 0 -); -""" - -queries = {'initialize': initialize, 'create_room':createroom} -fields_to_check = { - 'username':{'table':'users','attribute':'USERNAME'}, - 'room':{'table':'rooms', 'attribute':'CHAT_TABLE'} - } - -class db: - con = None - cur = None - -def create_db(workingdir): # todo: add password protection - conn = sqlite3.connect(f'{workingdir}/DATA.db') - conn.close() - -def decrypt_creds(fkey, workingdir): - with open(f'{workingdir}/creds/db', 'rb') as f: - data = f.read() - decrypted = fkey.decrypt(data) - dict = pickle.loads(decrypted) - try: - setattr(db, 'con', sqlite3.connect(dict['file'])) - setattr(db, 'cur', db.con.cursor()) - print('[INFO] Loaded Database') - except Exception as e: - print('[ERROR] Could not load database:', e) - -# # Create a connection object with the database -# conn = sqlite3.connect('my_database.db') - -# # Set password for the db file -# conn.execute("PRAGMA key='password'") - -# # Create tables -# conn.execute('''CREATE TABLE IF NOT EXISTS table1 (column1 datatype PRIMARY KEY (one or more columns), column2 datatype, column3 datatype, ….. columnN datatype);''') -# conn.execute('''CREATE TABLE IF NOT EXISTS table2 (column1 datatype PRIMARY KEY (one or more columns), column2 datatype, column3 datatype, ….. columnN datatype);''') - -# # Insert data into tables -# conn.execute("INSERT INTO table1 VALUES ('value1', 'value2', 'value3')") -# conn.execute("INSERT INTO table2 VALUES ('value4', 'value5', 'value6')") - -# # Commit changes and close connection -# conn.commit() -# conn.close() \ No newline at end of file diff --git a/client_modules/encryption.py b/client_modules/encryption.py index 9885983..708973a 100644 --- a/client_modules/encryption.py +++ b/client_modules/encryption.py @@ -1,19 +1,8 @@ -from cryptography.hazmat.primitives import serialization, hashes, hmac -from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import padding as spadding -from cryptography.fernet import Fernet from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -### For chat operations -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -# from cryptography.hazmat.primitives.asymmetric import ec -# from cryptography.hazmat.primitives.kdf.hkdf import HKDF -# from cryptography.hazmat.primitives.ciphers.aead import AESGCM from os import urandom -from getpass import getpass -from base64 import urlsafe_b64encode -import pickle -import i18n import pickle @@ -27,6 +16,7 @@ def create_rsa_key_pair(): public_key = private_key.public_key() return private_key, public_key + def ser_key_pem(key, type: str): if type == 'public': return key.public_bytes( @@ -39,12 +29,15 @@ def ser_key_pem(key, type: str): format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) + + def deser_pem(key, type): if type == 'public': return serialization.load_pem_public_key(key) elif type == 'private': return serialization.load_pem_private_key(key, password=None) + def encrypt_data(data, pubkey): data = pickle.dumps(data) # Generate symmetric key and encrypt it @@ -67,7 +60,8 @@ def encrypt_data(data, pubkey): encryptor = cipher.encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() - return pickle.dumps({'skey':encrypted_skey, 'cbc':cbc, 'ciphertext':ciphertext}) + return pickle.dumps({'skey': encrypted_skey, 'cbc': cbc, 'ciphertext': ciphertext}) + def decrypt_data(encrypted_data, privkey): try: @@ -93,60 +87,4 @@ def decrypt_data(encrypted_data, privkey): data = unpadder.update(decrypted_data) + unpadder.finalize() return pickle.loads(data) except Exception as error: - return {'type':'decrypt_error','data':f'{error}'} - -def fernet_initkey(workingdir): - passwd = '' - while True: - passwd = getpass(i18n.firstrun.passwd.input) - confirm = getpass(i18n.firstrun.passwd.confirm) - if passwd == confirm: - break - else: - print(i18n.firstrun.passwd.retry) - # Generate a Fernet key with the password and save the salt - salt = urandom(16) - with open(f"{workingdir}/creds/salt", "wb") as f: - f.write(salt) - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=600000, - ) - key = urlsafe_b64encode(kdf.derive(bytes(passwd, 'utf-8'))) - key = Fernet(key) - return key, passwd - -""" THIS PART OF THE MODULE IS RESERVED FOR CHAT OPERATIONS """ - -#def create_key_pair(): -# private_key_d = ec.generate_private_key(ec.SECP256K1()) -# public_key_d = private_key_d.public_key() -# return private_key_d, public_key_d - -#def derive_key(eprkey, epbkey, keyinfo): -# shared_key = eprkey.exchange( -# ec.ECDH(), epbkey) -# # Perform key derivation. -# derived_key = HKDF( -# algorithm=hashes.SHA256(), -# length=32, -# salt=None, -# info=keyinfo.encode(), -# ).derive(shared_key) -# return derived_key - -#def encrypt_packet(data, key): -# data = pickle.dumps(data) -# aesgcm = AESGCM(key) -# nonce = urandom(12) # Unique nonce for each message -# ciphertext = aesgcm.encrypt(nonce, data, None) -# return pickle.dumps({'nonce': nonce, 'ciphertext': ciphertext}) - -#def decrypt_packet(data, key): -# aesgcm = AESGCM(key) -# nonce = data['nonce'] -# ciphertext = data['ciphertext'] -# plaintext = aesgcm.decrypt(nonce, ciphertext, None) -# return plaintext \ No newline at end of file + return {'type': 'decrypt_error', 'data': f'{error}'} diff --git a/client_modules/first_run.py b/client_modules/first_run.py index c00fa1c..ed9dd9e 100644 --- a/client_modules/first_run.py +++ b/client_modules/first_run.py @@ -1,26 +1,28 @@ import os -from i18n import firstrun +from i18n import firstrun, savedata from yaml import dump as dumpyaml from . import encryption as e -from . import db_handler as db -try: + +try: from tkinter import filedialog except: pass + def get_client_dir(): while True: try: # Open GUI file picker if possible - print(firstrun.savedata.gui) + print(savedata.gui) spath = filedialog.askdirectory() except: - print(firstrun.savedata.nogui) + print(savedata.nogui) spath = input().rstrip('/\\') finally: break return spath + def setup_client_dir(): while True: while True: @@ -30,45 +32,34 @@ def setup_client_dir(): break # If either the path leads to a file or is not writable (or invalid) elif os.path.exists(spath) and not os.path.isdir(spath): - spath = input(f"{firstrun.savedata.not_a_dir}:\n") + print(f"{savedata.not_a_dir}:\n") + spath = get_client_dir() elif not os.path.exists(spath): - print(firstrun.savedata.creating, end=' ') + print(savedata.creating, end=' ') try: os.mkdir(spath) - except OSError as e: - print(f"{firstrun.savedata.error}:\n{e}") - spath = input(f"{firstrun.savedata.input_writable}:\n") + except OSError as error: + print(f"{savedata.error}:\n{error}") + print(f"{savedata.input_writable}:\n") + spath = get_client_dir() else: - print(firstrun.savedata.created) + print(savedata.created) break if not os.path.exists(f'{spath}/creds'): os.mkdir(f'{spath}/creds') break elif os.path.exists(f'{spath}/creds'): - print(firstrun.savedata.data_exists) + print(savedata.data_exists) return spath -def save_chat_keypair(fkey, workingdir): - prkey, pubkey = e.create_rsa_key_pair() - pem_prkey, pem_pubkey = e.ser_key_pem(prkey, 'private'), e.ser_key_pem(pubkey, 'public') - en_pem_prkey = fkey.encrypt(pem_prkey) - with open(f'{workingdir}/creds/chat_privatekey', 'wb') as f: - f.write(en_pem_prkey) - with open(f'{workingdir}/creds/chat_publickey', 'wb') as f: - f.write(pem_pubkey) def main(): print(firstrun.welcome_message) print(firstrun.setup_client_dir) workingdir = setup_client_dir() - fkey, passwd = e.fernet_initkey(workingdir) - save_chat_keypair(fkey, workingdir) - db.create_db(workingdir) # todo : add password protection - del passwd - del fkey - print("You will be able to change these later") - host = input("Enter Homeserver Address: ") - port = int(input("Enter Homeserver Port: ")) + print(firstrun.config_modinfo) + host = input(firstrun.hs_addr) + port = int(input(firstrun.hs_port)) with open(f'{os.path.dirname(os.path.abspath(__file__))}/../config.yml', 'w') as fi: config = {'working_directory': workingdir, 'homeserver_address': host, 'homeserver_port': port} - fi.write(dumpyaml(config)) \ No newline at end of file + fi.write(dumpyaml(config)) diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index fcb70aa..38eb843 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -5,103 +5,136 @@ from . import encryption as en from getpass import getpass from yaml import load as loadyaml +from i18n import sig_map, demo, log -sig_map = { - 'CONN_OK':"Connection to the server was successful", - 'CAPTCHA_WRONG':"The CAPTCHA code you entered was wrong. Try performing the action again", - 'SIGNUP_OK':"The account was successfully created", - 'SIGNUP_MISSING_CREDS': '', - 'SIGNUP_MISSING_CREDS': '', - 'SIGNUP_USERNAME_ABOVE_LIMIT': '', - 'SIGNUP_USERNAME_ALREADY_EXISTS': '', - 'SIGNUP_EMAIL_ABOVE_LIMIT': '', - 'SIGNUP_NAME_ABOVE_LIMIT': '', - 'SIGNUP_DOB_INVALID': '', - 'SIGNUP_PASSWORD_ABOVE_LIMIT': '', - 'SIGNUP_ERR': '', - 'LOGIN_MISSING_CREDS': '', - 'LOGIN_INCORRECT_PASSWORD': '', - 'LOGIN_ACCOUNT_NOT_FOUND': '', - 'LOGIN_OK': '', - 'QUEUE_END': '', - 'TOKEN_EXPIRED': '', - 'TOKEN_INVALID': '', - 'CHAT_PUBKEY_MISSING': '', - 'CHAT_PUBKEY_INVALID': '', - 'CHAT_PUBKEY_OK': '', - 'ROOM_DNE': '', - 'MKROOM_INSUFFICIENT_PARTICIPANTS': '', - 'MKROOM_NOT_IMPLEMENTED': '', - 'MKROOM_OK': '', - 'SENT': '', - 'MSG_NOT_YOURS': '' -} async def status(SERVER_CREDS, CLIENT_CREDS, websocket, data): - if data['sig'] == 'CHAT_PUBKEY_MISSING': - await send_pubkey(SERVER_CREDS, CLIENT_CREDS, websocket) print(sig_map[data['sig']]) + async def captcha(SERVER_CREDS, CLIENT_CREDS, websocket, data): - with open('captcha.png','wb') as f: + with open('captcha.png', 'wb') as f: f.write(data['challenge']) - print("Open the captcha.png in the client folder and enter the digits here:") + print(demo.captcha_p) solved = int(input("> ")) - solved_packet = pickle.dumps({'type':'S_CAPTCHA', 'data':{'solved':solved}}) - outpacket = en.encrypt_data(solved_packet, SERVER_CREDS['server_epbkey']) - await websocket.send(outpacket) - return en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) - -async def send_pubkey(SERVER_CREDS, CLIENT_CREDS, websocket): - rootdir = os.path.dirname(os.path.abspath(__file__)) - f = open(f'{rootdir}/config.yml', 'r+') - yaml = loadyaml(f.read()) + solved_packet = {'type': 'S_CAPTCHA', 'data': {'solved': solved}} + await websocket.send(en.encrypt_data(solved_packet, SERVER_CREDS['server_epbkey'])) + resp = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + if resp['type'] == 'STATUS': + return resp['data']['sig'] + else: + return resp -def signup(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): +async def signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir): data = {} - data['user'] = input("Enter your username: ") - data['email'] = input("Enter your email: ") - data['fullname'] = input("Enter your full name: ") - data['dob'] = input("Enter your date of birth (YYYY-MM-DD or DD-MM-YYYY): ") - pwd = getpass("Enter your password: ") - if pwd == getpass("Confirm your password: "): - data['password'] = pwd + data['user'] = input(demo.user_p) + data['email'] = input(demo.email_p) + data['fullname'] = input(demo.name_p) + data['dob'] = input(demo.dob_p.format("(YYYY-MM-DD or DD-MM-YYYY)")) + while True: + pwd = getpass(demo.pass_p) + conf = getpass(demo.pass_confirm) + if pwd == conf: + data['password'] = pwd + break + else: + print(demo.pass_mismatch) + await websocket.send(en.encrypt_data({'type': 'SIGNUP', 'data': data}, SERVER_CREDS['server_epbkey'])) + captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) + if captcha_flag == 'SIGNUP_OK': + print(demo.signup_success) else: - return 'ERR_CONFIRM' - return en.encrypt_data({'type':'SIGNUP', 'data':data}, SERVER_CREDS['server_epbkey']) - -def login(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): + print(demo.error_i, sig_map[captcha_flag]) + + +async def login(SERVER_CREDS, CLIENT_CREDS, websocket, dir): # {'type':'LOGIN', 'data':{'id':username/email,'password':password,'save':True or False}} - data = {} - data['id'] = input("Enter username/email: ") - data['password'] = getpass("Enter your password: ") - if input("Save login? No need to re-login for 30 days (y/N)").lower() == 'y': + data = {'id': input(demo.id_p), 'password': getpass(demo.pass_p)} + if input(demo.save_p + ' (y/N) > ').lower() == 'y': data['save'] = True else: data['save'] = False - return en.encrypt_data({'type':'LOGIN', 'data':data}, SERVER_CREDS['server_epbkey']) + await websocket.send(en.encrypt_data({'type': 'LOGIN', 'data': data}, SERVER_CREDS['server_epbkey'])) + captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) + if isinstance(captcha_flag, str): + print(captcha_flag + ":", sig_map[captcha_flag]) + else: + await save_token(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_flag) + + +async def save_token(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): + # {'type':'TOKEN_GEN','data':{'token':access_token}} + dir = CLIENT_CREDS['workingdir'] + try: + with open(dir + '/creds/acc_token', 'w') as f: + f.write(de_p['data']['token']) + print(demo.token_got) + except Exception as ee: + print(demo.token_fail.format(ee)) + + +async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + user = input(demo.id_p) + try: + with open(dir + '/creds/acc_token', 'r') as f: + token = f.read().rstrip('\r\n') + if token: + await websocket.send(en.encrypt_data({'type': 'AUTH_TOKEN', 'data': {'user': user, 'token': token}}, + SERVER_CREDS['server_epbkey'])) + elif not token: + print(demo.token_nf) + return None + flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + if flag['data']['sig'] == 'LOGIN_OK': + print(demo.login_success) + else: + print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) + + except Exception as ee: + print(demo.auth_err.format(ee)) +async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + await websocket.send(en.encrypt_data({'type': 'LOGOUT', 'data': {}}, SERVER_CREDS['server_epbkey'])) + flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + match flag['data']['sig']: + case 'LOGOUT_OK': + print(demo.logout_success) + os.remove(dir + '/creds/acc_token') + case other: + print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) + + +async def delete(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + data = {'id': input(demo.id_p), 'password': getpass(demo.pass_p)} + await websocket.send(en.encrypt_data({'type': 'DELETE', 'data': data}, SERVER_CREDS['server_epbkey'])) + captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) + match captcha_flag: + case 'ACC_DELETE_SUCCESS': + os.remove(dir + '/creds/acc_token') + # os.remove('sayori.chr') + # os.remove('natsuki.chr') + # os.remove('yuri.chr') + # Just Monika. + case other: + print(captcha_flag + ":", sig_map[captcha_flag]) + packet_map = { - 'STATUS':status, - 'CAPTCHA':captcha, -# 'TOKEN_GEN':save_token, + 'STATUS': status, + 'CAPTCHA': captcha, + 'TOKEN_GEN': save_token +} + -} async def handle(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): type = de_packet['type'] data = de_packet['data'] - + print(log.tags.debug + log.handle_packet.format(type)) if type in packet_map.keys(): func = packet_map[type] return await func(SERVER_CREDS, CLIENT_CREDS, websocket, data) - -async def dispatch(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): - type = de_packet['type'] - data = de_packet['data'] - - if type in packet_map.keys(): - func = packet_map[type] - await websocket.send(await func(SERVER_CREDS, CLIENT_CREDS, websocket, data)) \ No newline at end of file diff --git a/i18n.py b/i18n.py index da2d25b..8ff5684 100644 --- a/i18n.py +++ b/i18n.py @@ -1,37 +1,100 @@ -class firstrun(): +class firstrun: prompt1 = "Could not determine client's " - empty_config = "client configuration is empty. Deleting..." + empty_config = "Client configuration is empty. Deleting..." prompt2 = "Is this the first time you are running the client?" config_not_found = "Configuration file not found!" - exec = "client will now run its configuration process" - fix_missing = "Please enter the client's" - welcome_message = "Welcome to PesuPy Chat client Software!" - setup_client_dir = "Please enter the path to a folder where the client can store its files" - keypair_setup = "Setting up client Keypair..." + exec = "Client will now run its configuration process" + fix_missing = "Please enter the Client's" + welcome_message = "Welcome to the Account System Demonstration Frontend" + setup_client_dir = "Please enter the path to a folder where the Client can store its files" + keypair_setup = "Setting up Client Keypair..." initialize_db = "Setting up Databases for use..." - security = "For security reasons, enter client launch password again." exit = "Client will now exit. Please run it again!" - class savedata(): - gui = "Opening file chooser dialog..." - nogui = "Cannot open file chooser! Enter the path manually:" - error = "An error occurred" - write_error = "An error occurred while trying to write client files.\nPlease choose another path" - not_a_dir = "Please enter path to a folder!" - creating = "Folder not accessible." - input_writable = "Please enter a writeable folder path" - created = "Written Client files successfully." - created_new = "Created Client folder successfully." - data_exists = "Previous Installation Detected! Please delete the files or choose another folder." - class passwd(): - explain = "\ - \ - " - input = "Enter the client's launch password: " - confirm = "Enter it again to confirm: " - retry = "Passwords do not match!" -class log(): - class tags(): + config_modinfo = "You will be able to change these later" + hs_addr = "Enter Homeserver Address: " + hs_port = "Enter Homeserver Port: " + + +class savedata: + gui = "Opening file chooser dialog..." + nogui = "Cannot open file chooser! Enter the path manually:" + error = "An error occurred" + write_error = "An error occurred while trying to write client files.\nPlease choose another path" + not_a_dir = "Please enter path to a folder!" + creating = "Folder not accessible." + input_writable = "Please enter a writeable folder path" + created = "Written Client files successfully." + created_new = "Created Client folder successfully." + data_exists = "Previous Installation Detected! Please delete the files or choose another folder." + + +class demo: + captcha_p = "Open the captcha.png in the client folder and enter the digits here:" + user_p = "Enter your username: " + email_p = "Enter your email: " + name_p = "Enter your full name: " + dob_p = "Enter your date of birth in the format {}: " + pass_p = "Enter your password: " + pass_confirm = "Confirm your password: " + pass_mismatch = "Passwords don't match. Try again" + error_i = "An error occurred:" + signup_success = "Signup complete! Login with your credentials." + id_p = "Enter username/email: " + save_p = "Save login? No need to re-login for 30 days" + token_got = "Successfully acquired token." + token_fail = "Token generation failed. Please try again. Error:\n{}" + token_nf = "You have to login first! Token not found." + auth_err = "Token authentication failed. Please login again. Error:\n{}" + logout_p = "Are you sure you want to log out?\nThis will reset your token and you will have to login again." + logout_cancel = "Logout cancelled." + logout_success = "Successfully logged out. Token has been reset" + login_success = "You have been logged in successfully.\nIf you exit the app and decide to login again, you can use > auth to do it." + del_p = "Are you sure? Account deletion is irreversible" + del_cancel = "Account deletion cancelled." + +class menu: + the_menu = """ +Account System Demonstration Frontend +Welcome! +Available Operations: +> signup\t(Sign up) +> login \t(Log in) +> auth \t(Authenticate) +> logout\t(Logout) +> del \t(Delete) +> exit \t(Exit) +""" + input_p = "What would you like to do?" + invalid_op = "Enter a valid operation." + exit_msg = "Goodbye!" + +class log: + class tags: info = '[INFO] ' warn = '[WARN] ' error = '[ERR] ' - client_start = "Client starting from path {0}...." \ No newline at end of file + debug = '[DEBUG] ' + + client_start = "Client starting from path {0}...." + handle_packet = "Handling packet {}..." + + +sig_map = { + 'MISSING_CREDS': "A field in your form is empty. Please fill it again properly", + 'INCORRECT_PASSWORD': 'The password you entered is incorrect. Check your password', + 'ACCOUNT_NOT_FOUND': 'The requested account does not exist. Check your username/email', + 'CONN_OK': "Connection to the server was successful", + 'CAPTCHA_WRONG': "The CAPTCHA code you entered was wrong. Try performing the action again", + 'SIGNUP_USERNAME_ABOVE_LIMIT': 'Username exceeds 32 character limit', + 'SIGNUP_USERNAME_ALREADY_EXISTS': 'Username already exists', + 'SIGNUP_EMAIL_ABOVE_LIMIT': 'Your email ID is abnormally long. Are you sure its a valid address?', + 'SIGNUP_NAME_ABOVE_LIMIT': 'Your name is abnormally long. Are you Jugemu?', + 'SIGNUP_DOB_INVALID': 'Date of birth is Invalid. Please enter it in YYYY-MM-DD or DD-MM-YYYY format.', + 'SIGNUP_PASSWORD_ABOVE_LIMIT': 'No way in heaven will you remember the password. Choose a smaller one.', + 'SIGNUP_ERR': 'Unexpected error while signing up, please try again.', + 'TOKEN_EXPIRED': 'Your session has expired. Login again', + 'TOKEN_INVALID': 'Login again to fix this.', + 'LOGOUT_ERR': 'Unexpected error while logging out, please try again.', + 'NOT_LOGGED_IN': 'You need to be logged in to do that!', + 'TOKEN_NOT_FOUND': 'The provided auth token does not exist on the server. Log in again' +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3cc0df4 Binary files /dev/null and b/requirements.txt differ