from typing import Annotated from fastapi import APIRouter, Depends, Request, status from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user from app.database import get_db from app.models.user import User from app.schemas.auth import ( AuthResponse, LoginRequest, RefreshRequest, TokenResponse, UserResponse, RegisterRequest, ) from app.core.rate_limit import check_rate_limit from app.services import auth_service router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/register", response_model=AuthResponse, status_code=status.HTTP_201_CREATED) async def register( data: RegisterRequest, request: Request, db: Annotated[AsyncSession, Depends(get_db)], ): await check_rate_limit(request) from app.services.setting_service import get_setting_value registration_enabled = await get_setting_value(db, "self_registration_enabled", True) if not registration_enabled: from fastapi import HTTPException raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Registration is currently disabled") return await auth_service.register_user( db, data, ip_address=request.client.host if request.client else None, device_info=request.headers.get("user-agent"), ) @router.post("/login", response_model=AuthResponse) async def login( data: LoginRequest, request: Request, db: Annotated[AsyncSession, Depends(get_db)], ): await check_rate_limit(request) return await auth_service.login_user( db, email=data.email, password=data.password, remember_me=data.remember_me, ip_address=request.client.host if request.client else None, device_info=request.headers.get("user-agent"), ) @router.post("/refresh", response_model=TokenResponse) async def refresh( data: RefreshRequest, db: Annotated[AsyncSession, Depends(get_db)], ): return await auth_service.refresh_tokens(db, data.refresh_token) @router.post("/logout", status_code=status.HTTP_204_NO_CONTENT) async def logout( data: RefreshRequest, db: Annotated[AsyncSession, Depends(get_db)], ): await auth_service.logout_user(db, data.refresh_token) @router.get("/me", response_model=UserResponse) async def me(user: Annotated[User, Depends(get_current_user)]): return UserResponse.model_validate(user) # --- OAuth --- @router.get("/oauth/{provider}/authorize") async def oauth_authorize(provider: str): from app.services.oauth_service import get_authorize_url url = await get_authorize_url(provider) return {"authorize_url": url} @router.get("/oauth/{provider}/callback") async def oauth_callback( provider: str, code: str, request: Request, db: Annotated[AsyncSession, Depends(get_db)], ): from app.services.oauth_service import handle_callback result = await handle_callback( provider, code, db, ip_address=request.client.host if request.client else None, device_info=request.headers.get("user-agent"), ) # Redirect to frontend with tokens from fastapi.responses import RedirectResponse redirect_url = f"/auth/callback?access_token={result['access_token']}&refresh_token={result['refresh_token']}" return RedirectResponse(url=redirect_url) @router.post("/switch", response_model=AuthResponse) async def switch_account( data: RefreshRequest, db: Annotated[AsyncSession, Depends(get_db)], ): """Switch to another account using its refresh token. Returns full AuthResponse.""" tokens = await auth_service.refresh_tokens(db, data.refresh_token) # Get user from new access token from app.core.security import decode_access_token import uuid as uuid_mod payload = decode_access_token(tokens.access_token) user_id = uuid_mod.UUID(payload["sub"]) from sqlalchemy import select result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one() return AuthResponse( user=UserResponse.model_validate(user), access_token=tokens.access_token, refresh_token=tokens.refresh_token, )