diff --git a/core/__pycache__/app.cpython-310.pyc b/core/__pycache__/app.cpython-310.pyc index 26ad86b..3936b63 100644 Binary files a/core/__pycache__/app.cpython-310.pyc and b/core/__pycache__/app.cpython-310.pyc differ diff --git a/core/app.py b/core/app.py index e0420f5..ddce27b 100644 --- a/core/app.py +++ b/core/app.py @@ -1,10 +1,17 @@ from flask import Flask, render_template, session, request, url_for, redirect, g +from flask_session import Session from werkzeug.security import generate_password_hash, check_password_hash from functools import wraps + from core.forms import SignupForm, LoginForm +from core.database import Database app = Flask(__name__, template_folder="../ui/templates/") app.config["SECRET_KEY"] = "" +app.config["SESSION_PERMANENT"] = False +app.config["SESSION_TYPE"] = "filesystem" +app.teardown_appcontext(Database.close_connection) +Session(app) @app.before_request def logged_in_user(): @@ -21,7 +28,7 @@ def login_required(view): return wrapped_view def admin_required(view): - """add at start of routes where users admin needs to be logged in to access""" + """add at start of routes where admins need to be logged in to access""" @wraps(view) def wrapped_view(*args, **kwargs): if g.admin != "admin": @@ -49,8 +56,33 @@ def signup(): password = form.password.data password2 = form.password2.data - # Store in database - return + # Store in database and hash to avoid exposing sensitive information + db = Database() + cursor = db.create_connection("../database/app.db") + + # Check if user already exists to avoid duplicates + dup_email = cursor.execute("""SELECT * FROM users + WHERE email = ?;""", (email,)).fetchone() + dup_username = cursor.execute("""SELECT * FROM users + WHERE username = ?;""", (username,)).fetchone() + + if dup_email is not None: + form.email.errors.append("Email already taken.") + elif dup_username is not None: + form.username.errors.append("Username already taken.") + elif password != password2: + form.password.errors.append("Passwords must match.") + else: + db.execute("""INSERT INTO users (username, password, email, num_followers, isPartenered, bio) + VALUES (?, ?, ?, ?, ?, ?);""", (username, generate_password_hash(password), email, 0, 0, "This user does not have a Bio.")) + db.commit() + return redirect(url_for("login")) + + + # Close connection to prevent data leaks + db.close_connection() + + return render_template("signup.html", form=form) @app.route("/login", methods=["GET", "POST"]) def login(): @@ -61,4 +93,38 @@ def login(): password = form.username.data # Compare with database - return + db = Database() + cursor = db.create_connection("../database/app.db") + + # Check if user exists so only users who have signed up can login + user_exists = cursor.execute("""SELECT * FROM users + WHERE username = ?;""", (username,)).fetchone() + db.close_connection() + + if not user_exists: + form.username.errors.append("Incorrect username or password.") + + # Check is hashed passwords match to verify the user logging in + elif not check_password_hash(user_exists["password"], password): + form.username.errors.append("Incorrect username or password.") + + else: + # Create a new session to prevent users from exploiting horizontal access control + session.clear() + session["username"] = username + + # Return to previous page if applicable + next_page = request.args.get("next") + + # Otherwise return home + if not next_page: + next_page = url_for("index") + return redirect(next_page) + + return render_template("login.html", form=form) + +@app.route("/logout") +@login_required +def logout(): + session.clear() + return redirect(url_for("index")) \ No newline at end of file diff --git a/core/database.py b/core/database.py new file mode 100644 index 0000000..d3ded0e --- /dev/null +++ b/core/database.py @@ -0,0 +1,15 @@ +import sqlite3 + +class Database: + def __init__(self, db:str) -> None: + self._db = db + self._conn = None + + def create_connection(self) -> sqlite3.Cursor: + conn = sqlite3.connect(self._db) + self._conn = conn + cursor = conn.cursor() + return cursor + + def close_connection(self) -> None: + self._conn.close() \ No newline at end of file diff --git a/database/app.db b/database/app.db new file mode 100644 index 0000000..e69de29 diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..96f9ae8 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,33 @@ +DROP TABLE IF EXISTS users; +CREATE TABLE users +( + username VARCHAR(50) PRIMARY KEY NOT NULL, + password VARCHAR(256) NOT NULL, + email VARCHAR(64) NOT NULL, + num_followers INTEGER NOT NULL, + isPartenered BOOLEAN NOT NULL DEFAULT 0 + bio TEXT, +); + +DROP TABLE IF EXISTS streams; +CREATE TABLE streams +( + stream_id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + start_time DATETIME NOT NULL, + num_viewers INT NOT NULL DEFAULT 0, + isLive BOOLEAN NOT NULL DEFAULT 0, + vod_id INT, + streamer_id VARCHAR NOT NULL, + FOREIGN KEY (streamer_id) REFERENCES users(username) ON DELETE CASCADE +); + +DROP TABLE IF EXISTS follows; +CREATE TABLE follows +( + user_id INT NOT NULL, + following_id INT NOT NULL, + PRIMARY KEY (user_id, following_id), + FOREIGN KEY (user_id) REFERENCES users(username) ON DELETE CASCADE, + FOREIGN KEY (following_id) REFERENCES users(username) ON DELETE CASCADE +); \ No newline at end of file