diff --git a/Project5/__pycache__/passwords.cpython-312.pyc b/Project5/__pycache__/passwords.cpython-312.pyc new file mode 100644 index 0000000..f8228a6 Binary files /dev/null and b/Project5/__pycache__/passwords.cpython-312.pyc differ diff --git a/Project5/__pycache__/quotes.cpython-312.pyc b/Project5/__pycache__/quotes.cpython-312.pyc new file mode 100644 index 0000000..37494fd Binary files /dev/null and b/Project5/__pycache__/quotes.cpython-312.pyc differ diff --git a/Project5/app.log b/Project5/app.log new file mode 100644 index 0000000..cf9a007 --- /dev/null +++ b/Project5/app.log @@ -0,0 +1,32 @@ +2024-05-02 16:01:11,837 - INFO - register button hit +2024-05-02 16:02:24,317 - INFO - register button hit +2024-05-02 16:02:24,319 - INFO - All entries in the user collection: +2024-05-02 16:02:24,321 - INFO - {'_id': ObjectId('6633f0693184392a155335c9'), 'user': 'dsantoli', 'hashed_password': '66099026ced26ba9ad776abfa3f767f85157ccc88530cc467e673beb4ad12f3a', 'salt': '3344c8ECD22eAA839E476C70adb4e37E'} +2024-05-02 16:06:43,509 - INFO - register button hit +2024-05-02 16:16:56,974 - INFO - register button hit +2024-05-02 16:17:24,147 - INFO - register button hit +2024-05-02 16:17:55,106 - INFO - register button hit +2024-05-02 16:18:26,996 - INFO - register button hit +2024-05-02 16:20:21,970 - INFO - register button hit +2024-05-02 16:21:09,875 - INFO - register button hit +2024-05-02 17:14:21,362 - INFO - register button hit +2024-05-02 17:14:34,841 - INFO - register button hit +2024-05-02 17:14:34,843 - INFO - All entries in the user collection: +2024-05-02 17:14:34,843 - INFO - {'_id': ObjectId('6633f0693184392a155335c9'), 'user': 'dsantoli', 'hashed_password': '66099026ced26ba9ad776abfa3f767f85157ccc88530cc467e673beb4ad12f3a', 'salt': '3344c8ECD22eAA839E476C70adb4e37E'} +2024-05-02 17:14:34,844 - INFO - {'user': 'asantoli', 'hashed_password': 'b2ededd1a1a6e7e2e75d5d9a3af2e61b11076dc4a8e44a869259411590a03bad', 'salt': 'd63CfC1b4bC7f2a4d8BFDE639f3Ea7b1', '_id': ObjectId('6634023a7d63c9c79edabc5d')} +2024-05-02 17:22:30,353 - INFO - register button hit +2024-05-02 17:23:05,674 - INFO - register button hit +2024-05-02 17:23:54,252 - INFO - register button hit +2024-05-02 17:26:36,390 - INFO - register button hit +2024-05-07 12:06:45,656 - INFO - register button hit +2024-05-07 12:08:28,710 - INFO - register button hit +2024-05-07 12:08:28,720 - INFO - All entries in the user collection: +2024-05-07 12:08:28,720 - INFO - {'_id': ObjectId('6633f0693184392a155335c9'), 'user': 'dsantoli', 'hashed_password': '66099026ced26ba9ad776abfa3f767f85157ccc88530cc467e673beb4ad12f3a', 'salt': '3344c8ECD22eAA839E476C70adb4e37E'} +2024-05-07 12:08:28,720 - INFO - {'_id': ObjectId('6634023a7d63c9c79edabc5d'), 'user': 'asantoli', 'hashed_password': 'b2ededd1a1a6e7e2e75d5d9a3af2e61b11076dc4a8e44a869259411590a03bad', 'salt': 'd63CfC1b4bC7f2a4d8BFDE639f3Ea7b1'} +2024-05-07 12:08:28,721 - INFO - {'user': 'testuser', 'hashed_password': 'fecb8d3503747efd044485f92e8236f723cc51dce18b882c1b4f656a16649772', 'salt': 'feFAA9CAEaB7354De20FBBbad988c43f', '_id': ObjectId('663a51fc1c70d692547a9fc1')} +2024-05-07 12:27:17,465 - INFO - lgoin button hit +2024-05-07 12:30:54,752 - INFO - lgoin button hit +2024-05-07 12:33:05,895 - INFO - register button hit +2024-05-07 12:33:57,336 - INFO - lgoin button hit +2024-05-07 12:34:04,163 - INFO - lgoin button hit +2024-05-07 12:40:38,553 - INFO - lgoin button hit diff --git a/Project5/create-quotes-db.py b/Project5/create-quotes-db.py new file mode 100644 index 0000000..5c6f7be --- /dev/null +++ b/Project5/create-quotes-db.py @@ -0,0 +1,26 @@ +# Create a Mongita database with movie information +import json +from mongita import MongitaClientDisk + +quotes_data = [ + {"text": "I'm hungry. When's lunch?", "author": "Dorothy","owner":"Greg"}, + {"text": "You threw that ball. You go get it.", "author": "Suzy", "owner":"Dorothy"}, +] + +# create a mongita client connection +client = MongitaClientDisk() + +# create a movie database +quotes_db = client.quotes_db + +# create a quotes collection +quotes_collection = quotes_db.quotes_collection + +# empty the collection +quotes_collection.delete_many({}) + +# put the quotes in the database +quotes_collection.insert_many(quotes_data) + +# make sure the quotes are there +print(quotes_collection.count_documents({})) diff --git a/Project5/passwords.py b/Project5/passwords.py new file mode 100644 index 0000000..e01de19 --- /dev/null +++ b/Project5/passwords.py @@ -0,0 +1,30 @@ +import hashlib +import random +import string + +def hash_password(password): + salt = "".join(random.choices(string.hexdigits, k=32)) + salted_password = (password+salt).encode("utf-8") + hash_object = hashlib.sha256(salted_password) + hashed_password = hash_object.hexdigest() + return hashed_password, salt + + +def check_password(password, saved_hashed_password, salt): + salted_password = (password + salt).encode("utf-8") + hash_object = hashlib.sha256(salted_password) + hashed_password = hash_object.hexdigest() + return hashed_password == saved_hashed_password + + +def test_hash_and_check_password(): + hashed_password, salt = hash_password("hohoho") + assert type(hashed_password) is str + assert type(salt) is str + assert check_password("hoho",hashed_password,salt) == False + assert check_password("hohoho",hashed_password,salt) == True + + +if __name__ == "__main__": + test_hash_and_check_password() + print("done.") diff --git a/Project5/quotes.py b/Project5/quotes.py new file mode 100644 index 0000000..215fb85 --- /dev/null +++ b/Project5/quotes.py @@ -0,0 +1,223 @@ +from flask import Flask, render_template, request, make_response, redirect, flash, get_flashed_messages +from mongita import MongitaClientDisk +from bson import ObjectId +from passwords import hash_password,check_password + +import logging +from logging.handlers import RotatingFileHandler + + +app = Flask(__name__) + +app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' + +# Configure logging +logging.basicConfig(level=logging.INFO) +handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=1) +handler.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +handler.setFormatter(formatter) +app.logger.addHandler(handler) + +# create a mongita client connection +client = MongitaClientDisk() + +# open the quotes database +quotes_db = client.quotes_db +session_db = client.session_db +user_db = client.user_db + +import uuid + +user_collection = user_db.user_collection +session_collection = session_db.session_collection + +@app.route("/", methods=["GET"]) +@app.route("/quotes", methods=["GET"]) +def get_quotes(): + session_id = request.cookies.get("session_id", None) + if not session_id: + response = redirect("/login") + return response + # open the session collection + session_collection = session_db.session_collection + # get the data for this session + session_data = list(session_collection.find({"session_id": session_id})) + if len(session_data) == 0: + response = redirect("/logout") + return response + assert len(session_data) == 1 + session_data = session_data[0] + # get some information from the session + user = session_data.get("user", "unknown user") + # open the quotes collection + quotes_collection = quotes_db.quotes_collection + # load the data + data = list(quotes_collection.find({"owner":user})) + for item in data: + item["_id"] = str(item["_id"]) + item["object"] = ObjectId(item["_id"]) + # display the data + html = render_template( + "quotes.html", + data=data, + user=user, + ) + response = make_response(html) + response.set_cookie("session_id", session_id) + return response + +# ADDED ROUTE FOR LOGIN +@app.route("/login", methods=["GET", "POST"]) +def login(): + if request.method == "POST": + app.logger.info("lgoin button hit") + username = request.form["username"] + password = request.form["password"] + # Check if the username exists in the database + user_data = user_collection.find_one({"user": username}) + if user_data: + # Validate the password + if check_password(password, user_data["hashed_password"], user_data["salt"]): + # Password is correct, create a new session + session_id = str(uuid.uuid4()) + session_data = {"session_id": session_id, "user": username} + session_collection.insert_one(session_data) + response = redirect("/quotes") + response.set_cookie("session_id", session_id) + return response + # Invalid username or password + flash("Invalid username or password", "error") + return redirect("/login") + return render_template("login.html") + + +@app.route("/logout", methods=["GET"]) +def get_logout(): + # get the session id + session_id = request.cookies.get("session_id", None) + if session_id: + # open the session collection + session_collection = session_db.session_collection + # delete the session + session_collection.delete_one({"session_id": session_id}) + response = redirect("/login") + response.delete_cookie("session_id") + return response + + +@app.route("/add", methods=["GET"]) +def get_add(): + session_id = request.cookies.get("session_id", None) + if not session_id: + response = redirect("/login") + return response + return render_template("add_quote.html") + + +@app.route("/add", methods=["POST"]) +def post_add(): + session_id = request.cookies.get("session_id", None) + if not session_id: + response = redirect("/login") + return response + # open the session collection + session_collection = session_db.session_collection + # get the data for this session + session_data = list(session_collection.find({"session_id": session_id})) + if len(session_data) == 0: + response = redirect("/logout") + return response + assert len(session_data) == 1 + session_data = session_data[0] + # get some information from the session + user = session_data.get("user", "unknown user") + text = request.form.get("text", "") + author = request.form.get("author", "") + if text != "" and author != "": + # open the quotes collection + quotes_collection = quotes_db.quotes_collection + # insert the quote + quote_data = {"owner": user, "text": text, "author": author} + quotes_collection.insert_one(quote_data) + # usually do a redirect('....') + return redirect("/quotes") + + +@app.route("/edit/", methods=["GET"]) +def get_edit(id=None): + session_id = request.cookies.get("session_id", None) + if not session_id: + response = redirect("/login") + return response + if id: + # open the quotes collection + quotes_collection = quotes_db.quotes_collection + # get the item + data = quotes_collection.find_one({"_id": ObjectId(id)}) + data["id"] = str(data["_id"]) + return render_template("edit_quote.html", data=data) + # return to the quotes page + return redirect("/quotes") + + +@app.route("/edit", methods=["POST"]) +def post_edit(): + session_id = request.cookies.get("session_id", None) + if not session_id: + response = redirect("/login") + return response + _id = request.form.get("_id", None) + text = request.form.get("text", "") + author = request.form.get("author", "") + if _id: + # open the quotes collection + quotes_collection = quotes_db.quotes_collection + # update the values in this particular record + values = {"$set": {"text": text, "author": author}} + data = quotes_collection.update_one({"_id": ObjectId(_id)}, values) + # do a redirect('....') + return redirect("/quotes") + + +@app.route("/delete", methods=["GET"]) + +@app.route("/delete/", methods=["GET"]) +def get_delete(id=None): + session_id = request.cookies.get("session_id", None) + if not session_id: + response = redirect("/login") + return response + if id: + # open the quotes collection + quotes_collection = quotes_db.quotes_collection + # delete the item + quotes_collection.delete_one({"_id": ObjectId(id)}) + # return to the quotes page + return redirect("/quotes") + +# ADDED ROUTE FOR REGISTER +@app.route("/register", methods=["GET", "POST"]) +def register(): + if request.method == "POST": + app.logger.info("register button hit") + username = request.form["username"] + password = request.form["password"] + # Check if the username is already taken + if user_collection.find_one({"user": username}): + flash("Username already taken. Please choose a different username.", "error") + return redirect("/register") + # Hash the password before storing it + hashed_password, salt = hash_password(password) + # Store the username, hashed password, and salt in the user collection + user_data = {"user": username, "hashed_password": hashed_password, "salt": salt} + user_collection.insert_one(user_data) + flash("Registration successful. Please log in.", "success") + + # Log all entries in the user collection to the log file + app.logger.info("All entries in the user collection:") + for entry in user_collection.find(): + app.logger.info(entry) + + return redirect("/login") + return render_template("register.html") \ No newline at end of file diff --git a/Project5/start.sh b/Project5/start.sh new file mode 100644 index 0000000..7627930 --- /dev/null +++ b/Project5/start.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Set environment variables +export FLASK_APP=quotes.py +export FLASK_ENV=development + +# Start the Flask development server +python -m flask run diff --git a/Project5/static/css/styles.css b/Project5/static/css/styles.css new file mode 100644 index 0000000..c69a21d --- /dev/null +++ b/Project5/static/css/styles.css @@ -0,0 +1,111 @@ +/* styles.css */ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + margin: 0; + padding: 0; +} + +.container { + max-width: 400px; + margin: 50px auto; + padding: 20px; + background-color: #fff; + border-radius: 5px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +h1 { + text-align: center; + margin-bottom: 20px; + color: #333; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: #555; +} + +input[type="text"], +input[type="password"], +button { + width: 100%; + padding: 10px; + margin-bottom: 10px; + border: 1px solid #ccc; + border-radius: 3px; +} + +button { + background-color: #007bff; + color: #fff; + border: none; + cursor: pointer; +} + +button:hover { + background-color: #0056b3; +} + +a { + display: block; + text-align: center; + margin-top: 20px; + color: #007bff; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.login-links, .register-link { + text-align: center; + margin-top: 20px; +} + +.login-links, .register-link p { + display: inline; + margin-right: 10px; +} + +.login-links, .register-link a { + color: #007bff; + text-decoration: none; +} + +.login-links, .register-link a:hover { + text-decoration: underline; +} + + +/* stying for flash messages */ +.flash-message { + padding: 10px; + margin-bottom: 10px; + border-radius: 5px; + color: white; + font-weight: bold; + width: 100%; + text-align: center; + transition: opacity 0.5s ease-in-out; +} + +.flash-error { + background-color: #dc3545; +} + +.flash-success { + background-color: #28a745; +} + +.fade-out { + animation: fadeOut 10s ease-in-out forwards; +} + +@keyframes fadeOut { + 0% { opacity: 1; } + 100% { opacity: 0; } +} \ No newline at end of file diff --git a/Project5/templates/add_quote.html b/Project5/templates/add_quote.html new file mode 100644 index 0000000..939ec5c --- /dev/null +++ b/Project5/templates/add_quote.html @@ -0,0 +1,54 @@ + + + + + + + Add a Quote + + + + +
+

Add a Quote

+
+
+ + +
+
+ + +
+ + Cancel +
+
+ + \ No newline at end of file diff --git a/Project5/templates/edit_quote.html b/Project5/templates/edit_quote.html new file mode 100644 index 0000000..2607101 --- /dev/null +++ b/Project5/templates/edit_quote.html @@ -0,0 +1,55 @@ + + + + + + + Edit Quote + + + + +
+

Edit this Quote

+
+ +
+ + +
+
+ + +
+ + Cancel +
+
+ + \ No newline at end of file diff --git a/Project5/templates/login.html b/Project5/templates/login.html new file mode 100644 index 0000000..c4d4880 --- /dev/null +++ b/Project5/templates/login.html @@ -0,0 +1,48 @@ + + + + + + + + Login + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
+
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+ + {% endif %} + {% endwith %} +
+

Login

+
+ + + + + +
+ +
+ + + \ No newline at end of file diff --git a/Project5/templates/quotes.html b/Project5/templates/quotes.html new file mode 100644 index 0000000..cc34634 --- /dev/null +++ b/Project5/templates/quotes.html @@ -0,0 +1,73 @@ + + + + + + + Quotes + + + + + +
+

Quotes

+ + + + + + + + + + + {% for item in data %} + + + + + + + {% endfor %} + +
OwnerQuoteAuthorActions
{{ item["owner"] }}{{ item["text"] }}{{ item["author"] }} + edit + delete +
+ +
+

User: {{ user }} (Logout)

+
+ + \ No newline at end of file diff --git a/Project5/templates/register.html b/Project5/templates/register.html new file mode 100644 index 0000000..8bf058b --- /dev/null +++ b/Project5/templates/register.html @@ -0,0 +1,44 @@ + + + + + + + Register + + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ + {% endif %} + {% endwith %} +
+

Register

+
+ + + + + +
+ +
+ + \ No newline at end of file