Add Fleet control plane and split manage.sh deploy menu.
Strategy nodes gain fleet token APIs; control/ app for local ops; manage.sh offers strategy vs control one-click deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user