"""JWT 인증 — Authorization: Bearer 헤더 또는 ?token= 쿼리(EventSource·다운로드용). 토큰 발급은 issue_tokens() — 프론트 TokenResponse 모양.""" from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework_simplejwt.exceptions import InvalidToken, TokenError from rest_framework_simplejwt.tokens import AccessToken, RefreshToken class BearerOrQueryJWTAuthentication(JWTAuthentication): def authenticate(self, request): result = super().authenticate(request) if result is not None: return result raw = request.query_params.get("token") if hasattr(request, "query_params") else None raw = raw or request.GET.get("token") if not raw: return None validated = self.get_validated_token(raw) return self.get_user(validated), validated def issue_tokens(user, refresh: RefreshToken | None = None) -> dict: """TokenResponse. refresh 를 주면 그걸 유지하고 access 만 새로 찍음(회전 안 함).""" refresh = refresh or RefreshToken.for_user(user) access = refresh.access_token return { "token": str(access), "tokenExpirationTime": int(access["exp"]), "refreshToken": str(refresh), "refreshTokenExpirationTime": int(refresh["exp"]), "tokenType": "bearer", "user": user.as_payload(), } def user_from_token(raw: str): """순수 Django(async) 뷰용 — access 토큰 → 사용자, 실패 시 None.""" if not raw: return None try: return BearerOrQueryJWTAuthentication().get_user(AccessToken(raw)) except (InvalidToken, TokenError, Exception): return None