Postgres support (DB_* env), LoginEvent audit table, Choice.value 300, deploy.sh pg_dump backup
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,11 +12,11 @@ from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
from rest_framework.throttling import BaseThrottle, ScopedRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .authentication import invalidate_user_cache, issue_access_token
|
||||
from .models import OTPCode, Profile, Session, User, hash_refresh_token
|
||||
from .models import LoginEvent, OTPCode, Profile, Session, User, hash_refresh_token
|
||||
from .services import OTPDeliveryError, send_otp_sms
|
||||
from .serializers import (
|
||||
DeleteAccountRequestSerializer,
|
||||
@@ -38,6 +38,21 @@ from .serializers import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def record_login_event(request, event: str, *, user_id=None, phone: str = '') -> None:
|
||||
"""
|
||||
ردیفِ LoginEvent مینویسد. شکستش ورود/خروج را خراب نمیکند (ثبتِ سابقه نباید کاربر را بیرون
|
||||
بیندازد) ولی با logger.exception دیده میشود. زیرِ بارِ همزمان و روی Postgres سنجیده نشده.
|
||||
"""
|
||||
try:
|
||||
LoginEvent.objects.create(
|
||||
event=event, user_id=user_id, phone=phone,
|
||||
ip=BaseThrottle().get_ident(request) or None,
|
||||
user_agent=request.META.get('HTTP_USER_AGENT', '')[:300],
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('LoginEvent نوشته نشد: event=%s user_id=%s', event, user_id)
|
||||
|
||||
|
||||
class RequestOTPView(APIView):
|
||||
"""
|
||||
POST /api/auth/otp/request — درخواستِ کدِ ورود.
|
||||
@@ -273,6 +288,12 @@ class LoginView(APIView):
|
||||
|
||||
error = verify_phone_otp(phone, otp)
|
||||
if error is not None:
|
||||
record_login_event(
|
||||
request,
|
||||
LoginEvent.LOGIN_BLOCKED if error.status_code == status.HTTP_429_TOO_MANY_REQUESTS
|
||||
else LoginEvent.LOGIN_FAILED,
|
||||
phone=phone,
|
||||
)
|
||||
return error
|
||||
|
||||
with transaction.atomic():
|
||||
@@ -293,6 +314,7 @@ class LoginView(APIView):
|
||||
refresh_token=hash_refresh_token(refresh_token),
|
||||
expires_at=timezone.now() + timezone.timedelta(days=settings.REFRESH_TOKEN_TTL_DAYS),
|
||||
)
|
||||
record_login_event(request, LoginEvent.LOGIN_OK, user_id=user.id, phone=phone)
|
||||
access_token = issue_access_token(user.id)
|
||||
|
||||
data = {
|
||||
@@ -316,7 +338,7 @@ def _count_failed_attempt(phone: str) -> None:
|
||||
# Redemption و UserOffer هم میمانند: سقفِ مصرفِ کدِ تخفیف ردیفهای Redemption را میشمارد، پس پاک
|
||||
# شدنشان یک کدِ یکبارمصرف را با «بساز، بخر، حذف کن» دوباره آزاد میکرد.
|
||||
KEPT_ON_ACCOUNT_DELETE = ('payment.Order', 'statuslog.StatusChangeLog', 'affiliate.Conversion',
|
||||
'discount.Redemption', 'discount.UserOffer')
|
||||
'discount.Redemption', 'discount.UserOffer', 'accounts.LoginEvent')
|
||||
|
||||
|
||||
class DeleteAccountView(APIView):
|
||||
@@ -356,6 +378,7 @@ class DeleteAccountView(APIView):
|
||||
return error
|
||||
|
||||
logger.info('Account deleted by its owner: user_id=%s', user.id)
|
||||
record_login_event(request, LoginEvent.ACCOUNT_DELETED, user_id=user.id, phone=user.phone or '')
|
||||
# کدهای پیامکیِ شماره میمانند: سقفِ روزانهی پیامکِ هر شماره از همین ردیفها شمرده میشود.
|
||||
delete_users([user.id], keep=KEPT_ON_ACCOUNT_DELETE, delete_otp_codes=False)
|
||||
return Response(DeleteAccountResponseSerializer({'success': True}).data)
|
||||
@@ -395,6 +418,7 @@ class LogoutView(APIView):
|
||||
).update(revoked=True)
|
||||
User.objects.filter(pk=request.user.id).update(tokens_valid_after=timezone.now())
|
||||
invalidate_user_cache(request.user.id)
|
||||
record_login_event(request, LoginEvent.LOGOUT, user_id=request.user.id, phone=request.user.phone or '')
|
||||
return Response(LogoutResponseSerializer({'success': True}).data)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user