refactor: migrate main.py to modular routers and add project roadmap
- Migrated monolithic main.py to feature-scoped routers in app/routers/ - Added GEMINI.md for project context and AI instructional guidelines - Updated README.md with a comprehensive modernization plan (SQL migration, robust scraping DSL, frontend modernization) - Improved authentication with cookie support and modular JS - Updated test suite and documentation
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
Authentication routes for Ohm Stream Downloader API.
|
||||
|
||||
Endpoints:
|
||||
- POST /api/auth/register - Register a new user
|
||||
- POST /api/auth/login - Login user and return JWT token
|
||||
- GET /api/auth/me - Get current user information
|
||||
- POST /api/auth/logout - Logout user (client-side)
|
||||
- POST /api/auth/refresh - Refresh access token
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
from app.auth import (
|
||||
create_access_token,
|
||||
user_manager,
|
||||
verify_token,
|
||||
)
|
||||
from app.models.auth import User, UserCreate, UserLogin
|
||||
|
||||
security = HTTPBearer()
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
|
||||
async def get_current_user_from_token(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
) -> User:
|
||||
"""Dependency to get current user from JWT token"""
|
||||
token = credentials.credentials
|
||||
username = verify_token(token)
|
||||
|
||||
if username is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
user_dict = user_manager.get_user(username)
|
||||
if user_dict is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User not found",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
return User(**user_dict)
|
||||
|
||||
|
||||
@router.post("/register")
|
||||
async def register(user_data: UserCreate):
|
||||
"""Register a new user"""
|
||||
try:
|
||||
existing_user = user_manager.get_user(user_data.username)
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Username already registered",
|
||||
)
|
||||
|
||||
user = user_manager.create_user(
|
||||
username=user_data.username,
|
||||
password=user_data.password,
|
||||
email=user_data.email,
|
||||
full_name=user_data.full_name,
|
||||
)
|
||||
|
||||
user_response = User(
|
||||
id=user["id"],
|
||||
username=user["username"],
|
||||
email=user.get("email"),
|
||||
full_name=user.get("full_name"),
|
||||
is_active=user["is_active"],
|
||||
created_at=datetime.fromisoformat(user["created_at"]),
|
||||
last_login=datetime.fromisoformat(user["last_login"])
|
||||
if user.get("last_login")
|
||||
else None,
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "User registered successfully",
|
||||
"user": user_response,
|
||||
}
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
from logging import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
logger.error(f"Error registering user: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to register user",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
async def login(form_data: UserLogin):
|
||||
"""Login user and return JWT token"""
|
||||
user = user_manager.authenticate_user(form_data.username, form_data.password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
if not user.get("is_active", True):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="User account is disabled"
|
||||
)
|
||||
|
||||
access_token = create_access_token(
|
||||
data={"sub": user["username"]}, expires_delta=timedelta(days=7)
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": user["id"],
|
||||
"username": user["username"],
|
||||
"email": user.get("email"),
|
||||
"full_name": user.get("full_name"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def get_me(current_user: User = Depends(get_current_user_from_token)):
|
||||
"""Get current user information"""
|
||||
return {
|
||||
"user": {
|
||||
"id": current_user.id,
|
||||
"username": current_user.username,
|
||||
"email": current_user.email,
|
||||
"full_name": current_user.full_name,
|
||||
"is_active": current_user.is_active,
|
||||
"created_at": current_user.created_at,
|
||||
"last_login": current_user.last_login,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout():
|
||||
"""Logout user (client-side only)"""
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Logout successful. Please remove the token from client storage.",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def refresh_token(refresh_request: dict):
|
||||
"""Refresh access token using a valid refresh token."""
|
||||
from app.auth import (
|
||||
verify_refresh_token,
|
||||
create_access_refresh_tokens,
|
||||
revoke_refresh_token,
|
||||
user_manager as um,
|
||||
)
|
||||
|
||||
refresh_token_value = refresh_request.get("refresh_token")
|
||||
if not refresh_token_value:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Refresh token is required"
|
||||
)
|
||||
|
||||
username = verify_refresh_token(refresh_token_value)
|
||||
if not username:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired refresh token",
|
||||
)
|
||||
|
||||
user = um.get_user(username)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found"
|
||||
)
|
||||
if not user.get("is_active", True):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="User account is disabled"
|
||||
)
|
||||
|
||||
revoke_refresh_token(refresh_token_value)
|
||||
|
||||
access_token, new_refresh_token = create_access_refresh_tokens(
|
||||
data={"sub": username}
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": new_refresh_token,
|
||||
"token_type": "bearer",
|
||||
}
|
||||
Reference in New Issue
Block a user