Skip to content

Troys account branch #3

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Binary file added 5005CMD_new/__pycache__/newApp.cpython-311.pyc
Binary file not shown.
Binary file modified 5005CMD_new/instance/komodo_hub.db
Binary file not shown.
122 changes: 114 additions & 8 deletions 5005CMD_new/newApp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,23 @@
from functools import wraps
import re # Import regex here for input validation
from datetime import datetime, timezone
from werkzeug.utils import secure_filename
import os
import traceback

# Initialise the 'Flask' app
app = Flask(__name__)




@app.after_request
def add_security_headers(response):
response.headers["Content-Security-Policy"] = "img-src 'self' blob: data:;"
return response



"""Lines to create and connect to database """

# Database configuration - reference to 'sqlitestudio' - basic for first sprint
Expand Down Expand Up @@ -61,6 +73,8 @@ class User(db.Model, UserMixin):
accountType = db.Column(db.String(20), nullable=False, default='student') # User account type: (student, teacher, public user)
school_id = db.Column(db.Integer, db.ForeignKey('schools.id'), nullable=True) # Link students to a school and nullable True as different account types possible
profileCustomisation = db.Column(db.Text, nullable=True) # Store avatars, themes, and other settings for requirment met
profileDescription = db.Column(db.Text, nullable=True)
avatarChoice = db.Column(db.Text, nullable=True)

def __repr__(self):
return f"<User {self.username}, Account Type: {self.accountType}>"
Expand All @@ -71,13 +85,9 @@ def __repr__(self):
def load_user(user_id):

return User.query.get(int(user_id))

print("Flask-Login success!.") # Print debug message to confirm flask-login initialisation(debug mode test)

def is_valid_username(username): # Input validation function so random input not allowed

"""Validate username: Only allows alphanumeric characters and underscores, 3-20 characters long.""" # One security measure so prevents easy brute-force attack
return re.match(r"^[a-zA-Z0-9_]{3,20}$", username) # Input validation

@app.route('/register', methods=['GET', 'POST']) # Secure Route - register a new user
def register():
Expand All @@ -102,7 +112,7 @@ def register():
return redirect(url_for('register'))
hashedPassword = generate_password_hash(password, method='pbkdf2:sha256') # Hash the password before storing it in the database
# Create new user entry in the database with associated account type
new_user = User(username = username, password = hashedPassword, accountType = accountType, school_id = school_id)
new_user = User(username = username, password = hashedPassword, accountType = accountType, school_id = school_id, profileDescription = None, avatarChoice=None)
db.session.add(new_user)
db.session.commit()
flash("Registration success! You can now log in.", "success")
Expand Down Expand Up @@ -130,6 +140,11 @@ def login():
return render_template('login.html')

# Secure route - user dashboard - could me modified to change for redirection

def is_valid_username(username): # Input validation function so random input not allowed

"""Validate username: Only allows alphanumeric characters and underscores, 3-20 characters long.""" # One security measure so prevents easy brute-force attack
return re.match(r"^[a-zA-Z0-9_]{3,20}$", username) # Input validation
@app.route('/dashboard')
@login_required
def dashboard():
Expand Down Expand Up @@ -252,14 +267,104 @@ def test():

return "<h1>Flask is running correctly!</h1>"


### Post Endpoint to allow users description to be saved to data base
# TROY KENNETH MUYOBO CODE STARTS HERE
@app.route('/update_description', methods=['POST'])
@login_required # Ensure user is logged in before updating profile
def update_description():
data = request.get_json() # Get JSON data from the frontend
new_description = data.get('description', '').strip() # Extract the description

if not new_description: # Ensure the description isn't empty
return jsonify({'error': 'Description cannot be empty'}), 400

current_user.profileDescription = new_description # Update the user's description
db.session.commit() # Save changes to the database

return jsonify({'message': 'Description updated successfully', 'description': new_description}), 200

@app.route('/set_avatar', methods=['POST'])
@login_required # Ensure user is logged in before updating profile
def set_avatar():
try:
print(request.data)
data = request.get_json() # Get JSON data from the frontend


if not data or 'avatar' not in data:
return jsonify({'error': 'Invalid request. Expected JSON with "avatar" key'}), 400

choice = data['avatar'].strip() # Extract the avatar choice
print("Received avatar choice:", choice) # Debugging output

if not choice:
return jsonify({'error': 'Avatar choice cannot be empty'}), 400

# Update the user's avatar
current_user.avatarChoice = choice
db.session.commit() # Save changes to the database
print("Database updated successfully.") # Debugging output

return jsonify({'success': 'Avatar updated successfully', 'Value': choice}),200

except Exception as e:
print(f"Error occurred: {e}") # Print actual error for debugging
db.session.rollback() # Rollback any failed transaction
return jsonify({'error': 'An error occurred', 'details': str(e)}), 500







@app.route('/get_avatar' , methods=['GET'])
@login_required
def get_avatar():
user = current_user
avatar = user.avatarChoice
return jsonify({
'AvatarChoice': avatar
})

@app.route('/get_profile_description', methods=['GET'])
@login_required
def get_profile_description():
# Get the logged-in user
user = current_user
# Fetch the profile description from the database (use the correct attribute name)
profile_description = user.profileDescription # Assuming the column is named 'ProfileDescription'
# Return the description as JSON
return jsonify({
'description': profile_description
})

@app.route('/User')
@login_required
@app.route('/User', methods=['POST'])
def profile():
return render_template('Profile.html' , user=current_user)
return render_template('Profile.html' , user=current_user,)


@app.route('/Conservation')
def Conservation():
return render_template('Conservation.html')

@app.route('/Change_Username', methods=['POST'])
@login_required
def Change_Username():
try:
data = request.get_json()
new_username = data['NewUsername']
print(new_username)
if not new_username:
return jsonify({'Error': 'Username cannot be empty'}), 400
current_user.username = new_username
db.session.commit()
return jsonify({'Success': 'Username changed successfully'}), 200
except Exception as e:
return jsonify({'Error': str(e)}), 500
# Prevent Clickjacking (Security Headers)
# TROY KENNETH MUYOBO CODE ENDS HERE.
@app.after_request
def add_security_headers(response):

Expand All @@ -269,6 +374,7 @@ def add_security_headers(response):
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'" # Restricts external content
return response


# Run Flask
if __name__ == '__main__':
with app.app_context():
Expand Down
15 changes: 15 additions & 0 deletions 5005CMD_new/static/Js/Conservation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// TROY KENNETH MUYOBO CODE
let slideIndex = 0;

function carousel() {
var i;
var x = document.getElementsByClassName("mySlides");
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
slideIndex++;
if (slideIndex > x.length) {slideIndex = 1}
x[slideIndex-1].style.display = "block";
setTimeout(carousel, 2000); // Change image every 2 seconds
}
carousel();
Loading