From b8e915e6297472a52e955dc15855e85d5bb759cd Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Wed, 11 Feb 2026 23:35:38 +0000 Subject: [PATCH 1/6] Fix:CORS errors,JWT authentication --- .vscode/settings.json | 4 +++- backend/main.py | 41 +++++++++++++++++++++++++++-------------- front-end/lib/api.mjs | 7 +++++-- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 166be53..ff90cbd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { "liveServer.settings.root": "front-end/", - "python.defaultInterpreterPath": "backend/.venv/bin/python" + "python.defaultInterpreterPath": "backend/.venv/bin/python", + "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.pythonProjects": [] } diff --git a/backend/main.py b/backend/main.py index 7ba155f..8d6a6b7 100644 --- a/backend/main.py +++ b/backend/main.py @@ -17,7 +17,7 @@ ) from dotenv import load_dotenv -from flask import Flask +from flask import Flask, jsonify from flask_cors import CORS from flask_jwt_extended import JWTManager @@ -32,34 +32,47 @@ def main(): # Configure CORS to handle preflight requests CORS( app, + resources={r"/*": {"origins": "*"}}, supports_credentials=True, - resources={ - r"/*": { - "origins": "*", - "allow_headers": ["Content-Type", "Authorization"], - "methods": ["GET", "POST", "OPTIONS"], - } - }, + allow_headers=["Content-Type", "Authorization"], + methods=["GET", "POST", "OPTIONS"] ) app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"] jwt = JWTManager(app) jwt.user_lookup_loader(lookup_user) + # JWT error handlers + @app.errorhandler(422) + def handle_unprocessable_entity(e): + return jsonify({"success": False, "message": "Invalid or missing authentication token"}), 401 + + @jwt.expired_token_loader + def expired_token_callback(jwt_header, jwt_payload): + return jsonify({"success": False, "message": "Token has expired"}), 401 + + @jwt.invalid_token_loader + def invalid_token_callback(error): + return jsonify({"success": False, "message": "Invalid token"}), 401 + + @jwt.unauthorized_loader + def missing_authorization_callback(error): + return jsonify({"success": False, "message": "Missing authorization token"}), 401 + app.add_url_rule("/register", methods=["POST"], view_func=register) app.add_url_rule("/login", methods=["POST"], view_func=login) - app.add_url_rule("/home", view_func=home_timeline) + app.add_url_rule("/home", methods=["GET"], view_func=home_timeline) - app.add_url_rule("/profile", view_func=self_profile) - app.add_url_rule("/profile/", view_func=other_profile) + app.add_url_rule("/profile", methods=["GET"], view_func=self_profile) + app.add_url_rule("/profile/", methods=["GET"], view_func=other_profile) app.add_url_rule("/follow", methods=["POST"], view_func=do_follow) - app.add_url_rule("/suggested-follows/", view_func=suggested_follows) + app.add_url_rule("/suggested-follows/", methods=["GET"], view_func=suggested_follows) app.add_url_rule("/bloom", methods=["POST"], view_func=send_bloom) app.add_url_rule("/bloom/", methods=["GET"], view_func=get_bloom) - app.add_url_rule("/blooms/", view_func=user_blooms) - app.add_url_rule("/hashtag/", view_func=hashtag) + app.add_url_rule("/blooms/", methods=["GET"], view_func=user_blooms) + app.add_url_rule("/hashtag/", methods=["GET"], view_func=hashtag) app.run(host="0.0.0.0", port="3000", debug=True) diff --git a/front-end/lib/api.mjs b/front-end/lib/api.mjs index f4b5339..0ce37cf 100644 --- a/front-end/lib/api.mjs +++ b/front-end/lib/api.mjs @@ -44,10 +44,13 @@ async function _apiRequest(endpoint, options = {}) { if (!endpoint.includes("/login") && !endpoint.includes("/register")) { state.destroyState(); } + if (!endpoint.includes("/profile")) { + handleErrorDialog(error); + } + } else { + handleErrorDialog(error); } - // Pass all errors forward to a dialog on the screen - handleErrorDialog(error); throw error; } From f75cd068f510d6bbf9bf339ba7a17ff2b5e2cc63 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Thu, 12 Feb 2026 12:59:32 +0000 Subject: [PATCH 2/6] Fix:hashtag regex and drop a table them first before recreating --- db/schema.sql | 6 ++++++ front-end/components/bloom.mjs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/db/schema.sql b/db/schema.sql index 61e7580..8ac2037 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -1,3 +1,9 @@ + +DROP TABLE IF EXISTS hashtags; +DROP TABLE IF EXISTS follows; +DROP TABLE IF EXISTS blooms; +DROP TABLE IF EXISTS users; + CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR NOT NULL, diff --git a/front-end/components/bloom.mjs b/front-end/components/bloom.mjs index 0b4166c..38ffe75 100644 --- a/front-end/components/bloom.mjs +++ b/front-end/components/bloom.mjs @@ -37,7 +37,7 @@ const createBloom = (template, bloom) => { function _formatHashtags(text) { if (!text) return text; return text.replace( - /\B#[^#]+/g, + /#\w+/g, (match) => `${match}` ); } From 1e37dd29e244ef695024bf0273b1eee9acf0eb7e Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Thu, 12 Feb 2026 14:19:41 +0000 Subject: [PATCH 3/6] Fix:Make hashtagView() async and await the API call --- front-end/views/hashtag.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 7b7e996..d5dec39 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -14,10 +14,13 @@ import {createHeading} from "../components/heading.mjs"; // Hashtag view: show all tweets containing this tag -function hashtagView(hashtag) { +async function hashtagView(hashtag) { destroy(); - apiService.getBloomsByHashtag(hashtag); + const normalizedHashtag = hashtag.startsWith("#") ? hashtag : `#${hashtag}`; + if (state.currentHashtag !== normalizedHashtag) { + await apiService.getBloomsByHashtag(hashtag); + } renderOne( state.isLoggedIn, From ab4820949a7cf384e4ada6b374a9fae698a85e49 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Wed, 18 Feb 2026 09:57:29 +0000 Subject: [PATCH 4/6] Fix:The compatibility of Python 3.9, scrypt doesn't exist in hashlib --- backend/data/users.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/data/users.py b/backend/data/users.py index 00746f1..3ae57a3 100644 --- a/backend/data/users.py +++ b/backend/data/users.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from hashlib import scrypt import hashlib import random import string From 7889c4d15df9059b04841f3df4a467b00ed0bf3c Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Wed, 18 Feb 2026 10:07:29 +0000 Subject: [PATCH 5/6] Fix:update the file to fetch data before clearing the page, eliminating the blank flash when clicking hashtags --- front-end/views/hashtag.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index d5dec39..fd49e6a 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -15,13 +15,13 @@ import {createHeading} from "../components/heading.mjs"; // Hashtag view: show all tweets containing this tag async function hashtagView(hashtag) { - destroy(); - const normalizedHashtag = hashtag.startsWith("#") ? hashtag : `#${hashtag}`; if (state.currentHashtag !== normalizedHashtag) { await apiService.getBloomsByHashtag(hashtag); } + destroy(); + renderOne( state.isLoggedIn, getLogoutContainer(), From a303447564fd8ebaf7448eec4fbafa324301c2e8 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Wed, 18 Feb 2026 10:31:20 +0000 Subject: [PATCH 6/6] Fix: Use pbkdf2_hmac instead of scrypt for Python 3.9 compatibility --- backend/data/users.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/data/users.py b/backend/data/users.py index 3ae57a3..b655b7e 100644 --- a/backend/data/users.py +++ b/backend/data/users.py @@ -90,7 +90,8 @@ def register_user(username: str, password_plaintext: str) -> User: def scrypt(password_plaintext: bytes, password_salt: bytes) -> bytes: - return hashlib.scrypt(password_plaintext, salt=password_salt, n=8, r=8, p=1) + # Using pbkdf2_hmac for Python 3.9 compatibility instead of scrypt + return hashlib.pbkdf2_hmac('sha256', password_plaintext, password_salt, 100000) SALT_CHARACTERS = string.ascii_uppercase + string.ascii_lowercase + string.digits