Remove token revocation logic and unused dependencies

Eliminated the `RevokedToken` model and associated logic for managing token revocation. Removed unused files, related tests, and outdated dependencies in authentication modules. Simplified token decoding, user validation, and dependency injection by streamlining the flow and enhancing maintainability.
This commit is contained in:
2025-03-02 11:04:12 +01:00
parent 453016629f
commit cd92cd9780
24 changed files with 954 additions and 781 deletions

View File

@@ -0,0 +1,138 @@
# app/api/dependencies/auth.py
from typing import Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from app.core.auth import get_token_data, TokenExpiredError, TokenInvalidError
from app.core.database import get_db
from app.models.user import User
# OAuth2 configuration
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
def get_current_user(
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)
) -> User:
"""
Get the current authenticated user.
Args:
db: Database session
token: JWT token from request
Returns:
User: The authenticated user
Raises:
HTTPException: If authentication fails
"""
try:
# Decode token and get user ID
token_data = get_token_data(token)
# Get user from database
user = db.query(User).filter(User.id == token_data.user_id).first()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Inactive user"
)
return user
except TokenExpiredError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token expired",
headers={"WWW-Authenticate": "Bearer"}
)
except TokenInvalidError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}
)
def get_current_active_user(
current_user: User = Depends(get_current_user)
) -> User:
"""
Check if the current user is active.
Args:
current_user: The current authenticated user
Returns:
User: The authenticated and active user
Raises:
HTTPException: If user is inactive
"""
if not current_user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Inactive user"
)
return current_user
def get_current_superuser(
current_user: User = Depends(get_current_user)
) -> User:
"""
Check if the current user is a superuser.
Args:
current_user: The current authenticated user
Returns:
User: The authenticated superuser
Raises:
HTTPException: If user is not a superuser
"""
if not current_user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions"
)
return current_user
def get_optional_current_user(
db: Session = Depends(get_db),
token: Optional[str] = Depends(oauth2_scheme)
) -> Optional[User]:
"""
Get the current user if authenticated, otherwise return None.
Useful for endpoints that work with both authenticated and unauthenticated users.
Args:
db: Database session
token: JWT token from request
Returns:
User or None: The authenticated user or None
"""
if not token:
return None
try:
token_data = get_token_data(token)
user = db.query(User).filter(User.id == token_data.user_id).first()
if not user or not user.is_active:
return None
return user
except (TokenExpiredError, TokenInvalidError):
return None

View File

@@ -1,134 +1,3 @@
from typing import Any
from app.auth.utils import revoke_token, is_token_revoked
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.security import authenticate_user, create_access_token, create_refresh_token, decode_token
from app.core.database import get_db
from app.models.user import User
from app.schemas.token import TokenResponse, TokenPayload, RefreshToken
from app.schemas.user import UserResponse
from fastapi import APIRouter
router = APIRouter()
oauth2_scheme = OAuth2PasswordRequestForm
# Existing: User Login Endpoint
@router.post(
"/auth/login",
response_model=TokenResponse,
summary="Authenticate user and provide tokens"
)
async def login(
form_data: OAuth2PasswordRequestForm = Depends(),
db: AsyncSession = Depends(get_db)
) -> Any:
"""
Authenticate a user with their credentials and return an access and refresh token.
"""
user = await authenticate_user(email=form_data.username, password=form_data.password, db=db)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials.")
# Generate access and refresh tokens
access_token = create_access_token({"sub": str(user.id), "type": "access"})
refresh_token = create_refresh_token({"sub": str(user.id), "type": "refresh"})
return TokenResponse(
access_token=access_token,
refresh_token=refresh_token,
token_type="bearer",
expires_in=1800, # Example: 30 minutes for access token
user_id=str(user.id),
)
# New: Logout Endpoint (Revoke Token)
@router.post(
"/auth/logout",
summary="Revoke the current token",
response_model=dict,
status_code=status.HTTP_200_OK
)
async def logout(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(
lambda token=Depends(oauth2_scheme), db=Depends(get_db): decode_token(token, db=db))
):
"""
Logout the user by revoking the current token.
"""
# Decode the token and revoke it
payload: TokenPayload = await decode_token(token, db=db)
await revoke_token(payload.jti, payload.type, payload.sub, db)
return {"message": "Successfully logged out."}
# New: Bulk Logout (Revoke All of a User's Tokens)
@router.post(
"/auth/logout-all",
summary="Revoke all active tokens for the user",
response_model=dict,
status_code=status.HTTP_200_OK
)
async def logout_all(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(
lambda token=Depends(oauth2_scheme), db=Depends(get_db): decode_token(token, db=db))
):
"""
Revoke all tokens for the current user, effectively logging them out across all devices.
"""
await db.execute("DELETE FROM revoked_tokens WHERE user_id = :user_id", {"user_id": str(current_user.id)})
await db.commit()
return {"message": "Logged out from all devices."}
# Updated: Refresh Token Endpoint
@router.post(
"/auth/refresh-token",
response_model=TokenResponse,
summary="Generate a new access token using a refresh token"
)
async def refresh_token(
refresh_token: RefreshToken,
db: AsyncSession = Depends(get_db)
) -> TokenResponse:
"""
Refresh the user's access token using their refresh token while ensuring it has not been revoked.
"""
payload: TokenPayload = await decode_token(refresh_token.refresh_token, required_type="refresh", db=db)
if await is_token_revoked(payload.jti, db):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Token has been revoked.")
# Generate a new access token with the user's info
new_access_token = create_access_token({"sub": payload.sub, "type": "access"})
return TokenResponse(
access_token=new_access_token,
refresh_token=refresh_token.refresh_token, # Reuse existing refresh token
expires_in=1800, # Example: 30 minutes expiry for access token
token_type="bearer",
user_id=payload.sub,
)
# Existing: Get Current User Endpoint
@router.get(
"/auth/me",
response_model=UserResponse,
summary="Get user details from the token"
)
async def read_users_me(
current_user: User = Depends(
lambda token=Depends(oauth2_scheme), db=Depends(get_db): decode_token(token, db=db))
) -> UserResponse:
"""
Retrieves the details of the currently authenticated user.
"""
return current_user