Files
ohm_streaming/app/routers/router_auth.py
T
root d4d8d8a3b6
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
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
2026-03-24 10:12:04 +00:00

204 lines
6.0 KiB
Python

"""
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",
}