|
| 1 | +import base64 |
| 2 | +import hashlib |
1 | 3 | import jwt |
2 | 4 | import requests |
3 | 5 | import urllib.parse |
|
81 | 83 | from .services.cv_data_prepare import UserCVDataPreparerV2 |
82 | 84 | from .schema import USER_PK_PARAM, SKILL_PK_PARAM |
83 | 85 | from .tasks import send_mail_cv |
| 86 | +from .utils import random_bytes_in_hex |
84 | 87 |
|
85 | 88 | User = get_user_model() |
86 | 89 | Project = apps.get_model("projects", "Project") |
@@ -655,3 +658,108 @@ def get(self, request, *args, **kwargs): |
655 | 658 | cache.set(cache_key, timezone.now(), timeout=cooldown_time) |
656 | 659 |
|
657 | 660 | return Response(data={"detail": "success"}, status=status.HTTP_200_OK) |
| 661 | + |
| 662 | + |
| 663 | +class VKIDOauth2View(APIView): |
| 664 | + permission_classes = [AllowAny] |
| 665 | + |
| 666 | + def get(self, request, *args, **kwargs): |
| 667 | + """ |
| 668 | + Генерация state и code_challenge для OAuth2. |
| 669 | + """ |
| 670 | + code_verifier = random_bytes_in_hex(32) |
| 671 | + code_challenge = ( |
| 672 | + base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest()) |
| 673 | + .decode() |
| 674 | + .rstrip("=") |
| 675 | + ) |
| 676 | + state = random_bytes_in_hex(24) |
| 677 | + cache_timeout = 15 * 60 |
| 678 | + cache.set(state, code_verifier, cache_timeout) |
| 679 | + |
| 680 | + return Response( |
| 681 | + { |
| 682 | + "redirect_uri": settings.VKID_REDIRECT_URI, |
| 683 | + "state": state, |
| 684 | + "code_challenge": code_challenge, |
| 685 | + "client_id": settings.VKID_APP_ID, |
| 686 | + "scope": "email", |
| 687 | + }, |
| 688 | + status=status.HTTP_200_OK, |
| 689 | + ) |
| 690 | + |
| 691 | + def post(self, request, *args, **kwargs): |
| 692 | + """ |
| 693 | + Обработка callback после авторизации пользователя. |
| 694 | + """ |
| 695 | + required_fields = ["code", "device_id", "state"] |
| 696 | + data = request.data |
| 697 | + missing_fields = [field for field in required_fields if field not in data] |
| 698 | + if missing_fields: |
| 699 | + return Response( |
| 700 | + {"detail": f"Missing required fields: {', '.join(missing_fields)}"}, |
| 701 | + status=status.HTTP_400_BAD_REQUEST, |
| 702 | + ) |
| 703 | + code_verifier = cache.get(data.get("state")) |
| 704 | + client_id = settings.VKID_APP_ID |
| 705 | + request_data = { |
| 706 | + "code_verifier": code_verifier, |
| 707 | + "code": data.get("code"), |
| 708 | + "device_id": data.get("device_id"), |
| 709 | + "client_id": client_id, |
| 710 | + "redirect_uri": settings.VKID_REDIRECT_URI, |
| 711 | + "grant_type": "authorization_code", |
| 712 | + "scope": "email", |
| 713 | + } |
| 714 | + try: |
| 715 | + token_response = requests.post( |
| 716 | + "https://id.vk.com/oauth2/auth", data=request_data |
| 717 | + ) |
| 718 | + token_response.raise_for_status() |
| 719 | + token_data = token_response.json() |
| 720 | + except requests.RequestException as e: |
| 721 | + return Response( |
| 722 | + {"detail": f"Failed to fetch token: {str(e)}"}, |
| 723 | + status=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 724 | + ) |
| 725 | + |
| 726 | + access_token = token_data.get("access_token") |
| 727 | + if not access_token: |
| 728 | + return Response( |
| 729 | + {"detail": "Access token not provided by VK"}, |
| 730 | + status=status.HTTP_400_BAD_REQUEST, |
| 731 | + ) |
| 732 | + try: |
| 733 | + user_info_response = requests.post( |
| 734 | + "https://id.vk.com/oauth2/user_info", |
| 735 | + data={"access_token": access_token, "client_id": client_id}, |
| 736 | + ) |
| 737 | + user_info_response.raise_for_status() |
| 738 | + user_info = user_info_response.json() |
| 739 | + except requests.RequestException as e: |
| 740 | + return Response( |
| 741 | + {"detail": f"Failed to fetch user info: {str(e)}"}, |
| 742 | + status=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 743 | + ) |
| 744 | + |
| 745 | + user_email = user_info.get("user", {}).get("email") |
| 746 | + if not user_email: |
| 747 | + return Response( |
| 748 | + {"detail": "User email not provided by VK"}, |
| 749 | + status=status.HTTP_400_BAD_REQUEST, |
| 750 | + ) |
| 751 | + try: |
| 752 | + user = User.objects.get(email=user_email) |
| 753 | + except User.DoesNotExist: |
| 754 | + return Response( |
| 755 | + {"error": "User does not exist"}, status=status.HTTP_404_NOT_FOUND |
| 756 | + ) |
| 757 | + access_token = str(RefreshToken.for_user(user).access_token) |
| 758 | + refresh_token = str(RefreshToken.for_user(user)) |
| 759 | + return Response( |
| 760 | + { |
| 761 | + "access": access_token, |
| 762 | + "refresh": refresh_token, |
| 763 | + }, |
| 764 | + status=status.HTTP_200_OK, |
| 765 | + ) |
0 commit comments