from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from pydantic import BaseModel from sqlalchemy.orm import Session from typing import Optional from app.auth import ( ACCESS_TOKEN_EXPIRE_MINUTES, Token, UserOut, authenticate_user, create_access_token, get_current_active_user, get_user_by_username, hash_password, require_role, ) from app.database import get_db from app.models import UserModel router = APIRouter(prefix="/auth", tags=["auth"]) class UserCreate(BaseModel): username: str password: str email: Optional[str] = None role: str = "viewer" class PasswordChange(BaseModel): current_password: str new_password: str # ---------- Endpoints ---------- @router.post("/token", response_model=Token) def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db), ): user = authenticate_user(db, 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"}, ) access_token = create_access_token( {"sub": user.username, "role": user.role}, timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), ) return Token(access_token=access_token) @router.get("/me", response_model=UserOut) def me(current_user: UserModel = Depends(get_current_active_user)): return current_user @router.patch("/me/password") def change_my_password( body: PasswordChange, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db), ): from app.auth import verify_password if not verify_password(body.current_password, current_user.hashed_password): raise HTTPException(status_code=400, detail="Current password is incorrect") current_user.hashed_password = hash_password(body.new_password) current_user.force_password_change = False db.commit() return {"detail": "Password updated"} @router.get("/users", response_model=list[UserOut]) def list_users( _: UserModel = Depends(require_role("admin")), db: Session = Depends(get_db), ): return db.query(UserModel).order_by(UserModel.id).all() @router.post("/users", response_model=UserOut, status_code=201) def create_user( body: UserCreate, _: UserModel = Depends(require_role("admin")), db: Session = Depends(get_db), ): if get_user_by_username(db, body.username): raise HTTPException(status_code=409, detail="Username already exists") user = UserModel( username=body.username, hashed_password=hash_password(body.password), email=body.email, role=body.role, disabled=False, force_password_change=False, ) db.add(user) db.commit() db.refresh(user) return user @router.patch("/users/{username}/disable") def disable_user( username: str, _: UserModel = Depends(require_role("admin")), db: Session = Depends(get_db), ): user = get_user_by_username(db, username) if not user: raise HTTPException(status_code=404, detail="User not found") user.disabled = True db.commit() return {"detail": f"User {username!r} disabled"}