""" 인증 API — 프론트 `features/auth/api/auth.api.ts` 계약. POST /api/v1/auth/login {email,password} → TokenResponse POST /api/v1/auth/refresh {refreshToken} → TokenResponse (access 갱신) POST /api/v1/auth/logout → null (클라가 토큰 버림. 서버는 회전·블랙리스트 안 함) GET /api/v1/users/me → UserResponse GET /api/v1/auth/entra/config → 501 (Entra 안 씀 — 프론트가 버튼 숨김) """ from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_simplejwt.exceptions import TokenError from rest_framework_simplejwt.tokens import RefreshToken from common.envelope import CodedError from .authentication import issue_tokens from .models import User from .serializers import LoginSerializer, RefreshSerializer class LoginView(APIView): permission_classes = [AllowAny] authentication_classes = [] # 옛 토큰이 붙어 있어도 로그인은 통과시킴 def post(self, request): ser = LoginSerializer(data=request.data) ser.is_valid(raise_exception=True) user = User.objects.filter(email__iexact=ser.validated_data["email"]).first() if user is None or not user.check_password(ser.validated_data["password"]): raise CodedError(401, "INVALID_CREDENTIALS", "이메일 또는 비밀번호가 올바르지 않아") if not user.is_active: raise CodedError(403, "USER_INACTIVE", "비활성 계정이야") return Response(issue_tokens(user)) class RefreshView(APIView): permission_classes = [AllowAny] authentication_classes = [] def post(self, request): ser = RefreshSerializer(data=request.data or {}) ser.is_valid(raise_exception=True) raw = ser.validated_data.get("refreshToken") or "" if not raw: raise CodedError(401, "REFRESH_TOKEN_MISSING", "refresh 토큰이 없어") try: refresh = RefreshToken(raw) except TokenError: raise CodedError(401, "REFRESH_TOKEN_INVALID", "refresh 토큰이 만료됐거나 잘못됐어") user = User.objects.filter(id=refresh["uid"], is_active=True).first() if user is None: raise CodedError(401, "REFRESH_TOKEN_INVALID", "사용자를 찾을 수 없어") return Response(issue_tokens(user, refresh)) class LogoutView(APIView): permission_classes = [AllowAny] authentication_classes = [] def post(self, _request): return Response(None) class MeView(APIView): def get(self, request): return Response(request.user.as_response()) class EntraConfigView(APIView): permission_classes = [AllowAny] authentication_classes = [] def get(self, _request): raise CodedError(501, "NOT_IMPLEMENTED", "Entra 로그인은 지원하지 않아")