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()