BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@@ -11,6 +11,9 @@ from common.models.choiceModels import NotificationType
|
|||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
def create_user(self, email, password, **kwargs):
|
def create_user(self, email, password, **kwargs):
|
||||||
user = self.model(email = email, **kwargs)
|
user = self.model(email = email, **kwargs)
|
||||||
|
if not password:
|
||||||
|
user.set_unusable_password()
|
||||||
|
else:
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from .models import *
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
class JoinSerializer(serializers.ModelSerializer):
|
class JoinSerializer(serializers.ModelSerializer):
|
||||||
|
password = serializers.CharField(write_only=True, required=False)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = [
|
fields = [
|
||||||
@@ -20,7 +21,9 @@ class JoinSerializer(serializers.ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
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 SetPortofolioRequiredInfoSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ urlpatterns = [
|
|||||||
path('refresh-token/', RefreshAPIView.as_view()),
|
path('refresh-token/', RefreshAPIView.as_view()),
|
||||||
path('join/', JoinAPIView.as_view()),
|
path('join/', JoinAPIView.as_view()),
|
||||||
path('login/', LoginAPIView.as_view()),
|
path('login/', LoginAPIView.as_view()),
|
||||||
|
path('social-login/', GoogleLoginAPIView.as_view()),
|
||||||
path('check/', CheckUserFieldDuplicateAPIView.as_view()),
|
path('check/', CheckUserFieldDuplicateAPIView.as_view()),
|
||||||
path('portfolio-info/', SetPortofolioRequiredInfoAPIView.as_view()),
|
path('portfolio-info/', SetPortofolioRequiredInfoAPIView.as_view()),
|
||||||
path('portfolio-info/check/', SetPortofolioRequiredInfoAPIView.as_view()),
|
path('portfolio-info/check/', SetPortofolioRequiredInfoAPIView.as_view()),
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ from portfolios.serializers import *
|
|||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
from google.oauth2 import id_token
|
||||||
|
from google.auth.transport import requests
|
||||||
|
|
||||||
|
|
||||||
class RefreshAPIView(APIView):
|
class RefreshAPIView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
@@ -44,6 +47,65 @@ class RefreshAPIView(APIView):
|
|||||||
except TokenError as e:
|
except TokenError as e:
|
||||||
return Response({"message": f"Invalid token: {e}"}, status=status.HTTP_401_UNAUTHORIZED)
|
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):
|
class JoinAPIView(APIView):
|
||||||
|
|||||||
Reference in New Issue
Block a user