100 lines
4.1 KiB
Python
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
|