Files
crmApi/apps/jwt_auth/services.py
T
2026-10-08 16:37:45 +03:30

100 lines
4.1 KiB
Python

import secrets
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
from django.utils import timezone
from rest_framework import exceptions
from apps.cuser.models import CuserRole, SmsOpt
from apps.jwt_auth.models import CUserRoleLoginOpt
from utils.services.sms import DefaultSmsProvider
from utils.validations.mobile import normalize_mobile
CUser = get_user_model()
class SmsDeliveryUnavailable(exceptions.APIException):
status_code = 503
default_detail = 'ارسال پیامک انجام نشد. لطفاً کمی بعد دوباره تلاش کنید.'
default_code = 'sms_failed'
def eligible_roles(user, customer_only=False):
roles = CuserRole.objects.select_related('role_type', 'sys_ws', 'cuser').filter(
cuser=user, is_active=True, mobile_verify=True, sys_ws__is_active=True,
)
if customer_only:
roles = roles.filter(role_type__role_name_en='workspace_customer', customer_panel=True)
return [role for role in roles if
(not role.sys_ws.expire or role.sys_ws.expire > timezone.now()) and
(role.role_type.role_name_en != 'workspace_customer' or role.customer_panel is True)]
def create_login_options(user, roles):
CUserRoleLoginOpt.objects.filter(cuser=user).delete()
options = []
for role in roles:
options.append(CUserRoleLoginOpt.objects.create(
cuser=user, cuser_role=role, expire=timezone.now() + timedelta(minutes=5),
))
return options
@transaction.atomic
def login_by_username(username: str, password: str):
user = CUser.objects.select_for_update().filter(username=username).first()
if not user or not user.check_password(password):
raise exceptions.ValidationError({'error': 'نام کاربری یا رمز عبور اشتباه است.'})
if not user.is_active:
raise exceptions.PermissionDenied('حساب کاربری شما غیرفعال است.')
roles = eligible_roles(user)
if not roles:
raise exceptions.PermissionDenied('نقش یا دسترسی فعال برای این حساب وجود ندارد.')
return create_login_options(user, roles)
def mobile_user(mobile):
try:
mobile = normalize_mobile(mobile)
except DjangoValidationError as error:
raise exceptions.ValidationError(error.message_dict)
user = CUser.objects.filter(mobile=mobile).first()
if not user:
raise exceptions.ValidationError({'mobile': 'کاربری با این شماره موبایل ثبت نشده است.'})
if not user.is_active:
raise exceptions.PermissionDenied('حساب کاربری شما غیرفعال است.')
roles = eligible_roles(user, customer_only=True)
if not roles:
raise exceptions.PermissionDenied('دسترسی شما به پنل مشتریان فعال نیست.')
return user, roles
@transaction.atomic
def login_by_mobile(mobile: int):
user, _ = mobile_user(mobile)
# Serialize code requests for this account, retaining the old valid code if
# the provider refuses the new message.
CUser.objects.select_for_update().get(pk=user.pk)
code = secrets.randbelow(900000) + 100000
if not DefaultSmsProvider().send_sms(str(user.mobile), f'کد ورود شما: {code}'):
raise SmsDeliveryUnavailable()
SmsOpt.objects.filter(mobile=user.mobile).delete()
SmsOpt.objects.create(mobile=user.mobile, opt=code, expire=timezone.now() + timedelta(minutes=3))
return user.mobile
@transaction.atomic
def verify_mobile_and_get_token(mobile: int, opt: int):
user, roles = mobile_user(mobile)
CUser.objects.select_for_update().get(pk=user.pk)
code = SmsOpt.objects.select_for_update().filter(
mobile=user.mobile, opt=opt, expire__gt=timezone.now(),
).first()
if not code:
raise exceptions.ValidationError({'opt': 'کد تأیید اشتباه است یا اعتبار آن تمام شده است.'})
options = create_login_options(user, roles)
SmsOpt.objects.filter(mobile=user.mobile).delete()
return options