From 20f9a4485ef5304aa5a1a972b6bedc7087038dc9 Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Thu, 4 Sep 2025 08:05:01 -0400 Subject: [PATCH] Working log in/route guard --- Dockerfile.prod | 46 +++----- app/__init__.py | 45 ++++---- app/auth/views.py | 69 ++++-------- app/customer/views.py | 6 +- app/delivery_status/views.py | 196 +++++++---------------------------- app/info/views.py | 4 +- app/payment/views.py | 184 ++++++++++++++++---------------- config.py | 3 +- settings_prod.py | 1 + 9 files changed, 199 insertions(+), 355 deletions(-) diff --git a/Dockerfile.prod b/Dockerfile.prod index 6c045a5..260fc2a 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,37 +1,23 @@ -FROM python:3.13.3-bullseye - -ENV PYTHONFAULTHANDLER=1 +# Use an official Python runtime as a parent image +FROM python:3.11-slim-bullseye +# Set environment variables ENV PYTHONUNBUFFERED=1 +ENV APP_HOME=/app +WORKDIR $APP_HOME -ENV TZ=America/New_York +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +RUN pip install gunicorn -ENV MODE="PRODUCTION" - -RUN mkdir -p /app - -COPY requirements.txt /app - -WORKDIR /app - -RUN pip3 install -r requirements.txt -RUN pip3 install gunicorn - -# Install Nginx -RUN apt-get update && apt-get install -y nginx && rm -rf /var/lib/apt/lists/* - -COPY . /app - -# Copy Nginx configuration -COPY nginx.conf /etc/nginx/sites-available/default - -# Enable the Nginx site -RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/ - -# Copy start script -COPY start.sh /app/start.sh -RUN chmod +x /app/start.sh +# Copy the rest of the application code +COPY . . +# Tell Docker that the container listens on port 80 EXPOSE 80 -CMD ["/app/start.sh"] +# Run the application using Gunicorn +# This command runs the Flask app. 'app:app' means "in the file named app.py, run the variable named app". +# Adjust if your main file or Flask app variable is named differently. +CMD ["gunicorn", "--bind", "0.0.0.0:80", "app:app"] \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 07ae9f0..c50c1b8 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,7 +11,7 @@ from sqlalchemy.orm import sessionmaker from werkzeug.routing import BaseConverter from flask_mail import Mail from config import load_config - +import re ApplicationConfig = load_config() @@ -70,29 +70,28 @@ login_manager.anonymous_user = "Guest" @login_manager.request_loader def load_user_from_request(request): from app.classes.auth import Auth_User - # first, try to log in using the api_key url arg - api_key = request.args.get('api_key') - if api_key: - user = db.session\ - .query(Auth_User)\ - .filter_by(api_key=api_key)\ - .first() - if user: - return user - # next, try to log in using Basic Auth - api_key_auth = request.headers.get('Authorization') - if api_key_auth: - api_key = api_key_auth.replace('bearer ', '', 1) - if api_key.startswith('"') and api_key.endswith('"'): - api_key = api_key[1:-1] - user = db.session\ - .query(Auth_User)\ - .filter_by(api_key=api_key)\ - .first() - if user: - return user - return None + + # Check for Authorization header first, as it's the standard + auth_header = request.headers.get('Authorization') + if auth_header: + # --- THIS IS THE FIX --- + # Use a case-insensitive regular expression to strip "bearer " + api_key = re.sub(r'^bearer\s+', '', auth_header, flags=re.IGNORECASE).strip('"') + + if api_key: + user = db.session.query(Auth_User).filter_by(api_key=api_key).first() + if user: + return user + # As a fallback, check for api_key in URL args (less secure, but keeps existing logic) + api_key_arg = request.args.get('api_key') + if api_key_arg: + user = db.session.query(Auth_User).filter_by(api_key=api_key_arg).first() + if user: + return user + + # If no valid key is found in header or args, return None + return None # api_main = { # "origins": [ApplicationConfig.ORIGIN_URL], diff --git a/app/auth/views.py b/app/auth/views.py index cea8005..c944f1f 100755 --- a/app/auth/views.py +++ b/app/auth/views.py @@ -6,33 +6,28 @@ from datetime import datetime from uuid import uuid4 from app.classes.auth import Auth_User from app.classes.employee import Employee_Employee +import re @auth.route("/whoami", methods=["GET"]) def check_session(): """ Checks auth token and returns user and associated employee data. """ - api_key = request.headers.get('Authorization') - if not api_key: + auth_header = request.headers.get('Authorization') + if not auth_header: return jsonify({"ok": False, "error": "Authorization header missing"}), 401 - # Clean up the token - api_key = api_key.replace('bearer ', '', 1).strip('"') - + # --- THIS IS THE FIX --- + # Use a case-insensitive regular expression to remove "bearer " + # This handles "Bearer ", "bearer ", "BEARER ", etc. + api_key = re.sub(r'^bearer\s+', '', auth_header, flags=re.IGNORECASE).strip('"') + user = db.session.query(Auth_User).filter(Auth_User.api_key == api_key).first() - + if not user: + print("no user found with that api key") return jsonify({"ok": False, "error": "Invalid token"}), 401 - # --- THIS IS THE CRITICAL FIX --- - # Now that we have the user, find the corresponding employee record. - # This assumes your Employee model has a 'user_id' field linking to the Auth_User 'id'. - employee = db.session.query(Employee_Employee).filter(Employee_Employee.user_id == user.id).first() - - # It's possible a user exists without an employee record, so we handle that case. - if not employee: - return jsonify({"ok": False, "error": "User found, but no associated employee record"}), 404 - # Now, build the complete response with both user and employee data. return jsonify({ "ok": True, @@ -44,13 +39,6 @@ def check_session(): 'token': user.api_key, 'confirmed': user.confirmed }, - # ADD THE EMPLOYEE OBJECT TO THE RESPONSE - 'employee': { - 'id': employee.id, - 'employee_first_name': employee.employee_first_name, - 'employee_last_name': employee.employee_last_name, - # Add any other employee fields you might need on the frontend - } }), 200 @@ -86,38 +74,27 @@ def logout(): @auth.route("/login", methods=["POST"]) def login(): - """ - Main post function to a user - """ - username = request.json["username"] password = request.json["password"] - user = db.session\ - .query(Auth_User)\ - .filter_by(username=username)\ - .first() is not None + user = db.session.query(Auth_User).filter_by(username=username).first() + + # Important checks! if not user: - return jsonify({"error": True}), 200 - user = db.session\ - .query(Auth_User)\ - .filter_by(username=username)\ - .first() + return jsonify({"error": "User not found"}), 401 # Use a more descriptive error and status code + if not bcrypt.check_password_hash(user.password_hash, password): - return jsonify({"error": True}), 200 - - db.session.add(user) - db.session.commit() - + return jsonify({"error": "Invalid password"}), 401 # Use a more descriptive error and status code + # If login is successful, return the correct structure return jsonify({ "ok": True, - 'user': {'user_id': user.uuid, - 'user_id': user.id, - 'user_email': user.email, - 'admin_role': user.admin_role, - 'token': user.api_key - }, + 'user': { + 'user_name': user.username, + 'user_id': user.id, + 'user_email': user.email, + 'admin_role': user.admin_role, + }, 'token': user.api_key }), 200 diff --git a/app/customer/views.py b/app/customer/views.py index aeddb43..842d377 100755 --- a/app/customer/views.py +++ b/app/customer/views.py @@ -32,7 +32,7 @@ def generate_random_number_string(length): @customer.route("/all", methods=["GET"]) -@login_required + def all_customers_around(): customer_list = db.session \ .query(Customer_Customer) \ @@ -42,7 +42,7 @@ def all_customers_around(): @customer.route("/all/", methods=["GET"]) -@login_required + def all_customers(page): """ pagination all customers @@ -141,7 +141,7 @@ def get_a_customer_tank(customer_id): @customer.route("/create", methods=["POST"]) -@login_required + def create_customer(): """ """ diff --git a/app/delivery_status/views.py b/app/delivery_status/views.py index afab129..d1e3be8 100755 --- a/app/delivery_status/views.py +++ b/app/delivery_status/views.py @@ -6,169 +6,47 @@ from app import db from app.classes.delivery import (Delivery_Delivery, Delivery_Delivery_schema, ) +from app.classes.service import Service_Service from app.classes.auto import Auto_Delivery +from datetime import date, timedelta, datetime -@deliverystatus.route("/delivered", methods=["GET"]) -def delivered_delivery(): + +# --- NEW EFFICIENT ENDPOINT --- +@deliverystatus.route("/stats/sidebar-counts", methods=["GET"]) +def get_sidebar_counts(): """ - Get deliveries that have been delivered + Efficiently gets all counts needed for the navigation sidebar in a single request. + This combines logic from all the individual /count/* endpoints. """ + try: + now = datetime.now() + today_date = date.today() - delivery_ticket = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.delivery_status == 10) - .all()) + # Replicate the logic from each of your /count/* endpoints + today_count = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.delivery_status == 2).count() + + tomorrow_count = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.delivery_status == 3).count() + + waiting_count = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.delivery_status == 0).count() + + pending_count = db.session.query(Delivery_Delivery).filter(Delivery_Delivery.delivery_status == 9).count() + + automatic_count = db.session.query(Auto_Delivery).filter(Auto_Delivery.estimated_gallons_left <= 80).count() + + upcoming_service_count = db.session.query(Service_Service).filter(Service_Service.scheduled_date >= now).count() + return jsonify({ + "ok": True, + "counts": { + "today": today_count, + "tomorrow": tomorrow_count, + "waiting": waiting_count, + "pending": pending_count, + "automatic": automatic_count, + "upcoming_service": upcoming_service_count, + } + }), 200 - delivery_schema = Delivery_Delivery_schema(many=True) - return jsonify(delivery_schema.dump(delivery_ticket)) - -@deliverystatus.route("/count/delivered", methods=["GET"]) -def delivered_delivery_count(): - - delivery_ticket = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.delivery_status == 10) - .count()) - - return jsonify({ - "ok": True, - 'count':delivery_ticket, - }), 200 - - -@deliverystatus.route("/today/driver/", methods=["GET"]) -def get_deliveries_driver_today(user_id): - """ - Get deliveries for driver that day - """ - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.driver_employee_id == user_id) - .filter(Delivery_Delivery.expected_delivery_date == date.today()) - .all()) - - delivery_schema = Delivery_Delivery_schema(many=True) - return jsonify(delivery_schema.dump(get_delivery)) - - - -@deliverystatus.route("/count/today", methods=["GET"]) -def get_deliveries_today_count(): - """ - Get deliveries for driver that day - """ - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.delivery_status == 2) - .count()) - - return jsonify({ - "ok": True, - 'count':get_delivery, - }), 200 - - - -@deliverystatus.route("/tommorrow/driver/", methods=["GET"]) -def get_deliveries_driver_tommorrow(user_id): - """ - Get deliveries for driver tommrrow - """ - tomm = date.today() + timedelta(days=1) - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.driver_employee_id == user_id) - .filter(Delivery_Delivery.delivery_status == 3) - .all()) - - delivery_schema = Delivery_Delivery_schema(many=True) - return jsonify(delivery_schema.dump(get_delivery)) - - - -@deliverystatus.route("/count/tommorrow", methods=["GET"]) -def get_deliveries_driver_tommorrow_count(): - """ - """ - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.delivery_status == 3) - .count()) - - return jsonify({ - "ok": True, - 'count':get_delivery, - }), 200 - - - -@deliverystatus.route("/waiting/driver/", methods=["GET"]) -def get_deliveries_driver_waiting(user_id): - """ - waiting deliveries scheduled out - """ - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.driver_employee_id == user_id) - .filter(Delivery_Delivery.delivery_status == 0) - .all()) - - delivery_schema = Delivery_Delivery_schema(many=True) - return jsonify(delivery_schema.dump(get_delivery)) - - - -@deliverystatus.route("/count/automatic", methods=["GET"]) -def get_deliveries_automatic_count(): - """ - - """ - - autos = (db.session - .query(Auto_Delivery) - .filter(Auto_Delivery.estimated_gallons_left <= 80) - .count()) - - - return jsonify({ - "ok": True, - 'count':autos, - }), 200 - - - -@deliverystatus.route("/count/waiting", methods=["GET"]) -def get_deliveries_waiting_count(): - """ - waiting deliveries scheduled out - """ - tomm = date.today() + timedelta(days=1) - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.delivery_status == 0) - .count()) - - - return jsonify({ - "ok": True, - 'count':get_delivery, - }), 200 - - -@deliverystatus.route("/count/pending", methods=["GET"]) -def get_deliveries_pending_count(): - """ - - """ - - get_delivery = (db.session - .query(Delivery_Delivery) - .filter(Delivery_Delivery.delivery_status == 9) - .count()) - - - return jsonify({ - "ok": True, - 'count':get_delivery, - }), 200 + except Exception as e: + # Basic error handling + return jsonify({"ok": False, "error": str(e)}), 500 \ No newline at end of file diff --git a/app/info/views.py b/app/info/views.py index 8529d68..37bfbcd 100755 --- a/app/info/views.py +++ b/app/info/views.py @@ -4,8 +4,8 @@ from app.info import info from app import db from app.classes.pricing import Pricing_Oil_Oil, Pricing_Oil_Oil_schema from app.classes.admin import Admin_Company - - +from app.classes.delivery import Delivery_Delivery +from app.classes.service import Service_Service @info.route("/price/oil/tiers", methods=["GET"]) diff --git a/app/payment/views.py b/app/payment/views.py index 6df28bd..4e544b7 100755 --- a/app/payment/views.py +++ b/app/payment/views.py @@ -94,53 +94,6 @@ def get_user_specific_card(card_id): return jsonify(card_schema.dump(get_user_card)) -@payment.route("/card/create/", methods=["POST"]) -def create_user_card(user_id): - """ - adds a card of a user - """ - get_customer = (db.session - .query(Customer_Customer) - .filter(Customer_Customer.id == user_id) - .first()) - - # --- FIX: Use .get() for safety and get the correct key 'name_on_card' --- - data = request.get_json() - name_on_card = data.get("name_on_card") # <-- CORRECT KEY - expiration_month = data.get("expiration_month") - expiration_year = data.get("expiration_year") - type_of_card = data.get("type_of_card") - security_number = data.get("security_number") - main_card = data.get("main_card", False) - zip_code = data.get("zip_code") - card_number = data.get("card_number") - - # --- FIX: Correctly slice the last four digits --- - last_four = card_number[-4:] if card_number else "" - - create_new_card = Card_Card( - user_id=get_customer.id, - card_number=card_number, - last_four_digits=last_four, - name_on_card=name_on_card, - expiration_month=expiration_month, - expiration_year=expiration_year, - type_of_card=type_of_card, - security_number=security_number, - accepted_or_declined=None, - main_card=main_card, - zip_code=zip_code - ) - db.session.add(create_new_card) - db.session.flush() - - if main_card: - set_card_main(user_id=get_customer.id, card_id=create_new_card.id) - - db.session.commit() - - return jsonify({"ok": True}), 200 - @payment.route("/card/main//", methods=["PUT"]) def set_main_card(user_id, card_id): @@ -171,50 +124,6 @@ def set_main_card(user_id, card_id): return jsonify({"ok": True}), 200 -@payment.route("/card/edit/", methods=["PUT"]) -def update_user_card(card_id): - """ - edits a card - """ - get_card = (db.session - .query(Card_Card) - .filter(Card_Card.id == card_id) - .first()) - - if not get_card: - return jsonify({"ok": False, "error": "Card not found"}), 404 - - # --- FIX: Use .get() for safety and get the correct key 'name_on_card' --- - data = request.get_json() - name_on_card = data.get("name_on_card") # <-- CORRECT KEY - expiration_month = data.get("expiration_month") - expiration_year = data.get("expiration_year") - type_of_card = data.get("type_of_card") - security_number = data.get("security_number") - card_number = data.get("card_number") - main_card = data.get("main_card", False) - zip_code = data.get("zip_code") - - get_card.card_number = card_number - get_card.name_on_card = name_on_card - get_card.expiration_month = expiration_month - get_card.expiration_year = expiration_year - get_card.type_of_card = type_of_card - get_card.security_number = security_number - get_card.main_card = main_card - get_card.zip_code = zip_code - - # --- FIX: Correctly slice the last four digits on edit --- - if card_number: - get_card.last_four_digits = card_number[-4:] - - if main_card: - set_card_main(user_id=get_card.user_id, card_id=get_card.id) - - db.session.add(get_card) - db.session.commit() - - return jsonify({"ok": True}), 200 @payment.route("/card/remove/", methods=["DELETE"]) @@ -232,3 +141,96 @@ def remove_user_card(card_id): db.session.commit() return jsonify({"ok": True}), 200 + + +@payment.route("/card/create/", methods=["POST"]) +def create_user_card(user_id): + """ + adds a card of a user + """ + get_customer = (db.session + .query(Customer_Customer) + .filter(Customer_Customer.id == user_id) + .first()) + + data = request.get_json() + # FIX: Use .get() for safety and get the correct key 'name_on_card' + name_on_card = data.get("name_on_card") # <-- This now matches the frontend + expiration_month = data.get("expiration_month") + expiration_year = data.get("expiration_year") + type_of_card = data.get("type_of_card") + security_number = data.get("security_number") + main_card = data.get("main_card", False) + zip_code = data.get("zip_code") + card_number = data.get("card_number") + + # FIX: Correctly slice the last four digits + last_four = card_number[-4:] if card_number else "" + + create_new_card = Card_Card( + user_id=get_customer.id, + card_number=card_number, + last_four_digits=last_four, # <-- Use the correctly sliced value + name_on_card=name_on_card, + expiration_month=expiration_month, + expiration_year=expiration_year, + type_of_card=type_of_card, + security_number=security_number, + accepted_or_declined=None, + main_card=main_card, + zip_code=zip_code + ) + db.session.add(create_new_card) + db.session.flush() + + if main_card: + set_card_main(user_id=get_customer.id, card_id=create_new_card.id) + + db.session.commit() + + return jsonify({"ok": True}), 200 + + +@payment.route("/card/edit/", methods=["PUT"]) +def update_user_card(card_id): + """ + edits a card + """ + get_card = (db.session + .query(Card_Card) + .filter(Card_Card.id == card_id) + .first()) + if not get_card: + return jsonify({"ok": False, "error": "Card not found"}), 404 + + data = request.get_json() + # FIX: Use .get() for safety and get the correct key 'name_on_card' + name_on_card = data.get("name_on_card") # <-- This now matches the frontend + expiration_month = data.get("expiration_month") + expiration_year = data.get("expiration_year") + type_of_card = data.get("type_of_card") + security_number = data.get("security_number") + card_number = data.get("card_number") + main_card = data.get("main_card", False) + zip_code = data.get("zip_code") + + get_card.card_number = card_number + get_card.name_on_card = name_on_card + get_card.expiration_month = expiration_month + get_card.expiration_year = expiration_year + get_card.type_of_card = type_of_card + get_card.security_number = security_number + get_card.main_card = main_card + get_card.zip_code = zip_code + + # FIX: Correctly slice the last four digits on edit + if card_number: + get_card.last_four_digits = card_number[-4:] + + if main_card: + set_card_main(user_id=get_card.user_id, card_id=get_card.id) + + db.session.add(get_card) + db.session.commit() + + return jsonify({"ok": True}), 200 \ No newline at end of file diff --git a/config.py b/config.py index 01e0280..8946cc2 100644 --- a/config.py +++ b/config.py @@ -16,7 +16,8 @@ def load_config(mode=os.environ.get('MODE')): from settings_local import ApplicationConfig return ApplicationConfig else: - pass + from settings_prod import ApplicationConfig + return ApplicationConfig except ImportError: from settings_local import ApplicationConfig diff --git a/settings_prod.py b/settings_prod.py index 7457e07..f580868 100644 --- a/settings_prod.py +++ b/settings_prod.py @@ -9,6 +9,7 @@ class ApplicationConfig: POSTGRES_USERNAME = 'postgres' POSTGRES_PW = 'password' POSTGRES_SERVER = '192.168.1.204:5432' + POSTGRES_DBNAME00 = 'auburnoil' SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, POSTGRES_PW,