- RegisterSerializer에 비밀번호 정책 검증 추가 - ExtendPasswordExpiryView: 비밀번호 유효기간 연장 API - CustomTokenObtainPairSerializer: 로그인 시 만료/잠금 검증 - password_utils.py: 정책 검증, 계정 잠금, 만료 체크 유틸리티 - SiteSettings 모델에 비밀번호 정책 필드 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -19,19 +19,52 @@ class RegisterSerializer(serializers.ModelSerializer):
|
||||
return obj.has_usable_password()
|
||||
|
||||
def validate_email(self, value):
|
||||
if CustomUser.objects.filter(email=value).exists():
|
||||
# 업데이트 시에는 자기 자신 제외
|
||||
instance = getattr(self, 'instance', None)
|
||||
queryset = CustomUser.objects.filter(email=value)
|
||||
if instance:
|
||||
queryset = queryset.exclude(pk=instance.pk)
|
||||
if queryset.exists():
|
||||
raise ValidationError("이미 사용 중인 이메일입니다.")
|
||||
return value
|
||||
|
||||
def validate_name(self, value):
|
||||
if CustomUser.objects.filter(name=value).exists():
|
||||
# 업데이트 시에는 자기 자신 제외
|
||||
instance = getattr(self, 'instance', None)
|
||||
queryset = CustomUser.objects.filter(name=value)
|
||||
if instance:
|
||||
queryset = queryset.exclude(pk=instance.pk)
|
||||
if queryset.exists():
|
||||
raise ValidationError("이미 사용 중인 이름입니다.")
|
||||
return value
|
||||
|
||||
def validate_password(self, value):
|
||||
"""비밀번호 정책 검증 (회원가입 시)"""
|
||||
from .models import SiteSettings
|
||||
from .password_utils import validate_password_policy
|
||||
|
||||
# 비밀번호가 제공된 경우에만 검증 (업데이트 시 비밀번호 변경 안 할 수 있음)
|
||||
if value:
|
||||
site_settings = SiteSettings.get_settings()
|
||||
is_valid, errors = validate_password_policy(value, site_settings)
|
||||
if not is_valid:
|
||||
raise ValidationError(errors)
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
password = validated_data.pop("password")
|
||||
from .models import SiteSettings
|
||||
from .password_utils import validate_password_policy
|
||||
from django.utils import timezone
|
||||
|
||||
password = validated_data.pop("password", None)
|
||||
|
||||
# 회원가입 시 비밀번호 필수
|
||||
if not password:
|
||||
raise ValidationError({"password": "비밀번호는 필수입니다."})
|
||||
|
||||
user = CustomUser(**validated_data)
|
||||
user.set_password(password)
|
||||
user.password_changed_at = timezone.now() # 비밀번호 변경일 설정
|
||||
user.save()
|
||||
return user
|
||||
|
||||
@ -70,6 +103,12 @@ class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||
return token
|
||||
|
||||
def validate(self, attrs):
|
||||
from .models import SiteSettings
|
||||
from .password_utils import (
|
||||
check_account_lockout, record_login_failure,
|
||||
reset_login_failures, check_password_expiry
|
||||
)
|
||||
|
||||
identifier = attrs.get("email") # 이메일 또는 이름
|
||||
password = attrs.get("password")
|
||||
|
||||
@ -80,11 +119,27 @@ class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||
|
||||
if user is None:
|
||||
raise ValidationError("계정 또는 비밀번호가 올바르지 않습니다.")
|
||||
|
||||
# 사이트 설정 가져오기
|
||||
site_settings = SiteSettings.get_settings()
|
||||
|
||||
# 계정 잠금 상태 확인
|
||||
lockout_status = check_account_lockout(user, site_settings)
|
||||
if lockout_status['is_locked']:
|
||||
raise ValidationError(lockout_status['message'])
|
||||
|
||||
if not user.is_active:
|
||||
raise ValidationError("계정이 비활성화되어 있습니다. 관리자에게 문의하세요.")
|
||||
|
||||
# 비밀번호 검증
|
||||
if not user.check_password(password):
|
||||
# 로그인 실패 기록
|
||||
record_login_failure(user, site_settings)
|
||||
raise ValidationError("계정 또는 비밀번호가 올바르지 않습니다.")
|
||||
|
||||
# 로그인 성공 - 실패 횟수 초기화
|
||||
reset_login_failures(user)
|
||||
|
||||
# 부모 클래스의 validate를 위해 attrs에 실제 email 설정
|
||||
attrs["email"] = user.email
|
||||
self.user = user # ✅ 수동 설정 필요
|
||||
@ -92,6 +147,17 @@ class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||
|
||||
data["email"] = user.email
|
||||
data["grade"] = user.grade
|
||||
|
||||
# 비밀번호 만료 상태 확인
|
||||
expiry_status = check_password_expiry(user, site_settings)
|
||||
if expiry_status['is_expired']:
|
||||
data["password_expired"] = True
|
||||
data["password_message"] = expiry_status['message']
|
||||
elif expiry_status['is_warning']:
|
||||
data["password_warning"] = True
|
||||
data["password_message"] = expiry_status['message']
|
||||
data["password_days_until_expiry"] = expiry_status['days_until_expiry']
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user