diff --git a/README.md b/README.md index 7ad5fcd..4d60ff6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,14 @@ # msa-django-auth -mkdir -p wheelhouse -pip download -r requirements.txt -d wheelhouse/ +## dev env +### auth + +```bash +gunicorn auth_prj.wsgi:application --bind 0.0.0.0:8000 --workers 3 +``` + +### blog + +```bash +gunicorn auth_prj.wsgi:application --bind 0.0.0.0:8800 --workers 3 +``` \ No newline at end of file diff --git a/users/_unused_views.py b/users/_unused_views.py index 2426b25..c108917 100644 --- a/users/_unused_views.py +++ b/users/_unused_views.py @@ -1,19 +1,31 @@ # views.py +import logging +from opentelemetry import trace # OpenTelemetry 임포트 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework_simplejwt.views import TokenObtainPairView - from .serializers import RegisterSerializer, CustomTokenObtainPairSerializer +logger = logging.getLogger(__name__) +tracer = trace.get_tracer(__name__) # 트레이서 가져오기 + +def get_request_info(request): + ip = request.META.get("REMOTE_ADDR", "unknown") + ua = request.META.get("HTTP_USER_AGENT", "unknown") + email = getattr(request.user, "email", "anonymous") + return email, ip, ua class RegisterView(APIView): def post(self, request): + email, ip, ua = get_request_info(request) serializer = RegisterSerializer(data=request.data) if serializer.is_valid(): user = serializer.save() + logger.info(f"[REGISTER] user={user.email} | status=success | IP={ip} | UA={ua}") return Response({"message": "User registered successfully."}, status=status.HTTP_201_CREATED) + logger.warning(f"[REGISTER] user={email} | status=fail | IP={ip} | UA={ua} | detail={serializer.errors}") return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -21,52 +33,74 @@ class MeView(APIView): permission_classes = [IsAuthenticated] def get(self, request): - user = request.user - serializer = RegisterSerializer(user) + email, ip, ua = get_request_info(request) + logger.debug(f"[ME GET] user={email} | IP={ip} | UA={ua}") + serializer = RegisterSerializer(request.user) return Response(serializer.data) def put(self, request): - user = request.user - serializer = RegisterSerializer(user, data=request.data, partial=True) + email, ip, ua = get_request_info(request) + serializer = RegisterSerializer(request.user, data=request.data, partial=True) if serializer.is_valid(): serializer.save() + logger.info(f"[ME UPDATE] user={email} | status=success | IP={ip} | UA={ua}") return Response(serializer.data) + logger.warning(f"[ME UPDATE] user={email} | status=fail | IP={ip} | UA={ua} | detail={serializer.errors}") return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class CustomTokenObtainPairView(TokenObtainPairView): serializer_class = CustomTokenObtainPairSerializer + def post(self, request, *args, **kwargs): + ip = request.META.get("REMOTE_ADDR", "unknown") + ua = request.META.get("HTTP_USER_AGENT", "unknown") + email = request.data.get("email", "unknown") + logger.info(f"[LOGIN] user={email} | status=attempt | IP={ip} | UA={ua}") + response = super().post(request, *args, **kwargs) + + # 성공 실패 판단 + if response.status_code == 200: + logger.info(f"[LOGIN] user={email} | status=success | IP={ip} | UA={ua}") + else: + logger.warning(f"[LOGIN] user={email} | status=fail | IP={ip} | UA={ua} | detail={response.data}") + return response + class SSHKeyUploadView(APIView): permission_classes = [IsAuthenticated] def post(self, request): + email, ip, ua = get_request_info(request) private_key = request.data.get("private_key") key_name = request.data.get("key_name") if not private_key or not key_name: + logger.warning(f"[SSH UPLOAD] user={email} | status=fail | reason=missing_key_or_name | IP={ip} | UA={ua}") return Response( {"error": "private_key와 key_name 모두 필요합니다."}, status=status.HTTP_400_BAD_REQUEST ) - user = request.user try: - # ✅ 모델 메서드로 암호화 저장 처리 + user = request.user user.save_private_key(private_key) user.encrypted_private_key_name = key_name user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name"]) + logger.info(f"[SSH UPLOAD] user={email} | status=success | key_name={key_name} | IP={ip} | UA={ua}") return Response({"message": "SSH key 저장 완료."}, status=201) except Exception as e: + logger.exception(f"[SSH UPLOAD] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}") return Response({"error": f"암호화 또는 저장 실패: {str(e)}"}, status=500) def delete(self, request): + email, ip, ua = get_request_info(request) user = request.user user.encrypted_private_key = None user.encrypted_private_key_name = None user.last_used_at = None user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name", "last_used_at"]) + logger.info(f"[SSH DELETE] user={email} | status=success | IP={ip} | UA={ua}") return Response({"message": "SSH key deleted."}, status=200) @@ -74,6 +108,8 @@ class SSHKeyInfoView(APIView): permission_classes = [IsAuthenticated] def get(self, request): + email, ip, ua = get_request_info(request) + logger.debug(f"[SSH INFO] user={email} | IP={ip} | UA={ua}") user = request.user return Response({ "has_key": bool(user.encrypted_private_key), @@ -86,13 +122,16 @@ class SSHKeyRetrieveView(APIView): permission_classes = [IsAuthenticated] def get(self, request): + email, ip, ua = get_request_info(request) user = request.user if not user.encrypted_private_key: + logger.warning(f"[SSH RETRIEVE] user={email} | status=fail | reason=not_found | IP={ip} | UA={ua}") return Response({"error": "SSH 키가 등록되어 있지 않습니다."}, status=404) try: - # ✅ 모델 메서드로 복호화 처리 decrypted_key = user.decrypt_private_key() + logger.info(f"[SSH RETRIEVE] user={email} | status=success | IP={ip} | UA={ua}") return Response({"ssh_key": decrypted_key}) except Exception as e: + logger.exception(f"[SSH RETRIEVE] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}") return Response({"error": f"복호화 실패: {str(e)}"}, status=500) diff --git a/users/views.py b/users/views.py index 824d73b..044e7fd 100644 --- a/users/views.py +++ b/users/views.py @@ -1,5 +1,6 @@ # views.py import logging +from opentelemetry import trace # ✅ OpenTelemetry 트레이서 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status @@ -8,6 +9,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView from .serializers import RegisterSerializer, CustomTokenObtainPairSerializer logger = logging.getLogger(__name__) +tracer = trace.get_tracer(__name__) # ✅ 트레이서 생성 def get_request_info(request): ip = request.META.get("REMOTE_ADDR", "unknown") @@ -17,119 +19,121 @@ def get_request_info(request): class RegisterView(APIView): def post(self, request): - email, ip, ua = get_request_info(request) - serializer = RegisterSerializer(data=request.data) - if serializer.is_valid(): - user = serializer.save() - logger.info(f"[REGISTER] user={user.email} | status=success | IP={ip} | UA={ua}") - return Response({"message": "User registered successfully."}, status=status.HTTP_201_CREATED) - logger.warning(f"[REGISTER] user={email} | status=fail | IP={ip} | UA={ua} | detail={serializer.errors}") - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - + with tracer.start_as_current_span("RegisterView POST"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + serializer = RegisterSerializer(data=request.data) + if serializer.is_valid(): + user = serializer.save() + logger.info(f"[REGISTER] user={user.email} | status=success | IP={ip} | UA={ua}") + return Response({"message": "User registered successfully."}, status=status.HTTP_201_CREATED) + logger.warning(f"[REGISTER] user={email} | status=fail | IP={ip} | UA={ua} | detail={serializer.errors}") + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class MeView(APIView): permission_classes = [IsAuthenticated] def get(self, request): - email, ip, ua = get_request_info(request) - logger.debug(f"[ME GET] user={email} | IP={ip} | UA={ua}") - serializer = RegisterSerializer(request.user) - return Response(serializer.data) + with tracer.start_as_current_span("MeView GET"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + logger.debug(f"[ME GET] user={email} | IP={ip} | UA={ua}") + serializer = RegisterSerializer(request.user) + return Response(serializer.data) def put(self, request): - email, ip, ua = get_request_info(request) - serializer = RegisterSerializer(request.user, data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - logger.info(f"[ME UPDATE] user={email} | status=success | IP={ip} | UA={ua}") - return Response(serializer.data) - logger.warning(f"[ME UPDATE] user={email} | status=fail | IP={ip} | UA={ua} | detail={serializer.errors}") - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - + with tracer.start_as_current_span("MeView PUT"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + serializer = RegisterSerializer(request.user, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + logger.info(f"[ME UPDATE] user={email} | status=success | IP={ip} | UA={ua}") + return Response(serializer.data) + logger.warning(f"[ME UPDATE] user={email} | status=fail | IP={ip} | UA={ua} | detail={serializer.errors}") + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class CustomTokenObtainPairView(TokenObtainPairView): serializer_class = CustomTokenObtainPairSerializer def post(self, request, *args, **kwargs): - ip = request.META.get("REMOTE_ADDR", "unknown") - ua = request.META.get("HTTP_USER_AGENT", "unknown") - email = request.data.get("email", "unknown") - logger.info(f"[LOGIN] user={email} | status=attempt | IP={ip} | UA={ua}") - response = super().post(request, *args, **kwargs) - - # 성공 실패 판단 - if response.status_code == 200: - logger.info(f"[LOGIN] user={email} | status=success | IP={ip} | UA={ua}") - else: - logger.warning(f"[LOGIN] user={email} | status=fail | IP={ip} | UA={ua} | detail={response.data}") - return response + with tracer.start_as_current_span("TokenObtainPairView POST"): # ✅ Span 생성 + ip = request.META.get("REMOTE_ADDR", "unknown") + ua = request.META.get("HTTP_USER_AGENT", "unknown") + email = request.data.get("email", "unknown") + logger.info(f"[LOGIN] user={email} | status=attempt | IP={ip} | UA={ua}") + response = super().post(request, *args, **kwargs) + if response.status_code == 200: + logger.info(f"[LOGIN] user={email} | status=success | IP={ip} | UA={ua}") + else: + logger.warning(f"[LOGIN] user={email} | status=fail | IP={ip} | UA={ua} | detail={response.data}") + return response class SSHKeyUploadView(APIView): permission_classes = [IsAuthenticated] def post(self, request): - email, ip, ua = get_request_info(request) - private_key = request.data.get("private_key") - key_name = request.data.get("key_name") + with tracer.start_as_current_span("SSHKeyUploadView POST"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + private_key = request.data.get("private_key") + key_name = request.data.get("key_name") - if not private_key or not key_name: - logger.warning(f"[SSH UPLOAD] user={email} | status=fail | reason=missing_key_or_name | IP={ip} | UA={ua}") - return Response( - {"error": "private_key와 key_name 모두 필요합니다."}, - status=status.HTTP_400_BAD_REQUEST - ) + if not private_key or not key_name: + logger.warning(f"[SSH UPLOAD] user={email} | status=fail | reason=missing_key_or_name | IP={ip} | UA={ua}") + return Response( + {"error": "private_key와 key_name 모두 필요합니다."}, + status=status.HTTP_400_BAD_REQUEST + ) - try: - user = request.user - user.save_private_key(private_key) - user.encrypted_private_key_name = key_name - user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name"]) - logger.info(f"[SSH UPLOAD] user={email} | status=success | key_name={key_name} | IP={ip} | UA={ua}") - return Response({"message": "SSH key 저장 완료."}, status=201) - except Exception as e: - logger.exception(f"[SSH UPLOAD] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}") - return Response({"error": f"암호화 또는 저장 실패: {str(e)}"}, status=500) + try: + user = request.user + user.save_private_key(private_key) + user.encrypted_private_key_name = key_name + user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name"]) + logger.info(f"[SSH UPLOAD] user={email} | status=success | key_name={key_name} | IP={ip} | UA={ua}") + return Response({"message": "SSH key 저장 완료."}, status=201) + except Exception as e: + logger.exception(f"[SSH UPLOAD] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}") + return Response({"error": f"암호화 또는 저장 실패: {str(e)}"}, status=500) def delete(self, request): - email, ip, ua = get_request_info(request) - user = request.user - user.encrypted_private_key = None - user.encrypted_private_key_name = None - user.last_used_at = None - user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name", "last_used_at"]) - logger.info(f"[SSH DELETE] user={email} | status=success | IP={ip} | UA={ua}") - return Response({"message": "SSH key deleted."}, status=200) - + with tracer.start_as_current_span("SSHKeyUploadView DELETE"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + user = request.user + user.encrypted_private_key = None + user.encrypted_private_key_name = None + user.last_used_at = None + user.save(update_fields=["encrypted_private_key", "encrypted_private_key_name", "last_used_at"]) + logger.info(f"[SSH DELETE] user={email} | status=success | IP={ip} | UA={ua}") + return Response({"message": "SSH key deleted."}, status=200) class SSHKeyInfoView(APIView): permission_classes = [IsAuthenticated] def get(self, request): - email, ip, ua = get_request_info(request) - logger.debug(f"[SSH INFO] user={email} | IP={ip} | UA={ua}") - user = request.user - return Response({ - "has_key": bool(user.encrypted_private_key), - "encrypted_private_key_name": user.encrypted_private_key_name, - "last_used_at": user.last_used_at - }) - + with tracer.start_as_current_span("SSHKeyInfoView GET"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + logger.debug(f"[SSH INFO] user={email} | IP={ip} | UA={ua}") + user = request.user + return Response({ + "has_key": bool(user.encrypted_private_key), + "encrypted_private_key_name": user.encrypted_private_key_name, + "last_used_at": user.last_used_at + }) class SSHKeyRetrieveView(APIView): permission_classes = [IsAuthenticated] def get(self, request): - email, ip, ua = get_request_info(request) - user = request.user - if not user.encrypted_private_key: - logger.warning(f"[SSH RETRIEVE] user={email} | status=fail | reason=not_found | IP={ip} | UA={ua}") - return Response({"error": "SSH 키가 등록되어 있지 않습니다."}, status=404) + with tracer.start_as_current_span("SSHKeyRetrieveView GET"): # ✅ Span 생성 + email, ip, ua = get_request_info(request) + user = request.user + if not user.encrypted_private_key: + logger.warning(f"[SSH RETRIEVE] user={email} | status=fail | reason=not_found | IP={ip} | UA={ua}") + return Response({"error": "SSH 키가 등록되어 있지 않습니다."}, status=404) - try: - decrypted_key = user.decrypt_private_key() - logger.info(f"[SSH RETRIEVE] user={email} | status=success | IP={ip} | UA={ua}") - return Response({"ssh_key": decrypted_key}) - except Exception as e: - logger.exception(f"[SSH RETRIEVE] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}") - return Response({"error": f"복호화 실패: {str(e)}"}, status=500) + try: + decrypted_key = user.decrypt_private_key() + logger.info(f"[SSH RETRIEVE] user={email} | status=success | IP={ip} | UA={ua}") + return Response({"ssh_key": decrypted_key}) + except Exception as e: + logger.exception(f"[SSH RETRIEVE] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}") + return Response({"error": f"복호화 실패: {str(e)}"}, status=500) diff --git a/version b/version index 1f2ba7d..9eddb00 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.0.11_rc6 \ No newline at end of file +0.0.11_rc7 \ No newline at end of file