From d24d07a0d6f599406c295017c041708eac07fa7b Mon Sep 17 00:00:00 2001 From: Vianpyro Date: Tue, 1 Apr 2025 15:02:49 +0000 Subject: [PATCH 1/4] Refactor configuration and utility structure --- app.py | 2 +- .../test_email => config}/__init__.py | 0 db.py => config/database.py | 0 config.py => config/settings.py | 0 routes/authentication.py | 2 +- routes/picture.py | 2 +- routes/recipe.py | 2 +- .../test_email}/__init__.py | 0 .../{ => encryption}/test_email/conftest.py | 0 .../test_email/test_decrypt_email.py | 0 .../test_email/test_encrypt_email.py | 0 .../test_email/test_hash_email.py | 0 .../test_email/test_mask_email.py | 0 .../encryption/test_password/__init__.py | 0 .../test_password/conftest.py | 0 .../test_password/test_hash_password.py | 0 .../test_password/test_verify_password.py | 0 .../test_validate_email.py | 0 .../test_validate_password.py | 0 utility/__init__.py | 3 ++ utility/database.py | 29 ++++++++++ utility.py => utility/encryption.py | 54 +++---------------- utility/validation.py | 22 ++++++++ 23 files changed, 66 insertions(+), 50 deletions(-) rename {tests/test_utility/test_email => config}/__init__.py (100%) rename db.py => config/database.py (100%) rename config.py => config/settings.py (100%) rename tests/test_utility/{test_password => encryption/test_email}/__init__.py (100%) rename tests/test_utility/{ => encryption}/test_email/conftest.py (100%) rename tests/test_utility/{ => encryption}/test_email/test_decrypt_email.py (100%) rename tests/test_utility/{ => encryption}/test_email/test_encrypt_email.py (100%) rename tests/test_utility/{ => encryption}/test_email/test_hash_email.py (100%) rename tests/test_utility/{ => encryption}/test_email/test_mask_email.py (100%) create mode 100644 tests/test_utility/encryption/test_password/__init__.py rename tests/test_utility/{ => encryption}/test_password/conftest.py (100%) rename tests/test_utility/{ => encryption}/test_password/test_hash_password.py (100%) rename tests/test_utility/{ => encryption}/test_password/test_verify_password.py (100%) rename tests/test_utility/{test_email => validation}/test_validate_email.py (100%) rename tests/test_utility/{test_password => validation}/test_validate_password.py (100%) create mode 100644 utility/__init__.py create mode 100644 utility/database.py rename utility.py => utility/encryption.py (63%) create mode 100644 utility/validation.py diff --git a/app.py b/app.py index c70b575..89efeda 100644 --- a/app.py +++ b/app.py @@ -4,7 +4,7 @@ from flask import Flask, jsonify, request from flask_cors import CORS -from config import Config, limiter +from config.settings import Config, limiter from routes import register_routes from utility import extract_error_message diff --git a/tests/test_utility/test_email/__init__.py b/config/__init__.py similarity index 100% rename from tests/test_utility/test_email/__init__.py rename to config/__init__.py diff --git a/db.py b/config/database.py similarity index 100% rename from db.py rename to config/database.py diff --git a/config.py b/config/settings.py similarity index 100% rename from config.py rename to config/settings.py diff --git a/routes/authentication.py b/routes/authentication.py index 0c403bb..c7a13e7 100644 --- a/routes/authentication.py +++ b/routes/authentication.py @@ -2,7 +2,7 @@ from flask import Blueprint, jsonify, request from pymysql import MySQLError -from config import limiter +from config.settings import limiter from jwt_helper import ( TokenError, extract_token_from_header, diff --git a/routes/picture.py b/routes/picture.py index 16597f8..1909b66 100644 --- a/routes/picture.py +++ b/routes/picture.py @@ -2,7 +2,7 @@ from flask import Blueprint, jsonify, request, send_from_directory -from config import Config +from config.settings import Config from jwt_helper import token_required from utility import database_cursor diff --git a/routes/recipe.py b/routes/recipe.py index bf6901e..7e7ac67 100644 --- a/routes/recipe.py +++ b/routes/recipe.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify, request -from config import DEFAULT_PAGE_SIZE +from config.settings import DEFAULT_PAGE_SIZE from utility import database_cursor recipe_blueprint = Blueprint("recipe", __name__) diff --git a/tests/test_utility/test_password/__init__.py b/tests/test_utility/encryption/test_email/__init__.py similarity index 100% rename from tests/test_utility/test_password/__init__.py rename to tests/test_utility/encryption/test_email/__init__.py diff --git a/tests/test_utility/test_email/conftest.py b/tests/test_utility/encryption/test_email/conftest.py similarity index 100% rename from tests/test_utility/test_email/conftest.py rename to tests/test_utility/encryption/test_email/conftest.py diff --git a/tests/test_utility/test_email/test_decrypt_email.py b/tests/test_utility/encryption/test_email/test_decrypt_email.py similarity index 100% rename from tests/test_utility/test_email/test_decrypt_email.py rename to tests/test_utility/encryption/test_email/test_decrypt_email.py diff --git a/tests/test_utility/test_email/test_encrypt_email.py b/tests/test_utility/encryption/test_email/test_encrypt_email.py similarity index 100% rename from tests/test_utility/test_email/test_encrypt_email.py rename to tests/test_utility/encryption/test_email/test_encrypt_email.py diff --git a/tests/test_utility/test_email/test_hash_email.py b/tests/test_utility/encryption/test_email/test_hash_email.py similarity index 100% rename from tests/test_utility/test_email/test_hash_email.py rename to tests/test_utility/encryption/test_email/test_hash_email.py diff --git a/tests/test_utility/test_email/test_mask_email.py b/tests/test_utility/encryption/test_email/test_mask_email.py similarity index 100% rename from tests/test_utility/test_email/test_mask_email.py rename to tests/test_utility/encryption/test_email/test_mask_email.py diff --git a/tests/test_utility/encryption/test_password/__init__.py b/tests/test_utility/encryption/test_password/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_utility/test_password/conftest.py b/tests/test_utility/encryption/test_password/conftest.py similarity index 100% rename from tests/test_utility/test_password/conftest.py rename to tests/test_utility/encryption/test_password/conftest.py diff --git a/tests/test_utility/test_password/test_hash_password.py b/tests/test_utility/encryption/test_password/test_hash_password.py similarity index 100% rename from tests/test_utility/test_password/test_hash_password.py rename to tests/test_utility/encryption/test_password/test_hash_password.py diff --git a/tests/test_utility/test_password/test_verify_password.py b/tests/test_utility/encryption/test_password/test_verify_password.py similarity index 100% rename from tests/test_utility/test_password/test_verify_password.py rename to tests/test_utility/encryption/test_password/test_verify_password.py diff --git a/tests/test_utility/test_email/test_validate_email.py b/tests/test_utility/validation/test_validate_email.py similarity index 100% rename from tests/test_utility/test_email/test_validate_email.py rename to tests/test_utility/validation/test_validate_email.py diff --git a/tests/test_utility/test_password/test_validate_password.py b/tests/test_utility/validation/test_validate_password.py similarity index 100% rename from tests/test_utility/test_password/test_validate_password.py rename to tests/test_utility/validation/test_validate_password.py diff --git a/utility/__init__.py b/utility/__init__.py new file mode 100644 index 0000000..1a1a746 --- /dev/null +++ b/utility/__init__.py @@ -0,0 +1,3 @@ +from .database import * +from .encryption import * +from .validation import * diff --git a/utility/database.py b/utility/database.py new file mode 100644 index 0000000..3a7f701 --- /dev/null +++ b/utility/database.py @@ -0,0 +1,29 @@ +from contextlib import contextmanager + +from config.database import get_db_connection + +__all__ = ["database_cursor", "extract_error_message"] + + +@contextmanager +def database_cursor(): + db = get_db_connection() + cursor = db.cursor() + try: + yield cursor + db.commit() + except Exception as e: + db.rollback() + raise e + finally: + cursor.close() + db.close() + + +def extract_error_message(message): + """Extracts a user-friendly error message from a database error message.""" + try: + cleaner_message = message.split(", ")[1].strip("()'") + return cleaner_message if "SQL" not in cleaner_message else "Database error" + except IndexError: + return "An unknown error occurred" diff --git a/utility.py b/utility/encryption.py similarity index 63% rename from utility.py rename to utility/encryption.py index 9bfc3f0..6568fd8 100644 --- a/utility.py +++ b/utility/encryption.py @@ -2,15 +2,20 @@ import hashlib import os import re -from contextlib import contextmanager -from re import match from argon2 import PasswordHasher from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from dotenv import load_dotenv -from db import get_db_connection +__all__ = [ + "decrypt_email", + "encrypt_email", + "hash_email", + "hash_password", + "mask_email", + "verify_password", +] load_dotenv() ph = PasswordHasher() @@ -18,21 +23,6 @@ PEPPER = os.getenv("PEPPER", "SuperSecretPepper").encode("utf-8") -@contextmanager -def database_cursor(): - db = get_db_connection() - cursor = db.cursor() - try: - yield cursor - db.commit() - except Exception as e: - db.rollback() - raise e - finally: - cursor.close() - db.close() - - def decrypt_email(encrypted_email: str) -> str: """Decrypts an AES-256 encrypted email.""" encrypted_data = base64.b64decode(encrypted_email) @@ -59,15 +49,6 @@ def encrypt_email(email: str) -> str: return base64.b64encode(iv + ciphertext).decode() -def extract_error_message(message): - """Extracts a user-friendly error message from a database error message.""" - try: - cleaner_message = message.split(", ")[1].strip("()'") - return cleaner_message if "SQL" not in cleaner_message else "Database error" - except IndexError: - return "An unknown error occurred" - - def hash_email(email: str) -> str: """Generate a SHA-256 hash of the email (used for fast lookup).""" return hashlib.sha256(email.encode()).hexdigest() @@ -97,25 +78,6 @@ def mask_part(part: str) -> str: return masked_local + "@" + masked_domain -def validate_email(email: str) -> bool: - """Validates an email address using a regex pattern.""" - pattern = r"^[a-zA-Z0-9._+-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$" - return bool(re.match(pattern, email)) - - -def validate_password(password) -> bool: - """ - Validates a password based on the following criteria: - - At least 12 characters long. - - Contains at least one uppercase letter (A-Z). - - Contains at least one lowercase letter (a-z). - - Contains at least one digit (0-9). - - Contains at least one special character (any non-alphanumeric character). - """ - pattern = r"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$" - return bool(match(pattern, password)) - - def verify_password(password, stored_password): peppered_password = password.encode("utf-8") + PEPPER return ph.verify(stored_password, peppered_password) diff --git a/utility/validation.py b/utility/validation.py new file mode 100644 index 0000000..e7b3ad1 --- /dev/null +++ b/utility/validation.py @@ -0,0 +1,22 @@ +from re import match + +__all__ = ["validate_email", "validate_password"] + + +def validate_email(email: str) -> bool: + """Validates an email address using a regex pattern.""" + pattern = r"^[a-zA-Z0-9._+-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$" + return bool(match(pattern, email)) + + +def validate_password(password) -> bool: + """ + Validates a password based on the following criteria: + - At least 12 characters long. + - Contains at least one uppercase letter (A-Z). + - Contains at least one lowercase letter (a-z). + - Contains at least one digit (0-9). + - Contains at least one special character (any non-alphanumeric character). + """ + pattern = r"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$" + return bool(match(pattern, password)) From 2f442082b9e18500716aabf7c4254f37d9c8b86c Mon Sep 17 00:00:00 2001 From: Vianpyro Date: Thu, 3 Apr 2025 14:30:33 +0000 Subject: [PATCH 2/4] Disable validation for the entire codebase in super-linter configuration --- .github/workflows/super-linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index bb44250..877dc78 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -29,7 +29,7 @@ jobs: uses: super-linter/super-linter@v7 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_ALL_CODEBASE: true + VALIDATE_ALL_CODEBASE: false VALIDATE_JSON_PRETTIER: false VALIDATE_PYTHON_ISORT: false VALIDATE_PYTHON_PYLINT: false From 8e488ac6ef7a80b71051ea5da3b81e3b2ca23161 Mon Sep 17 00:00:00 2001 From: Vianpyro Date: Thu, 3 Apr 2025 14:46:58 +0000 Subject: [PATCH 3/4] Refactor utility imports to improve module organization --- app.py | 2 +- routes/authentication.py | 12 +++--------- utility/__init__.py | 3 --- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/app.py b/app.py index 89efeda..b1a811b 100644 --- a/app.py +++ b/app.py @@ -6,7 +6,7 @@ from config.settings import Config, limiter from routes import register_routes -from utility import extract_error_message +from utility.database import extract_error_message app = Flask(__name__) app.config.from_object(Config) diff --git a/routes/authentication.py b/routes/authentication.py index c7a13e7..a0cf8b8 100644 --- a/routes/authentication.py +++ b/routes/authentication.py @@ -10,15 +10,9 @@ generate_refresh_token, verify_token, ) -from utility import ( - database_cursor, - encrypt_email, - hash_email, - hash_password, - validate_email, - validate_password, - verify_password, -) +from utility.database import database_cursor +from utility.encryption import encrypt_email, hash_email, hash_password, verify_password +from utility.validation import validate_email, validate_password authentication_blueprint = Blueprint("authentication", __name__) diff --git a/utility/__init__.py b/utility/__init__.py index 1a1a746..e69de29 100644 --- a/utility/__init__.py +++ b/utility/__init__.py @@ -1,3 +0,0 @@ -from .database import * -from .encryption import * -from .validation import * From a62d3b3be689127a46df41770d5644fb7f05dfb1 Mon Sep 17 00:00:00 2001 From: Vianpyro Date: Thu, 3 Apr 2025 14:57:39 +0000 Subject: [PATCH 4/4] Refactor import statement for database cursor to improve module clarity --- routes/comment.py | 2 +- routes/ingredient.py | 2 +- routes/language.py | 2 +- routes/person.py | 6 +++--- routes/picture.py | 2 +- routes/recipe.py | 2 +- routes/recipe_engagement.py | 2 +- .../encryption/test_email/test_decrypt_email.py | 2 +- .../encryption/test_email/test_encrypt_email.py | 2 +- tests/test_utility/encryption/test_email/test_hash_email.py | 2 +- tests/test_utility/encryption/test_email/test_mask_email.py | 2 +- .../encryption/test_password/test_hash_password.py | 2 +- .../encryption/test_password/test_verify_password.py | 2 +- tests/test_utility/validation/test_validate_email.py | 2 +- tests/test_utility/validation/test_validate_password.py | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/routes/comment.py b/routes/comment.py index aa66e4a..53ca753 100644 --- a/routes/comment.py +++ b/routes/comment.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify -from utility import database_cursor +from utility.database import database_cursor comment_blueprint = Blueprint("comment", __name__) diff --git a/routes/ingredient.py b/routes/ingredient.py index 2e05747..28c199a 100644 --- a/routes/ingredient.py +++ b/routes/ingredient.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify -from utility import database_cursor +from utility.database import database_cursor ingredient_blueprint = Blueprint("ingredient", __name__) diff --git a/routes/language.py b/routes/language.py index ac71b38..3ff83da 100644 --- a/routes/language.py +++ b/routes/language.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify -from utility import database_cursor +from utility.database import database_cursor language_blueprint = Blueprint("language", __name__) diff --git a/routes/person.py b/routes/person.py index 9b2f658..b9dc065 100644 --- a/routes/person.py +++ b/routes/person.py @@ -1,16 +1,16 @@ from argon2 import exceptions from flask import Blueprint, jsonify, request -from utility import ( - database_cursor, +from utility.database import database_cursor +from utility.encryption import ( decrypt_email, encrypt_email, hash_email, hash_password, mask_email, - validate_password, verify_password, ) +from utility.validation import validate_password person_blueprint = Blueprint("person", __name__) diff --git a/routes/picture.py b/routes/picture.py index 1909b66..84b2191 100644 --- a/routes/picture.py +++ b/routes/picture.py @@ -4,7 +4,7 @@ from config.settings import Config from jwt_helper import token_required -from utility import database_cursor +from utility.database import database_cursor picture_blueprint = Blueprint("picture", __name__) diff --git a/routes/recipe.py b/routes/recipe.py index 7e7ac67..3ac73d4 100644 --- a/routes/recipe.py +++ b/routes/recipe.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, request from config.settings import DEFAULT_PAGE_SIZE -from utility import database_cursor +from utility.database import database_cursor recipe_blueprint = Blueprint("recipe", __name__) diff --git a/routes/recipe_engagement.py b/routes/recipe_engagement.py index 2ee9ec0..a5bbffc 100644 --- a/routes/recipe_engagement.py +++ b/routes/recipe_engagement.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify, request -from utility import database_cursor +from utility.database import database_cursor recipe_engagement_blueprint = Blueprint("recipe_engagement", __name__) diff --git a/tests/test_utility/encryption/test_email/test_decrypt_email.py b/tests/test_utility/encryption/test_email/test_decrypt_email.py index 7c150ef..be48881 100644 --- a/tests/test_utility/encryption/test_email/test_decrypt_email.py +++ b/tests/test_utility/encryption/test_email/test_decrypt_email.py @@ -1,4 +1,4 @@ -from utility import decrypt_email, encrypt_email +from utility.encryption import decrypt_email, encrypt_email def test_decrypt_email_type(sample_email): diff --git a/tests/test_utility/encryption/test_email/test_encrypt_email.py b/tests/test_utility/encryption/test_email/test_encrypt_email.py index 60709e7..b22fad3 100644 --- a/tests/test_utility/encryption/test_email/test_encrypt_email.py +++ b/tests/test_utility/encryption/test_email/test_encrypt_email.py @@ -1,4 +1,4 @@ -from utility import encrypt_email +from utility.encryption import encrypt_email def test_encrypt_email(sample_email): diff --git a/tests/test_utility/encryption/test_email/test_hash_email.py b/tests/test_utility/encryption/test_email/test_hash_email.py index 5803673..c3c8628 100644 --- a/tests/test_utility/encryption/test_email/test_hash_email.py +++ b/tests/test_utility/encryption/test_email/test_hash_email.py @@ -1,4 +1,4 @@ -from utility import hash_email +from utility.encryption import hash_email def test_hash_email_type(sample_email): diff --git a/tests/test_utility/encryption/test_email/test_mask_email.py b/tests/test_utility/encryption/test_email/test_mask_email.py index 67b9f18..6740b07 100644 --- a/tests/test_utility/encryption/test_email/test_mask_email.py +++ b/tests/test_utility/encryption/test_email/test_mask_email.py @@ -1,6 +1,6 @@ import pytest -from utility import mask_email +from utility.encryption import mask_email @pytest.mark.parametrize( diff --git a/tests/test_utility/encryption/test_password/test_hash_password.py b/tests/test_utility/encryption/test_password/test_hash_password.py index b624330..629a54c 100644 --- a/tests/test_utility/encryption/test_password/test_hash_password.py +++ b/tests/test_utility/encryption/test_password/test_hash_password.py @@ -1,6 +1,6 @@ from argon2 import PasswordHasher -from utility import hash_password +from utility.encryption import hash_password ph = PasswordHasher() diff --git a/tests/test_utility/encryption/test_password/test_verify_password.py b/tests/test_utility/encryption/test_password/test_verify_password.py index dbfa3a9..37ff0d4 100644 --- a/tests/test_utility/encryption/test_password/test_verify_password.py +++ b/tests/test_utility/encryption/test_password/test_verify_password.py @@ -2,7 +2,7 @@ from argon2 import PasswordHasher from argon2.exceptions import VerificationError, VerifyMismatchError -from utility import hash_password, verify_password +from utility.encryption import hash_password, verify_password ph = PasswordHasher() diff --git a/tests/test_utility/validation/test_validate_email.py b/tests/test_utility/validation/test_validate_email.py index 3420025..8cd19a0 100644 --- a/tests/test_utility/validation/test_validate_email.py +++ b/tests/test_utility/validation/test_validate_email.py @@ -1,4 +1,4 @@ -from utility import validate_email +from utility.validation import validate_email def test_valid_emails(): diff --git a/tests/test_utility/validation/test_validate_password.py b/tests/test_utility/validation/test_validate_password.py index 077f2bf..6625cf9 100644 --- a/tests/test_utility/validation/test_validate_password.py +++ b/tests/test_utility/validation/test_validate_password.py @@ -1,6 +1,6 @@ from argon2 import PasswordHasher -from utility import validate_password +from utility.validation import validate_password ph = PasswordHasher()