137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
import os
|
||
import secrets
|
||
import httpx
|
||
import flet as ft
|
||
|
||
API_URL = os.getenv("API_URL", "http://api:8000")
|
||
_TOKEN_KEY = "auth_token"
|
||
|
||
|
||
class AuthService:
|
||
"""Сервис авторизации: логин, логаут, проверка токена."""
|
||
|
||
def __init__(self, page: ft.Page):
|
||
self._page = page
|
||
|
||
async def login(self, username: str, password: str) -> str | None:
|
||
"""
|
||
Отправляет запрос на POST /auth/login.
|
||
Возвращает None при успехе, строку с ошибкой при неудаче.
|
||
"""
|
||
try:
|
||
async with httpx.AsyncClient(base_url=API_URL, timeout=10) as client:
|
||
resp = await client.post(
|
||
"/auth/login",
|
||
data={"username": username, "password": password},
|
||
)
|
||
if resp.status_code == 200:
|
||
token = resp.json()["access_token"]
|
||
self._page.session.store.set(_TOKEN_KEY, token)
|
||
# Случайный ключ для URL профиля — хранится только в памяти сессии
|
||
self._page.session.store.set("session_key", secrets.token_hex(16))
|
||
# user_id Для фетчинга данных
|
||
me = await self._get_me(token)
|
||
if me:
|
||
self._page.session.store.set("user_id", me["id"])
|
||
return None
|
||
elif resp.status_code == 401:
|
||
return "Неверный логин или пароль"
|
||
else:
|
||
return f"Ошибка сервера: {resp.status_code}"
|
||
except httpx.ConnectError:
|
||
return "Не удалось подключиться к серверу"
|
||
except Exception as e:
|
||
return f"Ошибка: {e}"
|
||
|
||
async def _get_me(self, token: str) -> dict | None:
|
||
"""Внутренний метод: получает данные текущего пользователя по токену."""
|
||
try:
|
||
async with httpx.AsyncClient(base_url=API_URL, timeout=10) as client:
|
||
resp = await client.get(
|
||
"/users/me",
|
||
headers={"Authorization": f"Bearer {token}"},
|
||
)
|
||
if resp.status_code == 200:
|
||
return resp.json()
|
||
except Exception:
|
||
pass
|
||
return None
|
||
|
||
async def get_me(self) -> dict | None:
|
||
"""Возвращает данные текущего пользователя или None."""
|
||
token = self._page.session.store.get(_TOKEN_KEY)
|
||
if not token:
|
||
return None
|
||
return await self._get_me(token)
|
||
|
||
async def logout(self):
|
||
"""Удаляет токен — пользователь разлогинен."""
|
||
self._page.session.store.remove(_TOKEN_KEY)
|
||
self._page.session.store.remove("user_id")
|
||
self._page.session.store.remove("session_key")
|
||
|
||
async def get_token(self) -> str | None:
|
||
"""Возвращает сохранённый токен или None если не авторизован."""
|
||
return self._page.session.store.get(_TOKEN_KEY)
|
||
|
||
async def is_authenticated(self) -> bool:
|
||
token = await self.get_token()
|
||
return token is not None and token != ""
|
||
|
||
async def register(
|
||
self,
|
||
username: str,
|
||
password: str,
|
||
first_name: str,
|
||
last_name: str,
|
||
group_id: str | None = None,
|
||
organization_id: str | None = None,
|
||
) -> str | None:
|
||
"""
|
||
Отправляет запрос на POST /auth/register.
|
||
Возвращает None при успехе, строку с ошибкой при неудаче.
|
||
"""
|
||
payload = {
|
||
"username": username,
|
||
"password": password,
|
||
"first_name": first_name,
|
||
"last_name": last_name,
|
||
}
|
||
if group_id:
|
||
payload["group_id"] = group_id
|
||
if organization_id:
|
||
payload["organization_id"] = organization_id
|
||
try:
|
||
async with httpx.AsyncClient(base_url=API_URL, timeout=10) as client:
|
||
resp = await client.post("/auth/register", json=payload)
|
||
if resp.status_code == 201:
|
||
return None
|
||
elif resp.status_code == 400:
|
||
return resp.json().get("detail", "Ошибка регистрации")
|
||
else:
|
||
return f"Ошибка сервера: {resp.status_code}"
|
||
except httpx.ConnectError:
|
||
return "Не удалось подключиться к серверу"
|
||
except Exception as e:
|
||
return f"Ошибка: {e}"
|
||
|
||
async def get_organizations(self) -> list:
|
||
try:
|
||
async with httpx.AsyncClient(base_url=API_URL, timeout=10) as client:
|
||
resp = await client.get("/organizations/")
|
||
if resp.status_code == 200:
|
||
return resp.json()
|
||
except Exception:
|
||
pass
|
||
return []
|
||
|
||
async def get_groups(self) -> list:
|
||
try:
|
||
async with httpx.AsyncClient(base_url=API_URL, timeout=10) as client:
|
||
resp = await client.get("/groups/")
|
||
if resp.status_code == 200:
|
||
return resp.json()
|
||
except Exception:
|
||
pass
|
||
return []
|