Refactor user authentication and dashboard features

- Added `is_admin` field to User model for role management.
- Updated user context middleware to include current user in templates.
- Enhanced session management with improved debug middleware.
- Refactored dashboard view to fetch user-specific data and recent events.
- Improved login and registration templates for better user experience.
- Added admin routes with access control for admin users.
- Updated Docker configuration for better error logging and dependency management.
- Updated requirements to include new dependencies and specify versions.
This commit is contained in:
Christian Krakau-Louis
2025-04-13 18:49:27 +02:00
parent 0de2eb5830
commit 4a79c5cacb
20 changed files with 790 additions and 579 deletions
+29 -100
View File
@@ -1,110 +1,39 @@
from fastapi import Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from sqlalchemy import inspect
from typing import Optional
from datetime import datetime
from .db import get_db
from . import models
from .auth import get_current_user, require_login, require_admin
from .db import SessionLocal, engine
from .models import User
from .security import SECRET_KEY, ALGORITHM
from .templates_config import templates
# Reuse functions from auth.py
# This is just for backward compatibility with any code that imported these from dependencies
# OAuth2 scheme for token authentication
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
# Function to get a db session
def get_session_db():
return next(get_db())
def get_db():
"""Database dependency."""
db = SessionLocal()
try:
yield db
finally:
db.close()
# Function to get current authenticated user
def get_authenticated_user(request: Request):
return get_current_user(request)
# Check if all required user columns exist
def get_available_user_columns():
inspector = inspect(engine)
if 'users' in inspector.get_table_names():
return [col['name'] for col in inspector.get_columns('users')]
return []
# These are kept for API backward compatibility
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
"""Get the current authenticated user based on the access token."""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# If no token, return None (not authenticated)
if not token:
return None
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = db.query(User).filter(User.username == username).first()
if user is None:
raise credentials_exception
# Update last login time if column exists
columns = get_available_user_columns()
if 'last_login' in columns and hasattr(user, 'last_login'):
user.last_login = datetime.utcnow()
db.commit()
def get_current_active_user(request: Request):
user = get_current_user(request)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
"""Check if the current user is active."""
if not current_user:
return None
columns = get_available_user_columns()
if 'is_active' in columns and hasattr(current_user, 'is_active') and not current_user.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
# Improved session-based user lookup with better error handling and logging
async def get_user_from_session(request: Request):
"""Get current user from session with improved error handling"""
try:
if not hasattr(request, "session"):
print("No session attribute in request")
return None
user_id = request.session.get("user_id")
if not user_id:
print("No user_id in session")
return None
print(f"Looking up user with ID: {user_id}")
# Manually get a database session from get_db
db = next(get_db())
try:
user = db.query(User).filter(User.id == user_id).first()
if not user:
print(f"User with ID {user_id} not found in database")
# Clear invalid session data
request.session.clear()
return None
return user
finally:
db.close()
except Exception as e:
print(f"Error getting user from session: {str(e)}")
return None
# Template context processor to add user to all templates
async def add_user_to_templates(request: Request):
"""Add current user to all template contexts."""
user = await get_user_from_session(request)
return {"current_user": user}
def get_current_admin_user(request: Request):
user = get_current_user(request)
if not user or not user.get("is_admin"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
)
return user