diff --git a/app.py b/app.py index 9b04f62..c880b8f 100644 --- a/app.py +++ b/app.py @@ -5,7 +5,7 @@ from flask_login import LoginManager, login_required, current_user import jinja_partials from jinja2_fragments import render_block from decorators import (validate_person, validate_topset, validate_workout, - require_ownership, get_auth_message, get_person_id_from_context) + require_ownership, get_auth_message, get_person_id_from_context, admin_required) from routes.auth import auth, get_person_by_id from routes.changelog import changelog_bp from routes.calendar import calendar_bp # Import the new calendar blueprint @@ -165,8 +165,8 @@ def create_person(): @ app.route("/person//delete", methods=['DELETE']) @login_required +@admin_required @validate_person -@require_ownership def delete_person(person_id): db.delete_person(person_id) return "", 200, {"HX-Trigger": "updatedPeople"} @@ -198,6 +198,7 @@ def get_person_name(person_id): @ app.route("/exercise", methods=['POST']) +@login_required def create_exercise(): name = request.form.get("name") attribute_ids = request.form.getlist('attribute_ids') @@ -218,6 +219,7 @@ def get_exercise(exercise_id): @ app.route("/exercise//edit_form", methods=['GET']) +@login_required def get_exercise_edit_form(exercise_id): exercise = db.get_exercise(exercise_id) all_attributes = db.exercises.get_attributes_by_category() @@ -243,6 +245,7 @@ def get_exercise_edit_form(exercise_id): @ app.route("/exercise//update", methods=['PUT']) +@login_required def update_exercise(exercise_id): new_name = request.form.get('name') attribute_ids = request.form.getlist('attribute_ids') @@ -262,9 +265,6 @@ def delete_exercise(exercise_id): @ app.route("/settings") @ login_required def settings(): - # check if user is admin - if current_user.id != 1007: - return redirect(url_for('dashboard')) people = db.get_people() exercises = db.get_all_exercises() all_attributes = db.exercises.get_attributes_by_category() @@ -329,6 +329,7 @@ def get_exercises(): return render_template('partials/exercise/exercise_dropdown.html', exercises=exercises, person_id=person_id) @app.route("/exercise//edit_name", methods=['GET', 'POST']) +@login_required def edit_exercise_name(exercise_id): exercise = db.exercises.get_exercise(exercise_id) person_id = request.args.get('person_id', type=int) @@ -340,6 +341,7 @@ def edit_exercise_name(exercise_id): return render_template('partials/exercise/exercise_list_item.html', exercise=updated_exercise, person_id=person_id) @app.route("/exercises/add", methods=['POST']) +@login_required def add_exercise(): exercise_name = request.form['query'] new_exercise = db.exercises.add_exercise(exercise_name) @@ -347,6 +349,8 @@ def add_exercise(): return render_template('partials/exercise/exercise_list_item.html', exercise=new_exercise, person_id=person_id) @ app.route("/exercise//delete", methods=['DELETE']) +@login_required +@admin_required def delete_exercise(exercise_id): db.exercises.delete_exercise(exercise_id) return "" diff --git a/decorators.py b/decorators.py index 978db37..5e35eba 100644 --- a/decorators.py +++ b/decorators.py @@ -97,11 +97,30 @@ ACTION_MAP = { 'tags.delete_tag': 'delete this tag', 'tags.add_tag_to_workout': 'add a tag to this workout', 'tags.create_new_tag_for_workout': 'create a new tag for this workout', - 'programs.create_program': 'create a workout program', + 'workout.create_program': 'create a workout program', 'programs.delete_program': 'delete this workout program', + 'delete_exercise': 'delete an exercise', + 'delete_person': 'delete a user', } +def admin_required(func): + @wraps(func) + def wrapper(*args, **kwargs): + if not current_user.is_authenticated or not getattr(current_user, 'is_admin', False): + from flask import flash + msg = "You must be an admin to perform this action." + if request.endpoint in ACTION_MAP: + msg = f"You must be an admin to {ACTION_MAP[request.endpoint]}." + + flash(msg, "warning") + if request.headers.get('HX-Request'): + return '', 200, {'HX-Redirect': url_for('dashboard')} + return render_template('error.html', error='403', message=msg, url='/') + return func(*args, **kwargs) + return wrapper + + def get_auth_message(endpoint, person_id=None, is_authenticated=False): """Generates a friendly authorization message.""" action = ACTION_MAP.get(endpoint) @@ -128,8 +147,9 @@ def require_ownership(func): def wrapper(*args, **kwargs): person_id = get_person_id_from_context() - # Authorization check: must be logged in and the owner - if not current_user.is_authenticated or person_id is None or int(current_user.get_id()) != person_id: + # Authorization check: must be logged in and (the owner or an admin) + is_admin = getattr(current_user, 'is_admin', False) + if not current_user.is_authenticated or (person_id is not None and int(current_user.get_id()) != person_id and not is_admin): from flask import flash msg = get_auth_message(request.endpoint, person_id, is_authenticated=current_user.is_authenticated) flash(msg, "info")