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
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"),
path("start/", views.vk_start_auth, name="vk_start"),
path("callback/", views.vk_auth_callback, name="vk_callback"),
]
137 changes: 137 additions & 0 deletions app/services/auth/vk_id/views.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}")
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']}")
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
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', '')
VK_CLIENT_SECRET = os.getenv('VK_CLIENT_SECRET', '')
VK_REDIRECT_URI = os.getenv('VK_REDIRECT_URI', '')