BugFix in verification subsystem
This commit is contained in:
parent
44da6faadd
commit
2c51925827
@ -1,5 +1,4 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from .models import PhoneVerificationService
|
|
||||||
|
|
||||||
|
|
||||||
class AccountConfig(AppConfig):
|
class AccountConfig(AppConfig):
|
||||||
@ -7,4 +6,6 @@ class AccountConfig(AppConfig):
|
|||||||
name = 'account'
|
name = 'account'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
from .models import PhoneVerificationService
|
||||||
PhoneVerificationService.create()
|
PhoneVerificationService.create()
|
||||||
|
return True
|
||||||
|
@ -37,14 +37,13 @@ class PhoneVerificationService:
|
|||||||
with PhoneVerificationService.__lock:
|
with PhoneVerificationService.__lock:
|
||||||
if PhoneVerificationService.__event.is_set():
|
if PhoneVerificationService.__event.is_set():
|
||||||
PhoneVerificationService.__event.clear()
|
PhoneVerificationService.__event.clear()
|
||||||
print("Event reached!")
|
|
||||||
phones = PhoneVerificationService.__to_verify.copy()
|
phones = PhoneVerificationService.__to_verify.copy()
|
||||||
PhoneVerificationService.__to_verify.clear()
|
PhoneVerificationService.__to_verify.clear()
|
||||||
|
|
||||||
if phones is not None:
|
if phones is not None:
|
||||||
for phone in phones:
|
for phone in phones:
|
||||||
# тут должна быть проверка, есть ли телефон в списке кодов
|
# тут должна быть проверка, есть ли телефон в списке кодов
|
||||||
print(f"Verify {phone}")
|
|
||||||
obj = {
|
obj = {
|
||||||
"code": None,
|
"code": None,
|
||||||
"attempts": PHONE_VERIFICATION_ATTEMPTS,
|
"attempts": PHONE_VERIFICATION_ATTEMPTS,
|
||||||
@ -68,6 +67,7 @@ class PhoneVerificationService:
|
|||||||
if res_json["status"] == "OK":
|
if res_json["status"] == "OK":
|
||||||
with PhoneVerificationService.__lock:
|
with PhoneVerificationService.__lock:
|
||||||
PhoneVerificationService.__codes[phone]["code"] = res_json["code"]
|
PhoneVerificationService.__codes[phone]["code"] = res_json["code"]
|
||||||
|
print(f"Verify code for {phone}: {res_json['code']}")
|
||||||
else:
|
else:
|
||||||
with PhoneVerificationService.__lock:
|
with PhoneVerificationService.__lock:
|
||||||
PhoneVerificationService.__codes[phone]["code"] = "FAILED"
|
PhoneVerificationService.__codes[phone]["code"] = "FAILED"
|
||||||
@ -79,41 +79,50 @@ class PhoneVerificationService:
|
|||||||
else:
|
else:
|
||||||
with PhoneVerificationService.__lock:
|
with PhoneVerificationService.__lock:
|
||||||
obj["code"] = random.randint(1000, 9999)
|
obj["code"] = random.randint(1000, 9999)
|
||||||
|
PhoneVerificationService.__codes[phone] = obj
|
||||||
|
print(f"Verify code for {phone}: {obj['code']}")
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create():
|
def create():
|
||||||
if PhoneVerificationService.__instance is None:
|
if PhoneVerificationService.__instance is None:
|
||||||
PhoneVerificationService.__instance = Thread(target=PhoneVerificationService.__service_run)
|
PhoneVerificationService.__instance = Thread(target=PhoneVerificationService.__service_run, daemon=True)
|
||||||
PhoneVerificationService.__instance.start()
|
PhoneVerificationService.__instance.start()
|
||||||
|
|
||||||
|
CHECK_PHONE_NOT_FOUND = "not-found"
|
||||||
|
CHECK_PHONE_NOT_READY = "not-ready"
|
||||||
|
CHECK_PHONE_FAILED = "failed"
|
||||||
|
CHECK_PHONE_INVALID_CODE = "invalid"
|
||||||
|
CHECK_PHONE_MAX_ATTEMPTS = "max-attempts"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_code(phone: str, code: int):
|
def check_code(phone: str, code: int):
|
||||||
with PhoneVerificationService.__lock:
|
with PhoneVerificationService.__lock:
|
||||||
if phone not in PhoneVerificationService.__codes:
|
if phone not in PhoneVerificationService.__codes:
|
||||||
return False
|
return False, PhoneVerificationService.CHECK_PHONE_NOT_FOUND
|
||||||
c = PhoneVerificationService.__codes[phone]
|
c = PhoneVerificationService.__codes[phone]
|
||||||
|
print(f"verify struct: {c}")
|
||||||
|
|
||||||
if c["code"] is None:
|
if c["code"] is None:
|
||||||
print(f"[PhoneVerificationService] for phone {phone} code not received yet")
|
print(f"[PhoneVerificationService] for phone {phone} code not received yet")
|
||||||
return False
|
return False, PhoneVerificationService.CHECK_PHONE_NOT_READY
|
||||||
|
|
||||||
if c["code"] == "FAILED":
|
if c["code"] == "FAILED":
|
||||||
PhoneVerificationService.__codes.pop(phone)
|
PhoneVerificationService.__codes.pop(phone)
|
||||||
return False
|
return False, PhoneVerificationService.CHECK_PHONE_FAILED
|
||||||
|
|
||||||
if c["attempts"] > 0:
|
if c["attempts"] > 0:
|
||||||
if c["code"] == code:
|
if c["code"] == code:
|
||||||
PhoneVerificationService.__codes.pop(phone)
|
PhoneVerificationService.__codes.pop(phone)
|
||||||
return True
|
return True, None
|
||||||
else:
|
|
||||||
c["attepts"] -= 1
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
raise Exception("Number of attempts exceeded")
|
|
||||||
|
|
||||||
return False
|
else:
|
||||||
|
print(f"invalid code! attempts: {c['attempts']}")
|
||||||
|
PhoneVerificationService.__codes[phone]["attempts"] -= 1
|
||||||
|
return False, PhoneVerificationService.CHECK_PHONE_INVALID_CODE
|
||||||
|
else:
|
||||||
|
return False, PhoneVerificationService.CHECK_PHONE_MAX_ATTEMPTS
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send_verify(phone: str):
|
def send_verify(phone: str):
|
||||||
@ -129,6 +138,7 @@ class SiteAccountManager(BaseUserManager):
|
|||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.is_staff = False
|
user.is_staff = False
|
||||||
user.is_superuser = False
|
user.is_superuser = False
|
||||||
|
user.is_phone_verified = False
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@ -161,12 +171,12 @@ class SiteUser(AbstractBaseUser, PermissionsMixin):
|
|||||||
name = models.CharField(max_length=60, verbose_name="Имя")
|
name = models.CharField(max_length=60, verbose_name="Имя")
|
||||||
email = models.EmailField(unique=True, verbose_name="Email")
|
email = models.EmailField(unique=True, verbose_name="Email")
|
||||||
phone = models.CharField(unique=True, max_length=16, verbose_name="Телефон", validators=[
|
phone = models.CharField(unique=True, max_length=16, verbose_name="Телефон", validators=[
|
||||||
RegexValidator(regex="^\\+7[0-9]*$"), # +79208109798
|
RegexValidator(regex="^\\+7[0-9]*$"),
|
||||||
MaxLengthValidator(limit_value=12),
|
MaxLengthValidator(limit_value=12),
|
||||||
MinLengthValidator(limit_value=12)
|
MinLengthValidator(limit_value=12)
|
||||||
])
|
])
|
||||||
is_staff = models.BooleanField(default=False, verbose_name="Разрешение на вход в админку")
|
is_staff = models.BooleanField(default=False, verbose_name="Разрешение на вход в админку")
|
||||||
is_valid_phone = models.BooleanField(default=False, verbose_name="Телефон верифицирован")
|
is_phone_verified = models.BooleanField(default=False, verbose_name="Телефон верифицирован")
|
||||||
REQUIRED_FIELDS = ['name', 'surname', 'phone']
|
REQUIRED_FIELDS = ['name', 'surname', 'phone']
|
||||||
USERNAME_FIELD = 'email'
|
USERNAME_FIELD = 'email'
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import traceback
|
import traceback
|
||||||
|
from account.models import PhoneVerificationService
|
||||||
|
|
||||||
# как создавать ошибку
|
# как создавать ошибку
|
||||||
# raise Exception(API_ERROR_XXX, <related_obj>)
|
# raise Exception(API_ERROR_XXX, <related_obj>)
|
||||||
@ -20,6 +21,22 @@ API_ERROR_INVALID_TOKEN = (503, 'invalid token')
|
|||||||
# времненное решение, позже нужно будет заменить на конкретные ошибки
|
# времненное решение, позже нужно будет заменить на конкретные ошибки
|
||||||
API_ERROR_USER_REGISTER = (510, 'user registration error')
|
API_ERROR_USER_REGISTER = (510, 'user registration error')
|
||||||
|
|
||||||
|
API_ERROR_VALIDATION_INVALID_CODE = (520, 'invalid code')
|
||||||
|
API_ERROR_VALIDATION_MAX_ATTEMPTS = (521, 'max attempts')
|
||||||
|
API_ERROR_VALIDATION_CURRENTLY_VERIFIED = (522, 'currently phone is verified')
|
||||||
|
API_ERROR_VALIDATION_FAILED = (523, 'cannot be verified')
|
||||||
|
API_ERROR_VALIDATION_NOT_READY = (524, 'verification service not ready. call this method later')
|
||||||
|
API_ERROR_VALIDATION_NOT_FOUND = (525, 'verification service did not send code. call this method without \'code\'')
|
||||||
|
API_ERROR_VALIDATION_UNKNOWN = (526, 'unknown verification error')
|
||||||
|
|
||||||
|
API_ERROR_VALIDATION = {
|
||||||
|
PhoneVerificationService.CHECK_PHONE_INVALID_CODE: API_ERROR_VALIDATION_INVALID_CODE,
|
||||||
|
PhoneVerificationService.CHECK_PHONE_MAX_ATTEMPTS: API_ERROR_VALIDATION_MAX_ATTEMPTS,
|
||||||
|
PhoneVerificationService.CHECK_PHONE_FAILED: API_ERROR_VALIDATION_FAILED,
|
||||||
|
PhoneVerificationService.CHECK_PHONE_NOT_READY: API_ERROR_VALIDATION_NOT_READY,
|
||||||
|
PhoneVerificationService.CHECK_PHONE_NOT_FOUND: API_ERROR_VALIDATION_NOT_FOUND,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def make_error_object(ex: Exception):
|
def make_error_object(ex: Exception):
|
||||||
try:
|
try:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import traceback
|
import traceback
|
||||||
import account.models
|
from account.models import *
|
||||||
from .api_utils import *
|
from .api_utils import *
|
||||||
from .models import *
|
from .models import *
|
||||||
from django.core.exceptions import *
|
from django.core.exceptions import *
|
||||||
@ -26,7 +26,7 @@ def account_register(params):
|
|||||||
email = api_get_param_str(params, "email")
|
email = api_get_param_str(params, "email")
|
||||||
password = api_get_param_str(params, "password")
|
password = api_get_param_str(params, "password")
|
||||||
|
|
||||||
user = account.models.SiteUser(
|
user = SiteUser(
|
||||||
name=name,
|
name=name,
|
||||||
surname=surname,
|
surname=surname,
|
||||||
phone=phone,
|
phone=phone,
|
||||||
@ -47,14 +47,55 @@ def account_register(params):
|
|||||||
user.delete()
|
user.delete()
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
except ValidationError as e:
|
except ValidationError as validation_error:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise Exception(API_ERROR_USER_REGISTER, e.message_dict)
|
errors = {}
|
||||||
|
for field_name in validation_error.error_dict:
|
||||||
|
err_list = validation_error.error_dict[field_name]
|
||||||
|
print(err_list)
|
||||||
|
obj = []
|
||||||
|
for err in err_list:
|
||||||
|
obj.append({
|
||||||
|
"code": err.code
|
||||||
|
})
|
||||||
|
errors[field_name] = obj
|
||||||
|
raise Exception(API_ERROR_USER_REGISTER, errors)
|
||||||
|
|
||||||
|
|
||||||
|
def account_verify_phone(params):
|
||||||
|
user = _reqire_access_token(params)
|
||||||
|
|
||||||
|
if user.is_phone_verified:
|
||||||
|
raise Exception(API_ERROR_VALIDATION_CURRENTLY_VERIFIED)
|
||||||
|
|
||||||
|
code = api_get_param_int(params, "code", False, None)
|
||||||
|
|
||||||
|
if code is None:
|
||||||
|
PhoneVerificationService.send_verify(user.phone)
|
||||||
|
return api_make_response({"action": "phone_call"})
|
||||||
|
else:
|
||||||
|
res, err_code = PhoneVerificationService.check_code(user.phone, code)
|
||||||
|
|
||||||
|
if res:
|
||||||
|
user.is_phone_verified = True
|
||||||
|
user.save()
|
||||||
|
return api_make_response({"status": "success"})
|
||||||
|
else:
|
||||||
|
if err_code in API_ERROR_VALIDATION:
|
||||||
|
raise Exception(API_ERROR_VALIDATION[err_code])
|
||||||
|
else:
|
||||||
|
raise Exception(API_ERROR_VALIDATION_UNKNOWN)
|
||||||
|
|
||||||
|
|
||||||
def account_get(params):
|
def account_get(params):
|
||||||
user = _reqire_access_token(params)
|
user = _reqire_access_token(params)
|
||||||
return api_make_response({"name": user.name, "surname": user.surname, "email": user.email, "phone": user.phone})
|
return api_make_response({
|
||||||
|
"name": user.name,
|
||||||
|
"surname": user.surname,
|
||||||
|
"email": user.email,
|
||||||
|
"phone": user.phone,
|
||||||
|
"phone_verified": user.is_phone_verified
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def __make_argument_doc(name, arg_type, description, required=True):
|
def __make_argument_doc(name, arg_type, description, required=True):
|
||||||
@ -83,13 +124,25 @@ api_methods = {
|
|||||||
],
|
],
|
||||||
"returns": "В случае правильных логина и пароля <code>access_token</code>. В противном случае объект ошибки."
|
"returns": "В случае правильных логина и пароля <code>access_token</code>. В противном случае объект ошибки."
|
||||||
},
|
},
|
||||||
|
|
||||||
"account.register": {
|
"account.register": {
|
||||||
"func": account_register,
|
"func": account_register,
|
||||||
"doc": "Регистрация нового пользователя",
|
"doc": "Регистрация нового пользователя",
|
||||||
"params": [
|
"params": [
|
||||||
|
|
||||||
],
|
],
|
||||||
"returns": "Поля пользователя (name, surname, email, phone)."
|
"returns": "Поля пользователя (name, surname, email, phone, phone_verified)."
|
||||||
|
},
|
||||||
|
|
||||||
|
"account.verifyPhone": {
|
||||||
|
"func": account_verify_phone,
|
||||||
|
"doc": "Запросить верификацию номера телефона."
|
||||||
|
"Если телефон уже верифицирован, метод вернет соответствующую ошибку",
|
||||||
|
"params": [
|
||||||
|
__make_argument_access_token(),
|
||||||
|
__make_argument_doc("code", __doc_type_string, "Код верификации. Если не передать, будет выполнен звонок"),
|
||||||
|
],
|
||||||
|
"returns": '{"status": "success"}, если верификация пройдена. Иначе одну из стандартных ошибок'
|
||||||
},
|
},
|
||||||
|
|
||||||
"account.get": {
|
"account.get": {
|
||||||
@ -98,6 +151,6 @@ api_methods = {
|
|||||||
"params": [
|
"params": [
|
||||||
__make_argument_access_token()
|
__make_argument_access_token()
|
||||||
],
|
],
|
||||||
"returns": "Поля пользователя (name, surname, email, phone)."
|
"returns": "Поля пользователя (name, surname, email, phone, phone_verified)."
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -45,10 +45,11 @@ class UserToken(models.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_by_token(token: str):
|
def get_user_by_token(token: str):
|
||||||
t = UserToken.objects.get(access_token=token)
|
t = UserToken.objects.filter(access_token=token)
|
||||||
if t is None:
|
|
||||||
|
if len(t) == 0:
|
||||||
raise Exception(API_ERROR_INVALID_TOKEN)
|
raise Exception(API_ERROR_INVALID_TOKEN)
|
||||||
return t.user
|
return t[0].user
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.email + ": " + self.access_token[:10] + "..."
|
return self.user.email + ": " + self.access_token[:10] + "..."
|
||||||
|
Reference in New Issue
Block a user