diff --git a/backend/blueprints/profile/__init__.py b/backend/blueprints/profile/__init__.py index f87c59e..c8d2dc4 100644 --- a/backend/blueprints/profile/__init__.py +++ b/backend/blueprints/profile/__init__.py @@ -3,8 +3,8 @@ from flask import Blueprint, request, jsonify, current_app from werkzeug.utils import secure_filename from datetime import datetime from db.model import db -from db.model import User, UserRole # Adjust based on your model's location -from werkzeug.security import generate_password_hash +from db.model import User, UserRole, Session # Adjust based on your model's location +from werkzeug.security import generate_password_hash,check_password_hash import uuid import os from config import * @@ -99,19 +99,73 @@ def register(): db.session.rollback() return jsonify({"error": "Registration failed, please try again later."}), 500 -# TODO: Implement laters -@profile.route('/update', methhods=['UPDATE', 'DELETE']) -def update(): - if request.method == 'DELETE': - pass - if request.method == 'UPDATE': - pass - -@profile.route('/me') -def my_profile(): - pass - -@profile.route('/info/') -def profile_info(user_uuid): - return user_uuid + +@profile.route('/login', methods=['POST']) +def login(): + """ + Handle user login. + """ + data = request.form # Expecting JSON body + + # Extract credentials from request + # username = data.get('username') + email = data.get('email') + password = data.get('password') + user_agent = request.headers.get('User-Agent', 'Unknown') + + # Validate required fields + if not email or not password: + return jsonify({"error": "email and password are required"}), 400 + + # Find the user by username + # user = User.query.filter_by(username=username).first() + user = User.query.filter_by(email=email).first() + + if not user: + return jsonify({"error": "Invalid email or password"}), 401 + + # Verify the password + if not check_password_hash(user.hash_password, password): + return jsonify({"error": "Invalid email or password"}), 401 + + # Create a new session + session_key = str(uuid.uuid4()) # Generate a unique session key + new_session = Session( + userID=user.id, + user=user, # Pass the user object here + key=session_key, + ua=user_agent, + creationDate=datetime.utcnow(), + lastUsed=datetime.utcnow(), + isValid=True + ) + + + try: + db.session.add(new_session) + db.session.commit() + return jsonify({ + "message": "Login successful", + "session_key": session_key, + "user_id": str(user.id) + }), 200 + except Exception as e: + db.session.rollback() + return jsonify({"error": "Login failed, please try again later."}), 500 + +#Implement laters +# @profile.route('/update', methhods=['UPDATE', 'DELETE']) +# def update(): +# if request.method == 'DELETE': +# pass +# if request.method == 'UPDATE': +# pass + +# @profile.route('/me') +# def my_profile(): +# pass + +# @profile.route('/info/') +# def profile_info(user_uuid): +# return user_uuid diff --git a/backend/blueprints/session/__init__.py b/backend/blueprints/session/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/db/model.py b/backend/db/model.py index be44ffe..40edac3 100644 --- a/backend/db/model.py +++ b/backend/db/model.py @@ -70,14 +70,14 @@ class Course(db.Model): enrollments: Mapped[List["Enrollment"]] = relationship(back_populates="course", cascade="all, delete-orphan") quizzes: Mapped[List['Quiz']] = relationship(back_populates="course", cascade="all, delete-orphan") chats: Mapped[List["Chat"]] = relationship(back_populates="course", cascade="all, delete-orphan") + authorID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id")) + author: Mapped["User"] = relationship(back_populates="publications") description: Mapped[str] = mapped_column(String(1024), nullable=False, default='') isActive: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) publishedStatus: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=PublishedStatus.DRAFT) creationDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) coverImage: Mapped[str] = mapped_column(String(256), nullable=False, default=DEFAULT_COURSE_COVER) serverFilename: Mapped[str] = mapped_column(String(256), nullable=False, default='') - authorID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id")) - author: Mapped["User"] = relationship(back_populates="publications") class Enrollment(db.Model): diff --git a/backend/utils/auth.py b/backend/utils/auth.py index 1829726..73ba416 100644 --- a/backend/utils/auth.py +++ b/backend/utils/auth.py @@ -1,13 +1,10 @@ from functools import wraps -from flask import request, jsonify +from flask import request, jsonify, g from sqlalchemy import select, and_ from ..db.model import User, Session, db from ..constants import UserRole -def requires_role(roles=None): - if roles is None: - roles = [UserRole.USER, UserRole.ADMIN] - roles = [int(r) for r in roles] +def auth_required(): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): @@ -23,14 +20,26 @@ def requires_role(roles=None): ).scalar() if not session: return jsonify({'error': 'Invalid or expired session'}), 401 - user = session.user + user: User = session.user if not user: return jsonify({'error': 'User not found for the Access token'}), 401 - # If no roles specified, allow access - if not roles: - return f(*args, **kwargs) - if user.role in roles: + g.current_session = session + g.current_user = user + g.is_authed = True + return f(*args, **kwargs) + return decorated_function + return decorator + +def requires_role(roles=None): + if roles is None: + roles = [UserRole.USER, UserRole.ADMIN] + roles = [int(r) for r in roles] + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.get('is_authed', False) is False: + return jsonify({'error': 'Unauthorized'}) + if g.current_user.role in roles: return f(*args, **kwargs) - return jsonify({'error': 'Not authorized'}), 403 return decorated_function return decorator \ No newline at end of file