From 6b0840b0c0a35c57f48d16b459ca91d5a67cc06d Mon Sep 17 00:00:00 2001 From: Casu Al Snek Date: Sat, 11 Jan 2025 15:06:49 +0545 Subject: [PATCH] Start work on role guard --- backend/app.py | 44 ++++++++++++------------------------------- backend/db/model.py | 8 +++++--- backend/utils/auth.py | 39 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 35 deletions(-) create mode 100644 backend/utils/auth.py diff --git a/backend/app.py b/backend/app.py index 08d5b69..f9c6787 100644 --- a/backend/app.py +++ b/backend/app.py @@ -10,6 +10,7 @@ from flask import jsonify import uuid from datetime import datetime from constants import UserRole +from utils.utils import random_string_generator, hash_string from blueprints.profile import profile as profileBlueprint @@ -24,9 +25,8 @@ app.register_blueprint(profileBlueprint, url_prefix='/api') def homepage(): return {'message': 'Welcome back !'}, 200 - -@app.route('/seed-users', methods=['GET']) -def seed_users(): +@app.route('/seedData', methods=['GET']) +def seed_data(): try: # Drop and recreate schema (only for testing, not recommended in production) sql = text('DROP SCHEMA public CASCADE; CREATE SCHEMA public;') @@ -35,38 +35,18 @@ def seed_users(): db.create_all() # Recreate tables # Define roles and constants (ensure UserRole.USER is an integer or map it) - default_role = UserRole.USER.value if hasattr(UserRole, 'USER') else 1 - default_profile_file = "defaultUserBanner.png" - # Create seed users - users = [ - User( - email=f"user{i}@example.com", - firstName=f"FirstName{i}", - lastName=f"LastName{i}", - username=f"username{i}", - hash_password=generate_password_hash("password123"), - pfpFilename=default_profile_file, - joinedDate=datetime.utcnow(), - lastOnline=datetime.utcnow(), - bio=f"This is user{i}'s bio.", - role=default_role, - isActivated=True, - sessions=[], - user_badges=[], - enrollments=[], - quizzes=[], - quiz_attempts=[], - chats=[], - notifications=[] - ) - for i in range(1, 6) - ] - - # Add users to the database + users = [ User( + email=f"user{i}@example.com", firstName=f"FirstName{i}", lastName=f"LastName{i}", + username=f"username{i}", hash_password=generate_password_hash("password123"), + activationKey=hash_string(random_string_generator(16)), pfpFilename=DEFAULT_PROFILE_FILE, + joinedDate=datetime.utcnow(), lastOnline=datetime.utcnow(), + bio=f"This is user{i}'s bio.", role=UserRole.ADMIN if i == 1 else UserRole.USER, + isActivated=True, sessions=[], user_badges=[], + enrollments=[], quizzes=[], quiz_attempts=[], chats=[], notifications=[]) + for i in range(1, 6) ] db.session.add_all(users) db.session.commit() - return {"message": "Users seeded successfully!"}, 201 except Exception as e: db.session.rollback() diff --git a/backend/db/model.py b/backend/db/model.py index 97bd408..f3a95f8 100644 --- a/backend/db/model.py +++ b/backend/db/model.py @@ -4,8 +4,8 @@ from sqlalchemy import types, text, String, DateTime, func, Boolean, ForeignKey, from datetime import datetime import uuid from typing import List -from config import * -from constants import UserRole, PublishedStatus +from ..config import * +from ..constants import UserRole, PublishedStatus class Base(MappedAsDataclass, DeclarativeBase): pass @@ -20,13 +20,15 @@ class User(db.Model): firstName: Mapped[str] = mapped_column(String(32), nullable=False) lastName: Mapped[str] = mapped_column(String(32), nullable=False) username: Mapped[str] = mapped_column(String(32), nullable=False) - hash_password: Mapped[str] = mapped_column(String(256), nullable=False) # Added field for hashed password + hash_password: Mapped[str] = mapped_column(String(256), nullable=False) + activationKey: Mapped[str] = mapped_column(String(128), nullable=False) sessions: Mapped[List["Session"]] = relationship(back_populates="user", cascade="all, delete-orphan") enrollments: Mapped[List["Enrollment"]] = relationship(back_populates="user", cascade="all, delete-orphan") quizzes: Mapped[List["Quiz"]] = relationship(back_populates="creatorUser", cascade="all, delete-orphan") quiz_attempts: Mapped[List["QuizAttempt"]] = relationship(back_populates="user", cascade="all, delete-orphan") chats: Mapped[List["Chat"]] = relationship(back_populates="user", cascade="all, delete-orphan") notifications: Mapped[List["Notification"]] = relationship(back_populates="user", cascade="all, delete-orphan") + dob: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.fromisocalendar(2002, 1, 1)) user_badges: Mapped[List["UserBadge"]] = relationship(back_populates="user", cascade="all, delete-orphan") pfpFilename: Mapped[str] = mapped_column(String(256), nullable=False, default=DEFAULT_PROFILE_FILE) joinedDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) diff --git a/backend/utils/auth.py b/backend/utils/auth.py new file mode 100644 index 0000000..24db859 --- /dev/null +++ b/backend/utils/auth.py @@ -0,0 +1,39 @@ +from functools import wraps +from flask import request, jsonify +from sqlalchemy import select +from ..db.model import User, Session, db + +def requires_role(roles=[]): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + auth_header = request.headers.get('Authorization') + if not auth_header: + return jsonify({'error': 'No authorization header sent'}), 401 + try: + session_key = auth_header.split(' ')[1] + except IndexError: + return jsonify({'error': 'Invalid authorization header format'}), 401 + + session = db.session.execute( + + ) + if not session: + return jsonify({'error': 'Invalid or expired session'}), 401 + user = User.query.get(session.userID) + if not user: + return jsonify({'error': 'User not found'}), 401 + + # If no roles specified, allow access + if not roles: + return f(*args, **kwargs) + + # Check if user has any of the required roles + if user.role in roles: + return f(*args, **kwargs) + + return jsonify({'error': 'Insufficient permissions'}), 403 + + return decorated_function + + return decorator \ No newline at end of file