Spaces:
Running
Running
| """ | |
| Auth API Endpoints — /api/v2/auth/* | |
| Production-grade authentication with token rotation and session management. | |
| """ | |
| import logging | |
| from fastapi import APIRouter, Depends, HTTPException, Request, status | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from app.database.session import get_db | |
| from app.services.auth_service import AuthService | |
| from app.core.permissions import get_current_user, AuthenticatedUser | |
| from app.schemas.auth import ( | |
| SignUpRequest, | |
| LoginRequest, | |
| RefreshTokenRequest, | |
| MagicLinkRequest, | |
| ChangePasswordRequest, | |
| AuthResponse, | |
| TokenPair, | |
| SessionInfo, | |
| ) | |
| from app.schemas.common import MessageResponse | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/auth", tags=["Authentication"]) | |
| def _get_client_info(request: Request) -> tuple[str | None, str | None]: | |
| """Extract IP and user agent from request.""" | |
| ip = request.client.host if request.client else None | |
| ua = request.headers.get("User-Agent") | |
| return ip, ua | |
| async def signup( | |
| request: Request, | |
| body: SignUpRequest, | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """Register a new user account.""" | |
| ip, ua = _get_client_info(request) | |
| service = AuthService(db) | |
| try: | |
| return await service.register(body, ip_address=ip, user_agent=ua) | |
| except ValueError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def login( | |
| request: Request, | |
| body: LoginRequest, | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """Authenticate and receive access + refresh tokens.""" | |
| ip, ua = _get_client_info(request) | |
| service = AuthService(db) | |
| try: | |
| return await service.login(body, ip_address=ip, user_agent=ua) | |
| except ValueError as e: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) | |
| async def refresh_tokens( | |
| request: Request, | |
| body: RefreshTokenRequest, | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """Rotate refresh token and get a new access + refresh token pair.""" | |
| ip, _ = _get_client_info(request) | |
| service = AuthService(db) | |
| try: | |
| return await service.refresh_tokens(body.refresh_token, ip_address=ip) | |
| except ValueError as e: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) | |
| async def logout( | |
| user: AuthenticatedUser = Depends(get_current_user), | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """Revoke the current session.""" | |
| service = AuthService(db) | |
| await service.logout(user.id) | |
| return MessageResponse(message="Logged out successfully") | |
| async def logout_all( | |
| user: AuthenticatedUser = Depends(get_current_user), | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """Revoke all sessions — logout from all devices.""" | |
| service = AuthService(db) | |
| await service.logout_all(user.id) | |
| return MessageResponse(message="All sessions revoked") | |
| async def list_sessions( | |
| user: AuthenticatedUser = Depends(get_current_user), | |
| db: AsyncSession = Depends(get_db), | |
| ): | |
| """List all active sessions for the current user.""" | |
| service = AuthService(db) | |
| return await service.get_active_sessions(user.id) | |
| async def get_current_user_info( | |
| user: AuthenticatedUser = Depends(get_current_user), | |
| ): | |
| """Get current authenticated user info from token.""" | |
| return { | |
| "id": user.id, | |
| "email": user.email, | |
| "roles": user.roles, | |
| "highest_role": user.highest_role, | |
| } | |