from __future__ import annotations import hmac from typing import Annotated from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from ..auth import issue_token, require_control_user from ..config import ControlSettings, get_control_settings from ..envfile import update_control_credentials router = APIRouter(prefix="/api/auth", tags=["auth"]) class LoginBody(BaseModel): username: str = Field(min_length=1) password: str = Field(min_length=1) class ChangeCredentialsBody(BaseModel): current_password: str = Field(min_length=1) new_username: str = Field(min_length=1, max_length=64) new_password: str = Field(min_length=6, max_length=128) @router.post("/login") async def login( body: LoginBody, settings: Annotated[ControlSettings, Depends(get_control_settings)], ) -> dict: user_ok = hmac.compare_digest( body.username.encode("utf-8"), settings.control_auth_username.encode("utf-8"), ) pwd_ok = hmac.compare_digest( body.password.encode("utf-8"), settings.control_auth_password.encode("utf-8"), ) if not (user_ok and pwd_ok): raise HTTPException(status_code=401, detail="用户名或密码错误") token, ttl = issue_token(body.username, settings) return {"token": token, "username": body.username, "expires_in": ttl} @router.get("/me") async def me( username: Annotated[str, Depends(require_control_user)], settings: Annotated[ControlSettings, Depends(get_control_settings)], ) -> dict: return { "username": username, "poll_interval_sec": settings.control_poll_interval_sec, } @router.post("/change-credentials") async def change_credentials( body: ChangeCredentialsBody, username: Annotated[str, Depends(require_control_user)], settings: Annotated[ControlSettings, Depends(get_control_settings)], ) -> dict: if not hmac.compare_digest( body.current_password.encode("utf-8"), settings.control_auth_password.encode("utf-8"), ): raise HTTPException(status_code=400, detail="当前密码不正确") try: update_control_credentials( new_username=body.new_username, new_password=body.new_password, ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) from e settings2 = get_control_settings() token, ttl = issue_token(body.new_username.strip(), settings2) return { "token": token, "username": body.new_username.strip(), "expires_in": ttl, }