feat(cloud-console): add user authentication and administration
This commit is contained in:
@@ -139,6 +139,76 @@ class CloudClient:
|
||||
resp = self._request("POST", "/plugins", json=payload)
|
||||
return resp.json()
|
||||
|
||||
# --------------------------------------------------------------- user auth
|
||||
|
||||
def login(self, *, username: str, password: str) -> dict[str, Any]:
|
||||
resp = self._request(
|
||||
"POST",
|
||||
"/auth/login",
|
||||
json={"username": username, "password": password},
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
def current_user(self) -> dict[str, Any]:
|
||||
resp = self._request("GET", "/auth/me")
|
||||
return resp.json()
|
||||
|
||||
def logout(self) -> None:
|
||||
self._request("POST", "/auth/logout")
|
||||
|
||||
def change_password(self, *, current_password: str, new_password: str) -> None:
|
||||
self._request(
|
||||
"POST",
|
||||
"/auth/password",
|
||||
json={
|
||||
"current_password": current_password,
|
||||
"new_password": new_password,
|
||||
},
|
||||
)
|
||||
|
||||
def list_users(self, *, limit: int = 50, offset: int = 0) -> dict[str, Any]:
|
||||
resp = self._request(
|
||||
"GET",
|
||||
"/users",
|
||||
params={"limit": limit, "offset": offset},
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
def create_user(
|
||||
self,
|
||||
*,
|
||||
username: str,
|
||||
display_name: str,
|
||||
role: str,
|
||||
password: str,
|
||||
) -> dict[str, Any]:
|
||||
resp = self._request(
|
||||
"POST",
|
||||
"/users",
|
||||
json={
|
||||
"username": username,
|
||||
"display_name": display_name,
|
||||
"role": role,
|
||||
"password": password,
|
||||
},
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
def update_user(self, user_id: str, **changes: Any) -> dict[str, Any]:
|
||||
resp = self._request("PATCH", f"/users/{user_id}", json=changes)
|
||||
return resp.json()
|
||||
|
||||
def reset_user_password(self, user_id: str, *, password: str) -> dict[str, Any]:
|
||||
resp = self._request(
|
||||
"POST",
|
||||
f"/users/{user_id}/password",
|
||||
json={"password": password},
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
def revoke_user_sessions(self, user_id: str) -> None:
|
||||
self._request("DELETE", f"/users/{user_id}/sessions")
|
||||
|
||||
# ------------------------------------------------------------------ helpers
|
||||
|
||||
def _url(self, path: str) -> str:
|
||||
@@ -152,12 +222,17 @@ class CloudClient:
|
||||
json: dict[str, Any] | None = None,
|
||||
params: dict[str, Any] | None = None,
|
||||
) -> httpx.Response:
|
||||
headers = dict(self._headers or {})
|
||||
if method in {"POST", "PUT", "PATCH", "DELETE"} and not headers:
|
||||
csrf_token = _cookie_value(self._http, "amcp_csrf")
|
||||
if csrf_token:
|
||||
headers["X-CSRF-Token"] = csrf_token
|
||||
response = self._http.request(
|
||||
method,
|
||||
self._url(path),
|
||||
json=json,
|
||||
params=params,
|
||||
headers=self._headers,
|
||||
headers=headers or None,
|
||||
auth=self._auth,
|
||||
)
|
||||
if response.is_success:
|
||||
@@ -173,3 +248,11 @@ class CloudClient:
|
||||
else CloudAPIError
|
||||
)
|
||||
raise error_type(response, str(detail))
|
||||
|
||||
|
||||
def _cookie_value(client: Any, name: str) -> str | None:
|
||||
cookies = getattr(client, "cookies", None)
|
||||
if cookies is None:
|
||||
return None
|
||||
value = cookies.get(name)
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
Reference in New Issue
Block a user