From e5bdcea8877b04552741720148842d3837bedbe0 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 12 May 2025 11:54:54 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#50]=20user=20?= =?UTF-8?q?model=20=EC=9C=A0=EC=A0=80=20=EC=83=9D=EC=84=B1=EC=97=90=20?= =?UTF-8?q?=EB=B9=84=EB=B2=88=20=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0(?= =?UTF-8?q?=EC=86=8C=EC=85=9C)=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 9f62dcd..ed643fd 100644 --- a/users/models.py +++ b/users/models.py @@ -11,7 +11,10 @@ from common.models.choiceModels import NotificationType class UserManager(BaseUserManager): def create_user(self, email, password, **kwargs): user = self.model(email = email, **kwargs) - user.set_password(password) + if not password: + user.set_unusable_password() + else: + user.set_password(password) user.save(using=self._db) return user From 651bee0f9c495c05c2c9f80a1b29490c9a4cd325 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 12 May 2025 11:55:46 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#50]=20joinser?= =?UTF-8?q?ializer=EC=97=90=20password=20=ED=95=84=EB=93=9C=20=ED=95=84?= =?UTF-8?q?=EC=88=98=EC=A7=80=EC=A0=95=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/users/serializers.py b/users/serializers.py index 817c753..315224f 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -2,6 +2,7 @@ from .models import * from rest_framework import serializers class JoinSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True, required=False) class Meta: model = User fields = [ @@ -20,7 +21,9 @@ class JoinSerializer(serializers.ModelSerializer): ] def create(self, validated_data): - return User.objects.create_user(**validated_data) + email = validated_data.pop('email', None) + password = validated_data.pop('password', None) + return User.objects.create_user(email=email, password=password, **validated_data) class SetPortofolioRequiredInfoSerializer(serializers.ModelSerializer): class Meta: From 170a044ff5d0f7a93bb2941b4b98b2a39980d30b Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 12 May 2025 11:56:15 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#50]=20social-?= =?UTF-8?q?login=20url=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/users/urls.py b/users/urls.py index 3d84a25..2f386c8 100644 --- a/users/urls.py +++ b/users/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('refresh-token/', RefreshAPIView.as_view()), path('join/', JoinAPIView.as_view()), path('login/', LoginAPIView.as_view()), + path('social-login/', GoogleLoginAPIView.as_view()), path('check/', CheckUserFieldDuplicateAPIView.as_view()), path('portfolio-info/', SetPortofolioRequiredInfoAPIView.as_view()), path('portfolio-info/check/', SetPortofolioRequiredInfoAPIView.as_view()), From 879ee691465abaf99aa278a23b58b9128953ec49 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 12 May 2025 11:56:52 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#50]=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/views.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/users/views.py b/users/views.py index d51fbe5..f8e557e 100644 --- a/users/views.py +++ b/users/views.py @@ -24,6 +24,9 @@ from portfolios.serializers import * from django.db import transaction +from google.oauth2 import id_token +from google.auth.transport import requests + class RefreshAPIView(APIView): permission_classes = [AllowAny] @@ -44,6 +47,65 @@ class RefreshAPIView(APIView): except TokenError as e: return Response({"message": f"Invalid token: {e}"}, status=status.HTTP_401_UNAUTHORIZED) +class GoogleLoginAPIView(APIView): + permission_classes = [AllowAny] + + @transaction.atomic + def post(self, request): + token_str = request.data.get("id_token") + if not token_str: + return Response({"message": "no id_token"}, status=status.HTTP_400_BAD_REQUEST) + + try: + idinfo = id_token.verify_oauth2_token( + token_str, + requests.Request(), + settings.GOOGLE_CLIENT_ID + ) + except ValueError: + return Response({"message": "wrong id_token"}, status=status.HTTP_400_BAD_REQUEST) + + email = idinfo["email"] + # sub = idinfo["sub"] # Google 고유 ID + + # 이미 소셜 회원가입을 한 경우, 로그인 기능. + if user := User.objects.filter(email=email).first(): + # 로그인 전 쿠키에 있는 리프레시 토큰 무효화 + try: + refresh = request.COOKIES.get('refresh') + if refresh: + refresh_token = RefreshToken(refresh) + refresh_token.blacklist() + except TokenError as e: + pass # 어차피 만료된거면 그냥 넘어가기 + + refresh = RefreshToken.for_user(user) + access = str(refresh.access_token) + + res = Response( + { + "message": "login success", + "user_id": user.id, + "nickname": user.nickname, + "access": access, + "is_member": True, + "is_custom_url": user.is_custom_url + }, + status=status.HTTP_200_OK, + ) + res.set_cookie("refresh", str(refresh), httponly=True, samesite=None, secure=not settings.DEBUG) + return res + else: + return Response( + { + "message": "Not member", + "email": email, + "is_member": False + }, + status=status.HTTP_200_OK + ) + + class JoinAPIView(APIView): From 1f7fe5203c9d7531ba5ff007e2f2d9eaea0b9477 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 12 May 2025 12:01:42 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#50]=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/views.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/users/views.py b/users/views.py index d51fbe5..f8e557e 100644 --- a/users/views.py +++ b/users/views.py @@ -24,6 +24,9 @@ from portfolios.serializers import * from django.db import transaction +from google.oauth2 import id_token +from google.auth.transport import requests + class RefreshAPIView(APIView): permission_classes = [AllowAny] @@ -44,6 +47,65 @@ class RefreshAPIView(APIView): except TokenError as e: return Response({"message": f"Invalid token: {e}"}, status=status.HTTP_401_UNAUTHORIZED) +class GoogleLoginAPIView(APIView): + permission_classes = [AllowAny] + + @transaction.atomic + def post(self, request): + token_str = request.data.get("id_token") + if not token_str: + return Response({"message": "no id_token"}, status=status.HTTP_400_BAD_REQUEST) + + try: + idinfo = id_token.verify_oauth2_token( + token_str, + requests.Request(), + settings.GOOGLE_CLIENT_ID + ) + except ValueError: + return Response({"message": "wrong id_token"}, status=status.HTTP_400_BAD_REQUEST) + + email = idinfo["email"] + # sub = idinfo["sub"] # Google 고유 ID + + # 이미 소셜 회원가입을 한 경우, 로그인 기능. + if user := User.objects.filter(email=email).first(): + # 로그인 전 쿠키에 있는 리프레시 토큰 무효화 + try: + refresh = request.COOKIES.get('refresh') + if refresh: + refresh_token = RefreshToken(refresh) + refresh_token.blacklist() + except TokenError as e: + pass # 어차피 만료된거면 그냥 넘어가기 + + refresh = RefreshToken.for_user(user) + access = str(refresh.access_token) + + res = Response( + { + "message": "login success", + "user_id": user.id, + "nickname": user.nickname, + "access": access, + "is_member": True, + "is_custom_url": user.is_custom_url + }, + status=status.HTTP_200_OK, + ) + res.set_cookie("refresh", str(refresh), httponly=True, samesite=None, secure=not settings.DEBUG) + return res + else: + return Response( + { + "message": "Not member", + "email": email, + "is_member": False + }, + status=status.HTTP_200_OK + ) + + class JoinAPIView(APIView): From 28317b820212e641c012cdd914d10cc811647535 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 12 May 2025 12:02:18 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9E=95=20Dependency:=20[#50]=20google-au?= =?UTF-8?q?th=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | Bin 982 -> 1162 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 641927a35550a11fb29bea7fe5f8090ce9d8b5d4..8ae4bec99de507bfdb28500a99150d05f631914c 100644 GIT binary patch delta 182 zcmcb{-o-h=PdZE6CgI4{G2IQxCkhk2sQ;IW(hK7vOjY>06~c%KmY&$ delta 22 ecmeC;yv9DkZ)3?n#>sP-7EJD7&X~->(gXl)s|f1=