Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/services/auth/users/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.urls import path
from django.urls import include, path

from . import views

Expand All @@ -12,4 +12,5 @@
path('login/', views.LoginUserView.as_view(), name='login'),
path('logout/', views.LogoutUserView.as_view(), name='logout'),
path('csrf/', views.get_csrf_token, name='csrf'),
path('vk/', include("app.services.auth.vk_id.urls")),
]
Empty file.
3 changes: 3 additions & 0 deletions app/services/auth/vk_id/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions app/services/auth/vk_id/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class VkIdConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app.services.auth.vk_id'
Empty file.
3 changes: 3 additions & 0 deletions app/services/auth/vk_id/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
3 changes: 3 additions & 0 deletions app/services/auth/vk_id/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
9 changes: 9 additions & 0 deletions app/services/auth/vk_id/urls.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

нужно либо добавить недостающие функции в app/services/auth/vk_id/views.py, либо убрать эти URL’ы.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path

from app.services.auth.vk_id import views

urlpatterns = [
path("", views.vk_draft_login, name="vk_login"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

вы вызываете views.vk_draft_login, но в views.py этих функций в диффе нет. В результате импорт/роутинг упадут при старте Django (AttributeError при доступе к атрибутам модуля).
Вероятно вы просто забыли добавить эти изменения в коммит.

path("start/", views.vk_start_auth, name="vk_start"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

вы вызываете views.vk_start_auth, но в views.py этих функций в диффе нет. В результате импорт/роутинг упадут при старте Django (AttributeError при доступе к атрибутам модуля).
Вероятно вы просто забыли добавить эти изменения в коммит.

path("callback/", views.vk_auth_callback, name="vk_callback"),
]
137 changes: 137 additions & 0 deletions app/services/auth/vk_id/views.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

отсутствуют vk_start_auth и vk_draft_login, хотя они используются в app/services/auth/vk_id/urls.py (см. выше).

Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import secrets
import requests
from urllib.parse import urlencode
import logging

from django.conf import settings
from django.contrib.auth import get_user_model, login
from django.http import HttpResponse
from django.shortcuts import redirect
from inertia import inertia

User = get_user_model()
logger = logging.getLogger(__name__)


class VKOAuthError(Exception):
"""Custom exception for VK OAuth errors"""
pass


def validate_state(request):
state = request.GET.get("state")
session_state = request.session.get("vk_state")

if not state or state != session_state:
logger.warning(f"Invalid state. Expected: {session_state}, Got: {state}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

validate_state() логирует ожидаемый и пришедший state целиком
state — это анти-CSRF токен. В логах (особенно централизованных) лучше не светить такие значения. Минимум: логировать факт несовпадения без значений, или маскировать (abcd…wxyz).

raise VKOAuthError("Invalid state parameter")

request.session.pop("vk_state", None)
logger.debug("State parameter validated")
return True


def get_access_token(code: str) -> dict:
params = {
"client_id": settings.VK_CLIENT_ID,
"client_secret": settings.VK_CLIENT_SECRET,
"redirect_uri": settings.VK_REDIRECT_URI,
"code": code,
}

try:
res = requests.get("https://oauth.vk.com/access_token", params=params, timeout=10)
res.raise_for_status()
data = res.json()

if "error" in data:
raise VKOAuthError(f"VK token error: {data['error']}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

при error in data выбрасывается только data['error']. У VK должен быть error_description (или аналог), её полезно включать в сообщение, чтобы быстрее понять причину. (Это особенно помогает при mismatch redirect_uri, scope, выключенном приложении и т.д.).

return data

except requests.Timeout:
raise VKOAuthError("Timeout while connecting to VK")
except requests.RequestException as e:
logger.error(f"RequestException during token fetch: {e}")
raise VKOAuthError(f"Failed to get VK access token: {str(e)}")


def get_vk_user_info(user_id: str, access_token: str) -> dict:
params = {
"user_ids": user_id,
"fields": "first_name,last_name",
"access_token": access_token,
"v": "5.131",
}

try:
res = requests.get("https://api.vk.com/method/users.get", params=params, timeout=10)
res.raise_for_status()
data = res.json()

if "error" in data:
raise VKOAuthError(f"VK API error: {data['error'].get('error_msg', 'Unknown error')}")
if not data.get("response"):
raise VKOAuthError("No user data in VK response")

return data["response"][0]

except requests.Timeout:
raise VKOAuthError("Timeout while fetching VK user info")
except requests.RequestException as e:
raise VKOAuthError(f"Failed to fetch VK user info: {str(e)}")


def get_or_create_user(email: str, user_id: str, user_info: dict) -> User:
if not email:
email = f"vk_user_{user_id}@fake.vk.com"
logger.debug(f"Generated fake email: {email}")

user, created = User.objects.get_or_create(
email=email,
defaults={
"username": f"vk_{user_id}",
"first_name": user_info.get("first_name", ""),
"last_name": user_info.get("last_name", ""),
},
)

if created:
user.set_unusable_password()
user.save()
logger.info(f"Created new user: {email}")
else:
logger.debug(f"Existing user found: {email}")

return user


def vk_auth_callback(request):
code = request.GET.get("code")
if not code:
return HttpResponse("Authorization code not provided", status=400)

try:
validate_state(request)
token_data = get_access_token(code)
access_token = token_data.get("access_token")
email = token_data.get("email")
user_id = token_data.get("user_id")

if not access_token or not user_id:
raise VKOAuthError("Missing access_token or user_id from VK")

user_info = get_vk_user_info(user_id, access_token)
user = get_or_create_user(email, user_id, user_info)

login(request, user)
logger.info(f"User {user.email} logged in via VK")

redirect_url = getattr(settings, "LOGIN_REDIRECT_URL", "/")
return inertia.redirect(request, redirect_url)

except VKOAuthError as e:
logger.error(f"VK OAuth error: {str(e)}")
return HttpResponse(str(e), status=400)
except (requests.RequestException, requests.Timeout) as e:
logger.error(f"VK API request failed: {str(e)}")
return HttpResponse(f"VK API error: {str(e)}", status=400)
5 changes: 5 additions & 0 deletions app/settings.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

стиль получения env-переменных сейчас смешанный (os.environ.get и os.getenv, плюс кавычки одинарные/двойные). Это не баг, но лучше привести к одному стилю по проекту для читаемости.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привести к единому стилю и добавить явную проверку/валидацию VK-конфига (или в settings, или в коде перед запросами).

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

INSTALLED_APPS = [
'app.services.auth.users',
'app.services.auth.vk_id',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
Expand Down Expand Up @@ -168,3 +169,7 @@
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
EMAIL_TIMEOUT = int(os.environ.get("EMAIL_TIMEOUT", 10))

VK_CLIENT_ID = os.getenv('VK_CLIENT_ID', '')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

пустые дефолты для критичных VK-полей могут скрывать проблему конфигурации до первого запроса.

VK_CLIENT_SECRET = os.getenv('VK_CLIENT_SECRET', '')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

пустые дефолты для критичных VK-полей могут скрывать проблему конфигурации до первого запроса.

VK_REDIRECT_URI = os.getenv('VK_REDIRECT_URI', '')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

пустые дефолты для критичных VK-полей могут скрывать проблему конфигурации до первого запроса.