"""YakPanel - Auth API""" from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel from app.core.database import get_db from app.core.security import verify_password, get_password_hash, create_access_token, decode_token from app.core.config import get_settings from app.models.user import User router = APIRouter(prefix="/auth", tags=["auth"]) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth/login") async def get_current_user( token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db), ) -> User: """Get current authenticated user from JWT""" credentials_exception = HTTPException(status_code=401, detail="Invalid credentials") payload = decode_token(token) if not payload: raise credentials_exception sub = payload.get("sub") user_id = int(sub) if isinstance(sub, str) else sub if not user_id: raise credentials_exception result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise credentials_exception if not user.is_active: raise HTTPException(status_code=400, detail="User inactive") return user @router.post("/login") async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db), ): """Login and return JWT token""" from sqlalchemy import select result = await db.execute(select(User).where(User.username == form_data.username)) user = result.scalar_one_or_none() if not user or not verify_password(form_data.password, user.password): raise HTTPException(status_code=401, detail="Incorrect username or password") if not user.is_active: raise HTTPException(status_code=400, detail="User inactive") access_token = create_access_token( data={"sub": str(user.id)}, expires_delta=timedelta(minutes=get_settings().access_token_expire_minutes), ) return {"access_token": access_token, "token_type": "bearer", "user": {"id": user.id, "username": user.username}} @router.post("/logout") async def logout(): """Logout (client should discard token)""" return {"message": "Logged out"} @router.get("/me") async def get_me(current_user: User = Depends(get_current_user)): """Get current user info""" return {"id": current_user.id, "username": current_user.username, "email": current_user.email, "is_superuser": current_user.is_superuser} class ChangePasswordRequest(BaseModel): old_password: str new_password: str @router.post("/change-password") async def change_password( body: ChangePasswordRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Change password""" if not verify_password(body.old_password, current_user.password): raise HTTPException(status_code=400, detail="Incorrect current password") result = await db.execute(select(User).where(User.id == current_user.id)) user = result.scalar_one() user.password = get_password_hash(body.new_password) await db.commit() return {"message": "Password changed"}