diff --git a/app/core/database.py b/app/core/database.py index a33990f..9759181 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -50,18 +50,37 @@ def init_db(): conn.commit() conn.close() -def add_session(login, access_token, refresh_token): +def add_session(login, access_token, refresh_token, update_existing=False): """Добавляет новую сессию или обновляет существующую.""" conn = get_connection() cursor = conn.cursor() - # REPLACE INTO - удобный способ для вставки или обновления - cursor.execute(''' - INSERT OR REPLACE INTO sessions (login, access_token, refresh_token, created_at) - VALUES (?, ?, ?, ?) - ''', (login, access_token, refresh_token, datetime.now())) + + if update_existing: + # Обновляем существующую сессию по access_token + cursor.execute(''' + UPDATE sessions + SET access_token = ?, refresh_token = ?, created_at = ? + WHERE access_token = ? + ''', (access_token, refresh_token, datetime.now(), access_token)) + else: + # Вставляем новую или заменяем существующую по логину + cursor.execute(''' + INSERT OR REPLACE INTO sessions (login, access_token, refresh_token, created_at) + VALUES (?, ?, ?, ?) + ''', (login, access_token, refresh_token, datetime.now())) + conn.commit() conn.close() +def logout(access_token: str): + """Удаляет сессию по токену доступа.""" + conn = get_connection() + cursor = conn.cursor() + cursor.execute('DELETE FROM sessions WHERE access_token = ?', (access_token,)) + conn.commit() + conn.close() + + def get_session(login: str): """Получает сессию по логину.""" conn = get_connection() @@ -80,13 +99,6 @@ def get_all_sessions(): conn.close() return sessions -def delete_session(login: str): - """Удаляет сессию по логину.""" - conn = get_connection() - cursor = conn.cursor() - cursor.execute('DELETE FROM sessions WHERE login = ?', (login,)) - conn.commit() - conn.close() def get_last_login(): """Получает логин последнего вошедшего пользователя.""" diff --git a/app/core/services/auth_service.py b/app/core/services/auth_service.py index ae85031..3d3002c 100644 --- a/app/core/services/auth_service.py +++ b/app/core/services/auth_service.py @@ -1,7 +1,7 @@ import httpx import asyncio from app.core import config -from app.core.database import add_session +from app.core.database import add_session, logout, get_session from app.core.localizer import localizer async def login(login, password): @@ -91,50 +91,90 @@ async def register(login, password, invite=None): return False, f"{localizer.translate('Произошла ошибка')}: {e}" -async def get_user_role(access_token: str): +async def refresh_token(access_token: str, refresh_token: str): """ - Получает роль и права пользователя по токену доступа. + Обновляет токен доступа, используя токен обновления. - :param access_token: Токен доступа пользователя - :return: Кортеж (успех: bool, данные: UserRoleData | str) + :param access_token: Истекший токен доступа + :param refresh_token: Токен обновления + :return: Кортеж (успех: bool, данные: dict | str) """ - url = f"{config.BASE_URL}/v1/user/role" - headers = {"Authorization": f"Bearer {access_token}"} + url = f"{config.BASE_URL}/v1/auth/token/refresh" + payload = {"access_token": access_token, "refresh_token": refresh_token} - timeout = httpx.Timeout(connect=5.0, read=10.0, write=5.0, pool=5.0) try: - async with httpx.AsyncClient(http2=True, timeout=timeout) as client: - response = await client.get(url, headers=headers) - print("headers", headers,"response", response, "status", response.json().get("status")) + async with httpx.AsyncClient(http2=True) as client: + response = await client.post(url, json=payload) if response.status_code == 200: data = response.json() if data.get("status") == "fine": - # Здесь можно добавить валидацию через Pydantic, если необходимо - # from app.core.models.user_models import UserRoleData - # user_role_data = UserRoleData(**data['data']) - return True, data['data'] + token_data = data["data"] + # Обновляем сессию с новыми токенами + add_session( + login=None, # Логин не требуется для обновления + access_token=token_data["access_token"], + refresh_token=token_data["refresh_token"], + update_existing=True + ) + return True, token_data else: - return False, data.get("detail", localizer.translate("Неизвестная ошибка ответа")) + return False, data.get("detail", "Unknown error") elif response.status_code == 401: - return False, localizer.translate("Токен недействителен или истек") - elif response.status_code == 404: - return False, localizer.translate("Пользователь не найден") + return False, "Refresh token is invalid or expired" + else: - return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}" + return False, f"Server error: {response.status_code}" except httpx.RequestError as e: - return False, f"{localizer.translate('Ошибка сети')}: {e}" + return False, f"Network error: {e}" except Exception as e: - return False, f"{localizer.translate('Произошла ошибка')}: {e}" + return False, f"An error occurred: {e}" -# Пример использования (для тестирования) -async def main(): - # Замените на реальные данные для теста - success, message = await login("testuser", "testpassword") - print(f"Результат входа: {success}, Сообщение: {message}") +async def get_user_role(access_token: str, login: str): + """ + Получает роль и права пользователя по токену доступа. + В случае истечения срока действия токена, пытается его обновить. + """ + url = f"{config.BASE_URL}/v1/user/role" + headers = {"Authorization": f"Bearer {access_token}"} -if __name__ == "__main__": - asyncio.run(main()) + try: + async with httpx.AsyncClient(http2=True) as client: + response = await client.get(url, headers=headers) + + if response.status_code == 200: + data = response.json() + return (True, data['data']) if data.get("status") == "fine" else (False, data.get("detail", "Unknown error")) + + elif response.status_code == 401: + # Токен истек, пытаемся обновить + session = get_session(login) + if not session or not session['refresh_token']: + return False, "No refresh token found" + + refresh_success, refresh_data = await refresh_token(access_token, session['refresh_token']) + + if refresh_success: + # Повторяем запрос с новым токеном + new_access_token = refresh_data['access_token'] + headers["Authorization"] = f"Bearer {new_access_token}" + response = await client.get(url, headers=headers) + + if response.status_code == 200: + data = response.json() + return (True, data['data']) if data.get("status") == "fine" else (False, "Failed to get role after refresh") + + # Если обновление не удалось, выходим из системы + logout(access_token) + return False, "Session expired, please log in again" + + else: + return False, f"Server error: {response.status_code}" + + except httpx.RequestError as e: + return False, f"Network error: {e}" + except Exception as e: + return False, f"An error occurred: {e}" diff --git a/app/ui/views/yobble_home_view.py b/app/ui/views/yobble_home_view.py index dc44714..63e633c 100644 --- a/app/ui/views/yobble_home_view.py +++ b/app/ui/views/yobble_home_view.py @@ -305,7 +305,7 @@ class YobbleHomeView(QWidget): print("[Permissions] Preload failed: No access token.") return - success, data = await get_user_role(access_token) + success, data = await get_user_role(access_token, self.username) if success: user_permissions = set(data.get("user_permissions", [])) @@ -358,7 +358,7 @@ class YobbleHomeView(QWidget): self.show_error_message(localizer.translate("Сессия не найдена. Пожалуйста, войдите снова.")) return - success, data = await get_user_role(access_token) + success, data = await get_user_role(access_token, self.username) if success and permission_code in data.get("user_permissions", []): # self.permission_cache.add(permission_code) user_permissions = set(data.get("user_permissions", []))