### Fixed - 🔒 **OAuth Facebook**: Обновлена версия API с v13.0 до v18.0 (актуальная) - 🔒 **OAuth Facebook**: Добавлены обязательные scope и параметры безопасности - 🔒 **OAuth Facebook**: Улучшена обработка ошибок API и валидация ответов - 🔒 **OAuth VK**: Обновлена версия API с v5.131 до v5.199+ (актуальная) - 🔒 **OAuth VK**: Исправлен endpoint с `authors.get` на `users.get` - 🔒 **OAuth GitHub**: Добавлены обязательные scope `read:user user:email` - 🔒 **OAuth GitHub**: Улучшена обработка ошибок и получения email адресов - 🔒 **OAuth Google**: Добавлены обязательные scope для OpenID Connect - 🔒 **OAuth X/Twitter**: Исправлен endpoint с `authors/me` на `users/me` - 🔒 **Session Cookies**: Автоматическое определение HTTPS через переменную окружения HTTPS_ENABLED - 🏷️ **Type Safety**: Исправлена ошибка в OAuth регистрации провайдеров
This commit is contained in:
148
auth/oauth.py
148
auth/oauth.py
@@ -78,16 +78,23 @@ OAUTH_STATE_TTL = 600 # 10 минут
|
||||
PROVIDER_CONFIGS = {
|
||||
"google": {
|
||||
"server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"client_kwargs": {
|
||||
"scope": "openid email profile",
|
||||
},
|
||||
},
|
||||
"github": {
|
||||
"access_token_url": "https://github.com/login/oauth/access_token",
|
||||
"authorize_url": "https://github.com/login/oauth/authorize",
|
||||
"api_base_url": "https://api.github.com/",
|
||||
"client_kwargs": {
|
||||
"scope": "read:user user:email",
|
||||
},
|
||||
},
|
||||
"facebook": {
|
||||
"access_token_url": "https://graph.facebook.com/v13.0/oauth/access_token",
|
||||
"authorize_url": "https://www.facebook.com/v13.0/dialog/oauth",
|
||||
"access_token_url": "https://graph.facebook.com/v18.0/oauth/access_token",
|
||||
"authorize_url": "https://www.facebook.com/v18.0/dialog/oauth",
|
||||
"api_base_url": "https://graph.facebook.com/",
|
||||
"scope": "email public_profile", # Явно указываем необходимые scope
|
||||
},
|
||||
"x": {
|
||||
"access_token_url": "https://api.twitter.com/2/oauth2/token",
|
||||
@@ -102,6 +109,9 @@ PROVIDER_CONFIGS = {
|
||||
"access_token_url": "https://oauth.vk.com/access_token",
|
||||
"authorize_url": "https://oauth.vk.com/authorize",
|
||||
"api_base_url": "https://api.vk.com/method/",
|
||||
"client_kwargs": {
|
||||
"scope": "email", # Минимальный scope для получения email
|
||||
},
|
||||
},
|
||||
"yandex": {
|
||||
"access_token_url": "https://oauth.yandex.ru/token",
|
||||
@@ -128,13 +138,27 @@ def _register_oauth_provider(provider: str, client_config: dict) -> None:
|
||||
return
|
||||
|
||||
# Базовые параметры для всех провайдеров
|
||||
register_params = {
|
||||
register_params: dict[str, Any] = {
|
||||
"name": provider,
|
||||
"client_id": client_config["id"],
|
||||
"client_secret": client_config["key"],
|
||||
**provider_config,
|
||||
}
|
||||
|
||||
# Добавляем конфигурацию провайдера с явной типизацией
|
||||
if isinstance(provider_config, dict):
|
||||
register_params.update(provider_config)
|
||||
|
||||
# 🔒 Для Facebook добавляем дополнительные параметры безопасности
|
||||
if provider == "facebook":
|
||||
register_params.update(
|
||||
{
|
||||
"client_kwargs": {
|
||||
"scope": "email public_profile",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
oauth.register(**register_params)
|
||||
logger.info(f"OAuth provider {provider} registered successfully")
|
||||
except Exception as e:
|
||||
@@ -174,51 +198,101 @@ PROVIDER_HANDLERS = {
|
||||
|
||||
async def _fetch_github_profile(client: Any, token: Any) -> dict:
|
||||
"""Получает профиль из GitHub API"""
|
||||
profile = await client.get("user", token=token)
|
||||
profile_data = profile.json()
|
||||
emails = await client.get("user/emails", token=token)
|
||||
emails_data = emails.json()
|
||||
primary_email = next((email["email"] for email in emails_data if email["primary"]), None)
|
||||
return {
|
||||
"id": str(profile_data["id"]),
|
||||
"email": primary_email or profile_data.get("email"),
|
||||
"name": profile_data.get("name") or profile_data.get("login"),
|
||||
"picture": profile_data.get("avatar_url"),
|
||||
}
|
||||
try:
|
||||
# Получаем основной профиль
|
||||
profile = await client.get("user", token=token)
|
||||
profile_data = profile.json()
|
||||
|
||||
# Проверяем наличие ошибок в ответе GitHub
|
||||
if "message" in profile_data:
|
||||
logger.error(f"GitHub API error: {profile_data['message']}")
|
||||
return {}
|
||||
|
||||
# Получаем email адреса (требует scope user:email)
|
||||
emails = await client.get("user/emails", token=token)
|
||||
emails_data = emails.json()
|
||||
|
||||
# Ищем основной email
|
||||
primary_email = None
|
||||
if isinstance(emails_data, list):
|
||||
primary_email = next((email["email"] for email in emails_data if email.get("primary")), None)
|
||||
|
||||
return {
|
||||
"id": str(profile_data.get("id", "")),
|
||||
"email": primary_email or profile_data.get("email"),
|
||||
"name": profile_data.get("name") or profile_data.get("login", ""),
|
||||
"picture": profile_data.get("avatar_url"),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching GitHub profile: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
async def _fetch_facebook_profile(client: Any, token: Any) -> dict:
|
||||
"""Получает профиль из Facebook API"""
|
||||
profile = await client.get("me?fields=id,name,email,picture.width(600)", token=token)
|
||||
profile_data = profile.json()
|
||||
return {
|
||||
"id": profile_data["id"],
|
||||
"email": profile_data.get("email"),
|
||||
"name": profile_data.get("name"),
|
||||
"picture": profile_data.get("picture", {}).get("data", {}).get("url"),
|
||||
}
|
||||
try:
|
||||
# Используем актуальную версию API v18.0+ и расширенные поля
|
||||
profile = await client.get("me?fields=id,name,email,picture.width(600).height(600)", token=token)
|
||||
profile_data = profile.json()
|
||||
|
||||
# Проверяем наличие ошибок в ответе Facebook
|
||||
if "error" in profile_data:
|
||||
logger.error(f"Facebook API error: {profile_data['error']}")
|
||||
return {}
|
||||
|
||||
return {
|
||||
"id": str(profile_data.get("id", "")),
|
||||
"email": profile_data.get("email"), # Может быть None если не предоставлен
|
||||
"name": profile_data.get("name", ""),
|
||||
"picture": profile_data.get("picture", {}).get("data", {}).get("url"),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching Facebook profile: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
async def _fetch_x_profile(client: Any, token: Any) -> dict:
|
||||
"""Получает профиль из X (Twitter) API"""
|
||||
profile = await client.get("authors/me?user.fields=id,name,username,profile_image_url", token=token)
|
||||
profile_data = profile.json()
|
||||
return PROVIDER_HANDLERS["x"](token, profile_data)
|
||||
try:
|
||||
# Используем правильный endpoint для X API v2
|
||||
profile = await client.get("users/me?user.fields=id,name,username,profile_image_url", token=token)
|
||||
profile_data = profile.json()
|
||||
|
||||
# Проверяем наличие ошибок в ответе X
|
||||
if "errors" in profile_data:
|
||||
logger.error(f"X API error: {profile_data['errors']}")
|
||||
return {}
|
||||
|
||||
return PROVIDER_HANDLERS["x"](token, profile_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching X profile: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
async def _fetch_vk_profile(client: Any, token: Any) -> dict:
|
||||
"""Получает профиль из VK API"""
|
||||
profile = await client.get("authors.get?fields=photo_400_orig,contacts&v=5.131", token=token)
|
||||
profile_data = profile.json()
|
||||
if profile_data.get("response"):
|
||||
user_data = profile_data["response"][0]
|
||||
return {
|
||||
"id": str(user_data["id"]),
|
||||
"email": user_data.get("contacts", {}).get("email"),
|
||||
"name": f"{user_data.get('first_name', '')} {user_data.get('last_name', '')}".strip(),
|
||||
"picture": user_data.get("photo_400_orig"),
|
||||
}
|
||||
return {}
|
||||
try:
|
||||
# Используем актуальную версию API v5.199+
|
||||
profile = await client.get("users.get?fields=photo_400_orig,contacts&v=5.199", token=token)
|
||||
profile_data = profile.json()
|
||||
|
||||
# Проверяем наличие ошибок в ответе VK
|
||||
if "error" in profile_data:
|
||||
logger.error(f"VK API error: {profile_data['error']}")
|
||||
return {}
|
||||
|
||||
if profile_data.get("response"):
|
||||
user_data = profile_data["response"][0]
|
||||
return {
|
||||
"id": str(user_data["id"]),
|
||||
"email": user_data.get("contacts", {}).get("email"),
|
||||
"name": f"{user_data.get('first_name', '')} {user_data.get('last_name', '')}".strip(),
|
||||
"picture": user_data.get("photo_400_orig"),
|
||||
}
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching VK profile: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
async def _fetch_yandex_profile(client: Any, token: Any) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user