[0.9.22] - 2025-09-22
All checks were successful
Deploy on push / deploy (push) Successful in 9m43s

### 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:
2025-09-22 23:56:04 +03:00
parent a4411e3c86
commit d87c0c522c
4 changed files with 157 additions and 43 deletions

View File

@@ -1,5 +1,20 @@
# Changelog
## [0.9.22] - 2025-09-22
### 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 регистрации провайдеров
## [0.9.21] - 2025-09-21
### 📚 Documentation Updates

View File

@@ -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:

View File

@@ -151,13 +151,16 @@ async def get_oauth_state(state: str) -> Optional[dict]:
GOOGLE_OAUTH_CONFIG = {
"client_id": os.getenv("GOOGLE_CLIENT_ID"),
"client_secret": os.getenv("GOOGLE_CLIENT_SECRET"),
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"user_info_url": "https://www.googleapis.com/oauth2/v2/userinfo",
"server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
"scope": "openid email profile"
}
```
**✅ Преимущества OpenID Connect:**
- Автоматическое обнаружение endpoints через `.well-known/openid-configuration`
- Поддержка актуальных стандартов безопасности
- Автоматические обновления при изменениях Google API
### GitHub OAuth
```python
GITHUB_OAUTH_CONFIG = {
@@ -170,6 +173,11 @@ GITHUB_OAUTH_CONFIG = {
}
```
**⚠️ Важные требования GitHub:**
- Scope `user:email` **обязателен** для получения email адреса
- Проверяйте rate limits (5000 запросов/час для авторизованных пользователей)
- Используйте `User-Agent` header во всех запросах к API
### Facebook OAuth
```python
FACEBOOK_OAUTH_CONFIG = {
@@ -178,10 +186,17 @@ FACEBOOK_OAUTH_CONFIG = {
"auth_url": "https://www.facebook.com/v18.0/dialog/oauth",
"token_url": "https://graph.facebook.com/v18.0/oauth/access_token",
"user_info_url": "https://graph.facebook.com/v18.0/me",
"scope": "email public_profile"
"scope": "email public_profile",
"token_endpoint_auth_method": "client_secret_post" # Требование Facebook
}
```
**⚠️ Важные требования Facebook:**
- Используйте **минимум API v18.0**
- Обязательно настройте **точные Redirect URIs** в Facebook App
- Приложение должно быть в режиме **"Live"** для работы с реальными пользователями
- **HTTPS обязателен** для production окружения
### VK OAuth
```python
VK_OAUTH_CONFIG = {
@@ -190,7 +205,16 @@ VK_OAUTH_CONFIG = {
"auth_url": "https://oauth.vk.com/authorize",
"token_url": "https://oauth.vk.com/access_token",
"user_info_url": "https://api.vk.com/method/users.get",
"scope": "email"
"scope": "email",
"api_version": "5.199" # Актуальная версия API
}
```
**⚠️ Важные требования VK:**
- Используйте **API версию 5.199+** (5.131 устарела)
- Scope `email` необходим для получения email адреса
- Redirect URI должен **точно совпадать** с настройками в приложении VK
- Поддерживаются только HTTPS redirect URI в production
}
```

View File

@@ -82,7 +82,8 @@ JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(environ.get("JWT_REFRESH_TOKEN_EXPIRE_DAYS",
# Настройки для HTTP cookies (используется в auth middleware)
SESSION_COOKIE_NAME = "session_token"
SESSION_COOKIE_SECURE = True # Включаем для HTTPS
# 🔒 Автоматически определяем HTTPS на основе окружения
SESSION_COOKIE_SECURE = os.getenv("HTTPS_ENABLED", "true").lower() in ["true", "1", "yes"]
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE: Literal["lax", "strict", "none"] = "lax"
SESSION_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 # 30 дней