diff --git a/README.md b/README.md index fff0a3e..8be1186 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,19 @@ Depending on what you are making, it can be a good idea to include screenshots o
## Configuration -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +1. Clone and cd to repository +2. In /application, /cli, and /server create a copy of the template .ini file removing the leading template_ +3. Generate sesssion key with generate_seesion_key.py and add to /server/bank.ini +4. Add SMTP details if required. If SMTP is set to False OTP will not work, but an OTP code is still required. In this case, just enter any 6 digit number in. +5. Run docker command: docker-compose up -d + + +
### Email Configuration -In order for OTP to work, an SMTP server is needed +In order for OTP to work, an SMTP server is needed. ```ini [smtp] diff --git a/application/connection.py b/application/connection.py index 3133a76..821c5d3 100644 --- a/application/connection.py +++ b/application/connection.py @@ -43,7 +43,6 @@ def logout_client(): response = requests.post(CONFIG["server"]["url"] + "/Client/Logout", cookies=session_data['session_cookie']) return response except requests.exceptions.RequestException as e: - print(f"RequestException: {e}") response = Response() response.status_code = 500 response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}' @@ -58,7 +57,6 @@ def get_client(client_id): response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: - print(f"RequestException: {e}") return {'success': False, 'message': "Could not connect to the server. Please try again later."} def update_client(client_id, otp_code, email=None, phone_number=None, address=None): diff --git a/cli/cli.py b/cli/cli.py index 6d6e27d..6b4e0b4 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -1,14 +1,26 @@ # Lucas Mathews - Fontys Student ID: 5023572 # Banking System CLI Utility +import json import argparse import sys -from connection import login, logout +from config import CONFIG +from getpass import getpass +from connection import login, logout, get_client +from test_database_generator import generate_test_database + + +def show_menu(): + print("\nAvailable options:") + print("1. Logout") + print("2. New user") + print("3. Add test users") + print("4. Exit") def main(): parser = argparse.ArgumentParser(description='Banking System CLI Utility') - parser.add_argument('-u', '--username', type=str, help='Username for login') - parser.add_argument('-p', '--password', type=str, help='Password for login') + parser.add_argument('-u', '--username', type=str, default=CONFIG["client"]["default_id"], help='Username for login') + parser.add_argument('-p', '--password', type=str, default=CONFIG["client"]["default_password"], help='Password for login') subparsers = parser.add_subparsers(dest='command') @@ -17,7 +29,59 @@ def main(): args = parser.parse_args() - if args.command == 'login': + if not args.command: + while True: + if not args.username: + args.username = input("Enter username: ") + if not args.password: + args.password = getpass("Enter password: ") + + response = login(args.username, args.password) + print(response) + if response['success']: + print(f"Login successful: {response['message']}") + try: + with open('session_data.json', 'r') as f: + session_data = json.load(f) + client_id = session_data['client_id'] + client_info = get_client(client_id) + if client_info.get('success'): + client = client_info['data'] + name = client['name'] + print(f"Welcome, {name}!") + except Exception as e: + print(f"Error loading client data: {str(e)}") + + while True: + show_menu() + option = input("Choose an option: ") + if option == "1": + response = logout() + json_response = response.json() + if json_response['success']: + print(f"Logout successful: {json_response['message']}") + else: + print(f"Logout failed: {json_response['message']}") + args.username = None + args.password = None + elif option == "2": + print("New user option selected.") + # Implement new user functionality here + elif option == "3": + print("Add test users option selected.") + generate_test_database(args.username, args.password) + + elif option == "4": + print("Exiting...") + break + else: + print("Invalid option. Please try again.") + break + else: + print(f"Login failed: {response['message']}. Please try again.") + args.username = None + args.password = None + elif args.command == 'login': if not args.username or not args.password: print("Username and password are required for login.") sys.exit(1) @@ -25,13 +89,13 @@ def main(): if response['success']: print(f"Login successful: {response['message']}") else: - print(f"Login failed: {response['message']}") + print(f"Login failed: {response['message']}. Please try again.") elif args.command == 'logout': response = logout() if response['success']: print(f"Logout successful: {response['message']}") else: - print(f"Check Credentials: {response['message']}") + print(f"Logout failed: {response['message']}") else: print("Invalid command. Use 'login' or 'logout'.") diff --git a/cli/connection.py b/cli/connection.py index 78b7acf..ac35478 100644 --- a/cli/connection.py +++ b/cli/connection.py @@ -1,10 +1,11 @@ # Lucas Mathews - Fontys Student ID: 5023572 # Banking System CLI Utility Connection File -import requests -from config import CONFIG import hashlib import json +import requests +from requests.models import Response +from config import CONFIG ############## ### System ### @@ -14,7 +15,7 @@ def format_balance(balance): """Formats the balance as a currency string with comma separators.""" return f"€{balance:,.2f}" -def hash_password(password:str): +def hash_password(password: str): """Hashes a password using the SHA-512 algorithm and returns the hexadecimal representation of the hash.""" return hashlib.sha512(password.encode()).hexdigest() @@ -30,6 +31,12 @@ def login(client_id, password): response.raise_for_status() response_content = json.loads(response.content) # Parse the JSON response if response.status_code == 200 and response_content.get('success'): + session_data = { + 'session_cookie': response.cookies.get_dict(), + 'client_id': client_id + } + with open('session_data.json', 'w') as f: + json.dump(session_data, f) return {'success': True, 'message': response_content.get('message')} else: return {'success': False, 'message': response_content.get('message')} @@ -37,14 +44,50 @@ def login(client_id, password): return {'success': False, 'message': str(e)} def logout(): - url = f"{CONFIG['server']['url']}/logout" + """Logs out the current client by deleting the session data.""" try: - response = requests.get(url) - response.raise_for_status() - response_content = json.loads(response.content) # Parse the JSON response - if response.status_code == 200 and response_content.get('success'): - return {'success': True, 'message': response_content.get('message')} - else: - return {'success': False, 'message': response_content.get('message')} + with open('session_data.json', 'r') as f: + session_data = json.load(f) + response = requests.post(CONFIG["server"]["url"] + "/Client/Logout", cookies=session_data['session_cookie']) + return response except requests.exceptions.RequestException as e: - return {'success': False, 'message': str(e)} + response = Response() + response.status_code = 500 + response._content = b'{"success": false, "message": "Could not connect to the server. Please try again later."}' + return response + +def get_client(client_id): + """Retrieves the client details for the given client_id.""" + try: + with open('session_data.json', 'r') as f: + session_data = json.load(f) + response = requests.get(CONFIG["server"]["url"] + "/Client", cookies=session_data['session_cookie'], params={'client_id': client_id}) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print(f"RequestException: {e}") + return {'success': False, 'message': "Could not connect to the server. Please try again later."} + +def add_client(name, birthdate, address, phone_number, email, password, notes): + data = { + "name": name, + "birthdate": birthdate, + "address": address, + "phone_number": phone_number, + "email": email, + "password": password, + "notes": notes + } + try: + with open('session_data.json', 'r') as f: + session_data = json.load(f) + response = requests.get(CONFIG["server"]["url"] + "/Client", cookies=session_data['session_cookie'], params=data) + response.raise_for_status() + if response.status_code == 200: + print("Client retrieved successfully.") + else: + print(f"Failed to retrieve client. Status code: {response.status_code}, message: {response.text}") + except requests.exceptions.RequestException as e: + print(f"RequestException: {e}") + return {'success': False, 'message': "Could not connect to the server. Please try again later."} + \ No newline at end of file diff --git a/cli/template_cli.ini b/cli/template_cli.ini index 04ba943..02f3867 100644 --- a/cli/template_cli.ini +++ b/cli/template_cli.ini @@ -4,5 +4,5 @@ port = 8066 url = http://127.0.0.1:8066 [client] -default_id = -default_password = \ No newline at end of file +default_id = None +default_password = None \ No newline at end of file diff --git a/cli/test_database_generator.py b/cli/test_database_generator.py index 7fe2d8f..d115996 100644 --- a/cli/test_database_generator.py +++ b/cli/test_database_generator.py @@ -7,122 +7,93 @@ # creates a new SQLite database called test_database.db and writes the test data to the database. The client ID of the administrator account and the # password for the administrator account. -ADMIN_EMAIL = "lmath56@hotmail.com" # Email address of the administrator account - - from faker import Faker -import class_account -import class_client -import class_transaction -from connection import login, add_client, add_account, add_transaction, logout -import argparse +from connection import login, add_client, logout import random import datetime import hashlib import uuid +ADMIN_EMAIL = "lmath56@hotmail.com" # Email address of the administrator account + + def generate_hash(): # Creates a hash for a password seed = str(random.random()).encode('utf-8') return hashlib.sha512(seed).hexdigest() -def generate_uuid(): # Generates a unique identifier for transactions - return str(uuid.uuid4()) - -def generate_uuid_short(): # Generates a short uuid for accounts and clients - return str(uuid.uuid4())[:8] - def timestamp(): # Returns the current timestamp return (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) -fake = Faker() # Create a Faker instance +def generate_test_database(client_id, password): + fake = Faker() # Create a Faker instance -all_account_ids = [] # List to store all account IDs + all_account_ids = [] # List to store all account IDs -# Set up argument parsing -parser = argparse.ArgumentParser(description="Generate test database for the banking system.") -parser.add_argument('-u', '--username', required=True, help="Username for admin login") -parser.add_argument('-p', '--password', required=True, help="Password for admin login") + # Log in as the admin using provided username and password + client_hash = hashlib.sha512(password.encode()).hexdigest() + client = login(client_id, client_hash) -args = parser.parse_args() + if client is not None: # Check if login was successful + print("Admin logged in successfully") -# Log in as the admin using provided username and password -client_id = args.username -client_hash = hashlib.sha512(args.password.encode()).hexdigest() -client = login(client_id, client_hash) + for i in range(50): # Generate 50 clients + is_administrator = 1 if i == 0 else 0 # Set the first client as an administrator + # Set the password hash for the first account so that the password is "Happymeal1" + password = "Happymeal1" if i == 0 else generate_hash() + name = "ADMIN" if i == 0 else fake.name() + birthdate = "ADMIN" if i == 0 else fake.date_of_birth(minimum_age=18, maximum_age=90) + address = "ADMIN" if i == 0 else fake.address() + phone_number = "ADMIN" if i == 0 else fake.phone_number() + email = ADMIN_EMAIL if i == 0 else fake.email() + notes = fake.text(max_nb_chars=50) -if client is not None: # Check if login was successful - print("Admin logged in successfully") + # Add client using add_client function + response = add_client(name, birthdate, address, phone_number, email, password, notes) + print(response) # Print the response message - for i in range(50): # Generate 50 clients - is_administrator = 1 if i == 0 else 0 # Set the first client as an administrator - # Set the password hash for the first account so that the password is "Happymeal1" - password = "Happymeal1" if i == 0 else generate_hash() - client_id = generate_uuid_short() - client_name = "ADMIN" if i == 0 else fake.name() - birthdate = "ADMIN" if i == 0 else fake.date_of_birth(minimum_age=18, maximum_age=90) - opening_timestamp = timestamp() if i == 0 else fake.date_this_century() - address = "ADMIN" if i == 0 else fake.address() - phone_number = "ADMIN" if i == 0 else fake.phone_number() - email = ADMIN_EMAIL if i == 0 else fake.email() - notes = fake.text(max_nb_chars=50) + for j in range(2): # Each client has 2 accounts + balance = 1000 # Initialize balance to 1000 + account_type = random.choice(['Spending', 'Savings']) + account_notes = fake.text(max_nb_chars=50) - # Add client using add_client function - client_response = add_client( - name=client_name, - birthdate=birthdate, - address=address, - phone_number=phone_number, - email=email, - password=password, - notes=notes - ) - print(client_response[1]) # Print the response message - - for j in range(2): # Each client has 2 accounts - account_id = generate_uuid_short() - balance = 1000 # Initialize balance to 1000 - account_type = random.choice(['Spending', 'Savings']) - account_notes = fake.text(max_nb_chars=50) - - # Add account using add_account function - account_response = add_account( - client_id=client_id, - description=fake.text(max_nb_chars=200), - account_type=account_type, - notes=account_notes - ) - print(account_response[1]) # Print the response message - - for k in range(40): # Each account has 40 transactions - if not all_account_ids: # Skip creating a transaction if there are no accounts yet - continue - - transaction_type = random.choice(['Deposit', 'Withdrawal']) - amount = random.randint(1, 200) - - if transaction_type == 'Withdrawal' and balance - amount < 0: # Skip withdrawal if it would make balance negative - continue - - if transaction_type == 'Deposit': # Update balance based on transaction type - balance += amount - elif transaction_type == 'Withdrawal': - balance -= amount - - transaction_description = fake.text(max_nb_chars=50) - recipient_account_id = random.choice(all_account_ids) - - # Add transaction using add_transaction function - transaction_response = add_transaction( - amount=amount, - account_id=account_id, - recipient_account_id=recipient_account_id, - otp_code=123456, # Replace with actual OTP verification code - description=transaction_description + # Add account using add_account function + account_response = add_account( + client_id=client_id, + description=fake.text(max_nb_chars=200), + account_type=account_type, + notes=account_notes ) - print(transaction_response[1]) # Print the response message + print(account_response[1]) # Print the response message - all_account_ids.append(account_id) - logout() # Log out of the admin account - print(f"The client_id of the administrator account of this test database is: {all_account_ids[0]}. The password is: Happymeal1") -else: - print("Admin login failed.") \ No newline at end of file + for k in range(40): # Each account has 40 transactions + if not all_account_ids: # Skip creating a transaction if there are no accounts yet + continue + + transaction_type = random.choice(['Deposit', 'Withdrawal']) + amount = random.randint(1, 200) + + if transaction_type == 'Withdrawal' and balance - amount < 0: # Skip withdrawal if it would make balance negative + continue + + if transaction_type == 'Deposit': # Update balance based on transaction type + balance += amount + elif transaction_type == 'Withdrawal': + balance -= amount + + transaction_description = fake.text(max_nb_chars=50) + recipient_account_id = random.choice(all_account_ids) + + # Add transaction using add_transaction function + transaction_response = add_transaction( + amount=amount, + recipient_account_id=recipient_account_id, + otp_code=123456, # Replace with actual OTP verification code + description=transaction_description + ) + print(transaction_response[1]) # Print the response message + + all_account_ids.append(account_id) + logout() # Log out of the admin account + print(f"The client_id of the administrator account of this test database is: {all_account_ids[0]}. The password is: Happymeal1") + else: + print("Admin login failed.") \ No newline at end of file diff --git a/server/manager.py b/server/manager.py index 6791987..f6b767d 100644 --- a/server/manager.py +++ b/server/manager.py @@ -349,9 +349,10 @@ def add_transaction(amount: float, account_id: str, recipient_account_id: str, o current_client_id, is_admin = get_current_client() if not is_admin and account_id != current_client_id: return format_response(False, "You can only view your own client information."), 403 - otp_verified = verify_otp(current_client_id, otp_code) - if not otp_verified: - return format_response(False, "Invalid OTP."), 400 + if not is_admin: + otp_verified = verify_otp(current_client_id, otp_code) + if not otp_verified: + return format_response(False, "Invalid OTP."), 400 transaction_id = generate_uuid() account_from = session.query(Account).filter_by(account_id=account_id).one_or_none()