|
1 |
| -import logging |
2 | 1 | from typing import Any
|
| 2 | +from urllib.request import urlopen |
3 | 3 |
|
4 |
| -from allauth.account import app_settings |
5 |
| -from allauth.account import app_settings as allauth_account_settings |
6 |
| -from allauth.account import app_settings as allauth_settings |
7 |
| -from allauth.account.models import ( |
8 |
| - EmailConfirmation, |
9 |
| - EmailConfirmationHMAC, |
10 |
| - get_emailconfirmation_model, |
11 |
| -) |
12 |
| -from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter |
13 |
| -from allauth.socialaccount.providers.oauth2.client import OAuth2Client |
| 4 | +import requests |
14 | 5 | from dj_rest_auth.app_settings import api_settings
|
15 |
| -from dj_rest_auth.models import get_token_model |
16 |
| -from dj_rest_auth.registration.views import RegisterView, SocialLoginView |
| 6 | +from dj_rest_auth.jwt_auth import set_jwt_cookies |
| 7 | +from dj_rest_auth.registration.views import RegisterView |
17 | 8 | from dj_rest_auth.utils import jwt_encode
|
18 | 9 | from dj_rest_auth.views import LoginView
|
19 | 10 | from django.conf import settings
|
20 | 11 | from django.core.cache import cache
|
21 |
| -from django.http import Http404, HttpResponse, HttpResponseRedirect |
22 |
| -from django.shortcuts import render |
| 12 | +from django.core.files.base import ContentFile |
23 | 13 | from django.utils import timezone
|
24 |
| -from drf_spectacular.utils import ( |
25 |
| - extend_schema, |
26 |
| - extend_schema_serializer, |
27 |
| - extend_schema_view, |
28 |
| - inline_serializer, |
29 |
| -) |
30 |
| -from rest_framework import permissions, serializers, status |
31 |
| -from rest_framework.exceptions import NotFound |
32 |
| -from rest_framework.generics import CreateAPIView |
33 |
| -from rest_framework.permissions import AllowAny |
| 14 | +from drf_spectacular.utils import extend_schema |
| 15 | +from rest_framework import permissions, status |
34 | 16 | from rest_framework.request import Request
|
35 | 17 | from rest_framework.response import Response
|
36 | 18 | from rest_framework.views import APIView
|
37 | 19 |
|
| 20 | +from apps.common.utils import uuid4_generator |
38 | 21 | from apps.user.api_schema import (
|
39 | 22 | ConfirmRequestSchema,
|
40 | 23 | ConfirmResponseSchema,
|
|
49 | 32 | from apps.user.serializers import ConfirmEmailSerializer, SendCodeSerializer
|
50 | 33 | from apps.user.utils import generate_confirmation_code, send_email
|
51 | 34 |
|
52 |
| -# class GoogleLogin(SocialLoginView): |
53 |
| -# adapter_class = GoogleOAuth2Adapter |
54 |
| -# callback_url = api_settings.GOOGLE_OAUTH2_URL |
55 |
| -# client_class = OAuth2Client |
56 |
| - |
57 | 35 |
|
58 | 36 | @extend_schema(request=SignupRequestSchema, responses=SignupResponseSchema)
|
59 | 37 | class CustomSignupView(RegisterView): # type: ignore
|
@@ -125,8 +103,6 @@ def get_response(self) -> Response:
|
125 | 103 | data.pop("user", None)
|
126 | 104 | response = Response(data, status=status.HTTP_200_OK)
|
127 | 105 | if api_settings.USE_JWT:
|
128 |
| - from dj_rest_auth.jwt_auth import set_jwt_cookies |
129 |
| - |
130 | 106 | set_jwt_cookies(response, self.access_token, self.refresh_token)
|
131 | 107 | return response
|
132 | 108 |
|
@@ -174,6 +150,188 @@ def post(self, request: Response, *args: Any, **kwargs: Any) -> Response:
|
174 | 150 | return Response({"message": "Email confirmation successful."}, status=status.HTTP_200_OK)
|
175 | 151 |
|
176 | 152 |
|
| 153 | +class KakaoLoginView(APIView): |
| 154 | + permission_classes = [permissions.AllowAny] |
| 155 | + |
| 156 | + def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: |
| 157 | + code = request.data.get("code") # 프론트에서 보내준 코드 |
| 158 | + # 카카오 oauth 토큰 발급 url로 code가 담긴 post 요청을 보내 응답을 받는다. |
| 159 | + CLIENT_ID = settings.KAKAO_CLIENT_ID |
| 160 | + REDIRECT_URI = settings.REDIRECT_URI |
| 161 | + token_response = requests.post( |
| 162 | + "https://kauth.kakao.com/oauth/token", |
| 163 | + headers={"Content-Type": "application/x-www-form-urlencoded"}, |
| 164 | + data={ |
| 165 | + "grant_type": "authorization_code", |
| 166 | + "code": code, |
| 167 | + "redirect_uri": REDIRECT_URI, |
| 168 | + "client_id": CLIENT_ID, |
| 169 | + }, |
| 170 | + ) |
| 171 | + |
| 172 | + if token_response.status_code != status.HTTP_200_OK: |
| 173 | + return Response( |
| 174 | + {"msg": "카카오 서버로 부터 토큰을 받아오는데 실패하였습니다."}, |
| 175 | + status=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 176 | + ) |
| 177 | + # 응답으로부터 액세스 토큰을 가져온다. |
| 178 | + access_token = token_response.json().get("access_token") |
| 179 | + response = requests.get( |
| 180 | + "https://kapi.kakao.com/v2/user/me", |
| 181 | + headers={ |
| 182 | + "Authorization": f"Bearer {access_token}", |
| 183 | + "Content-type": "application/x-www-form-urlencoded;charset=utf-8", |
| 184 | + }, |
| 185 | + ) |
| 186 | + |
| 187 | + if response.status_code != status.HTTP_200_OK: |
| 188 | + return Response( |
| 189 | + {"msg": "카카오 서버로 부터 프로필 데이터를 받아오는데 실패하였습니다."}, |
| 190 | + status=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 191 | + ) |
| 192 | + response_data = response.json() |
| 193 | + kakao_account = response_data["kakao_account"] |
| 194 | + profile = kakao_account.get("profile") |
| 195 | + requests.post("https://kapi.kakao.com/v1/user/logout", headers={"Authorization": f"Bearer {access_token}"}) |
| 196 | + try: |
| 197 | + user = Account.objects.get(email=kakao_account.get("email")) |
| 198 | + access_token, refresh_token = jwt_encode(user) |
| 199 | + response = Response( |
| 200 | + { |
| 201 | + "access": str(access_token), |
| 202 | + "refresh": str(refresh_token), # type: ignore |
| 203 | + "email": user.email, |
| 204 | + "nickname": user.nickname, |
| 205 | + "profile_image": user.profile_img.url, |
| 206 | + }, |
| 207 | + status=status.HTTP_200_OK, |
| 208 | + ) |
| 209 | + # set_jwt_cookies(response, access_token, refresh_token) |
| 210 | + return response # type: ignore |
| 211 | + |
| 212 | + except Account.DoesNotExist: |
| 213 | + # 이미지를 다운로드하여 파일 객체로 가져옴 |
| 214 | + image_response = urlopen(profile.get("profile_image_url")) |
| 215 | + image_content = image_response.read() |
| 216 | + kakao_profile_image = ContentFile(image_content, name=f"kakao-profile-{uuid4_generator(8)}.jpg") |
| 217 | + user = Account.objects.create( |
| 218 | + email=kakao_account.get("email"), |
| 219 | + nickname=profile.get("nickname"), |
| 220 | + profile_img=kakao_profile_image, |
| 221 | + ) |
| 222 | + user.set_unusable_password() |
| 223 | + access_token, refresh_token = jwt_encode(user) |
| 224 | + response = Response( |
| 225 | + { |
| 226 | + "access": str(access_token), |
| 227 | + "refresh": str(refresh_token), # type: ignore |
| 228 | + "email": user.email, |
| 229 | + "nickname": user.nickname, |
| 230 | + "profile_image": user.profile_img.url, |
| 231 | + }, |
| 232 | + status=status.HTTP_200_OK, |
| 233 | + ) |
| 234 | + # set_jwt_cookies(response, access_token, refresh_token) |
| 235 | + return response # type: ignore |
| 236 | + except Exception as e: |
| 237 | + return Response({"msg": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) |
| 238 | + |
| 239 | + |
| 240 | +class GoogleLoginView(APIView): |
| 241 | + permission_classes = [permissions.AllowAny] |
| 242 | + |
| 243 | + def post(self, request: Request) -> Response: |
| 244 | + code = request.data.get("code") |
| 245 | + |
| 246 | + client_id = settings.GOOGLE_CLIENT_ID |
| 247 | + client_secret = settings.GOOGLE_SECRET |
| 248 | + redirect_uri = settings.REDIRECT_URI |
| 249 | + |
| 250 | + if not code: |
| 251 | + return Response({"msg": "인가코드가 필요합니다."}, status=status.HTTP_400_BAD_REQUEST) |
| 252 | + |
| 253 | + # 인가코드를 통해 토큰을 가져오는 요청 |
| 254 | + token_req = requests.post( |
| 255 | + # f"https://oauth2.googleapis.com/token?client_id={client_id}&client_secret={client_secret}&code={code}&grant_type=authorization_code&redirect_uri={redirect_uri}" |
| 256 | + "https://oauth2.googleapis.com/token", |
| 257 | + headers={ |
| 258 | + "Content-type": "application/x-www-form-urlencoded;charset=utf-8", |
| 259 | + }, |
| 260 | + data={ |
| 261 | + "client_id": client_id, |
| 262 | + "client_secret": client_secret, |
| 263 | + "code": code, |
| 264 | + "grant_type": "authorization_code", |
| 265 | + "redirect_uri": redirect_uri |
| 266 | + }, |
| 267 | + ) |
| 268 | + # 요청의 응답을 json 파싱 |
| 269 | + token_req_json = token_req.json() |
| 270 | + if token_req_json.status_code != 200: |
| 271 | + return Response({"msg": token_req_json.get("error")}, status=status.HTTP_400_BAD_REQUEST) |
| 272 | + # 파싱된 데이터중 액세스 토큰을 가져옴 |
| 273 | + google_access_token = token_req_json.get("access_token") |
| 274 | + |
| 275 | + # 가져온 액세스토큰을 통해 사용자 정보에 접근하는 요청 |
| 276 | + info_response = requests.get( |
| 277 | + f"https://www.googleapis.com/oauth2/v1/userinfo?access_token={google_access_token}" |
| 278 | + ) |
| 279 | + |
| 280 | + # 상태코드로 요청이 실패했는지 확인 |
| 281 | + if info_response.status_code != 200: |
| 282 | + return Response( |
| 283 | + {"message": "구글 api로부터 액세스토큰을 받아오는데 실패했습니다."}, |
| 284 | + status=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 285 | + ) |
| 286 | + |
| 287 | + # 요청의 응답을 json 파싱 |
| 288 | + res_json = info_response.json() |
| 289 | + # 파싱된 데이터중 이메일값을 가져옴 |
| 290 | + email = res_json.get("email") |
| 291 | + # 파싱된 데이터중 닉네임을 가져옴 |
| 292 | + nickname = res_json.get("nickname") |
| 293 | + try: |
| 294 | + user = Account.objects.get(email=email) |
| 295 | + access_token, refresh_token = jwt_encode(user) |
| 296 | + response_data = { |
| 297 | + "access": str(access_token), |
| 298 | + "refresh": str(refresh_token), |
| 299 | + "email": user.email, |
| 300 | + "nickname": user.nickname |
| 301 | + } |
| 302 | + if user.profile_img: |
| 303 | + response_data["profile_image"] = user.profile_img.url |
| 304 | + response = Response(response_data, status=status.HTTP_200_OK) |
| 305 | + # if api_settings.USE_JWT: |
| 306 | + # set_jwt_cookies(response, access_token, refresh_token) |
| 307 | + return response |
| 308 | + except Account.DoesNotExist: |
| 309 | + # 파싱된 데이터에서 프로필 이미지 url을 가져와서 파일로 변환 |
| 310 | + image_response = urlopen(res_json.get("picture")) |
| 311 | + image_content = image_response.read() |
| 312 | + google_profile_image = ContentFile(image_content, name=f"google-profile-{uuid4_generator(8)}.jpg") |
| 313 | + # 가져온 이메일, 닉네임, 프로필 이미지를 통해 유저 생성 |
| 314 | + user = Account.objects.create(email=email, nickname=nickname, profile_img=google_profile_image) |
| 315 | + user.set_unusable_password() |
| 316 | + access_token, refresh_token = jwt_encode(user) |
| 317 | + response_data = { |
| 318 | + "access": str(access_token), |
| 319 | + "refresh": str(refresh_token), |
| 320 | + "email": user.email, |
| 321 | + "nickname": user.nickname |
| 322 | + } |
| 323 | + if user.profile_img: |
| 324 | + response_data["profile_image"] = user.profile_img.url |
| 325 | + response = Response(response_data, status=status.HTTP_200_OK) |
| 326 | + # if api_settings.USE_JWT: |
| 327 | + # set_jwt_cookies(response, access_token, refresh_token) |
| 328 | + return response |
| 329 | + |
| 330 | + except Exception as e: |
| 331 | + # 가입이 필요한 회원 |
| 332 | + return Response({"msg": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) |
| 333 | + |
| 334 | + |
177 | 335 | # class CustomConfirmEmailView(APIView):
|
178 | 336 | # permission_classes = [AllowAny]
|
179 | 337 | #
|
|
0 commit comments