From ed455cb995ebf7ac069c3a915244f9c100de208e Mon Sep 17 00:00:00 2001 From: Casu Al Snek Date: Sat, 11 Jan 2025 17:13:14 +0545 Subject: [PATCH 1/6] Add user and session to request globals --- backend/utils/auth.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/utils/auth.py b/backend/utils/auth.py index 1829726..a8bc72e 100644 --- a/backend/utils/auth.py +++ b/backend/utils/auth.py @@ -1,5 +1,5 @@ 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 @@ -23,9 +23,12 @@ 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 + g.current_session = session + g.current_user = user # If no roles specified, allow access if not roles: return f(*args, **kwargs) From dc11e54ee4b2560a227f2f69e6f545eb73de8acb Mon Sep 17 00:00:00 2001 From: Kushal Dotel Date: Sat, 11 Jan 2025 17:29:26 +0545 Subject: [PATCH 2/6] feat:add login --- backend/blueprints/profile/__init__.py | 56 +++++++++++++++++++++++++- backend/blueprints/session/__init__.py | 0 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 backend/blueprints/session/__init__.py diff --git a/backend/blueprints/profile/__init__.py b/backend/blueprints/profile/__init__.py index edc594a..9907f79 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 * @@ -98,3 +98,55 @@ def register(): except Exception as e: db.session.rollback() return jsonify({"error": "Registration failed, please try again later."}), 500 + + +@profile.route('/login', methods=['POST']) +def login(): + """ + Handle user login. + """ + data = request.json # Expecting JSON body + + # Extract credentials from request + username = data.get('username') + password = data.get('password') + user_agent = request.headers.get('User-Agent', 'Unknown') + + # Validate required fields + if not username or not password: + return jsonify({"error": "Username and password are required"}), 400 + + # Find the user by username + user = User.query.filter_by(username=username).first() + + if not user: + return jsonify({"error": "Invalid username or password"}), 401 + + # Verify the password + if not check_password_hash(user.hash_password, password): + return jsonify({"error": "Invalid username 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 \ No newline at end of file diff --git a/backend/blueprints/session/__init__.py b/backend/blueprints/session/__init__.py new file mode 100644 index 0000000..e69de29 From 0f1fc25239b1686bf1167dafff962fae4cd6eb64 Mon Sep 17 00:00:00 2001 From: Casu Al Snek Date: Sat, 11 Jan 2025 17:42:58 +0545 Subject: [PATCH 3/6] Split auth based decorators --- backend/utils/auth.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/backend/utils/auth.py b/backend/utils/auth.py index a8bc72e..b146565 100644 --- a/backend/utils/auth.py +++ b/backend/utils/auth.py @@ -4,10 +4,7 @@ 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,16 +20,24 @@ def requires_role(roles=None): ).scalar() if not session: return jsonify({'error': 'Invalid or expired session'}), 401 - user: User = session.user if not user: return jsonify({'error': 'User not found for the Access token'}), 401 g.current_session = session g.current_user = user - # If no roles specified, allow access - if not roles: - return f(*args, **kwargs) - if user.role in roles: + 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.current_user.role in roles: return f(*args, **kwargs) return jsonify({'error': 'Not authorized'}), 403 return decorated_function From 2af17a17641323f27e50242f340ea3ecef507352 Mon Sep 17 00:00:00 2001 From: Casu Al Snek Date: Sat, 11 Jan 2025 18:08:39 +0545 Subject: [PATCH 4/6] Check authorization in requires role guard decorator --- backend/utils/auth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/utils/auth.py b/backend/utils/auth.py index b146565..73ba416 100644 --- a/backend/utils/auth.py +++ b/backend/utils/auth.py @@ -37,8 +37,9 @@ def requires_role(roles=None): 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 From 595f2761edd3bee06dc7aca8672403cbabafac01 Mon Sep 17 00:00:00 2001 From: Kushal Dotel Date: Sat, 11 Jan 2025 18:24:04 +0545 Subject: [PATCH 5/6] update field position --- backend/blueprints/profile/__init__.py | 30 +++++++++++++------------- backend/db/model.py | 4 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/backend/blueprints/profile/__init__.py b/backend/blueprints/profile/__init__.py index 642e917..03b503e 100644 --- a/backend/blueprints/profile/__init__.py +++ b/backend/blueprints/profile/__init__.py @@ -150,20 +150,20 @@ def login(): 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 +# @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/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): From 577675317494a1739176082ebeecb0d00d2d29cc Mon Sep 17 00:00:00 2001 From: Kushal Dotel Date: Sat, 11 Jan 2025 18:29:33 +0545 Subject: [PATCH 6/6] handle login by form --- backend/blueprints/profile/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/backend/blueprints/profile/__init__.py b/backend/blueprints/profile/__init__.py index 03b503e..c8d2dc4 100644 --- a/backend/blueprints/profile/__init__.py +++ b/backend/blueprints/profile/__init__.py @@ -105,26 +105,28 @@ def login(): """ Handle user login. """ - data = request.json # Expecting JSON body + data = request.form # Expecting JSON body # Extract credentials from request - username = data.get('username') + # 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 username or not password: - return jsonify({"error": "Username and password are required"}), 400 + 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(username=username).first() + user = User.query.filter_by(email=email).first() if not user: - return jsonify({"error": "Invalid username or password"}), 401 + return jsonify({"error": "Invalid email or password"}), 401 # Verify the password if not check_password_hash(user.hash_password, password): - return jsonify({"error": "Invalid username or password"}), 401 + return jsonify({"error": "Invalid email or password"}), 401 # Create a new session session_key = str(uuid.uuid4()) # Generate a unique session key