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 []