پیاده سازی Mobile Number Authentication در Django

پیاده سازی Mobile Number Authentication در Djangoچند بار شده که دوستان مختلف برای Authentication با شماره موبایل در Django سوالات و مشکلات مختلفی داشتن. امیدوارم این پست بتونه پوشش خوبی به سوالاتشون بده.در ابتدا به موارد زیر حتما توجه می کنیم:من فرض کردم میخوایم همزمان فیلد mobile_number و username رو داشته باشیم. من در این مقاله کاری به فیلد یوزرنیم ندارم ولی میخوام مثلا کاربر بتونه درآینده username برای حساب کاربریش بسازه ولی رجیستر و لاگین با شماره موبایل انجام بشه(مثل تلگرام)کاربرد USERNAME_FIELD چیست؟ در این پارامتر بایستی مشخص شود چه فیلدی تعیین کننده هویت user هست که معمولا روی فیلدهای email یا username یا هر فیلد دیگه که توسط ما ساخته میشه می تونیم معرفی کنیم.کاربرد objects چیست؟ اگر با زیر ساخت Django آشنایی داشته باشید هر model یک manager دارد که query ها و خیلی از عملیات مربوط به database مدیریت میکنه. manager هر model بصورت پیشفرض از طریق objects در دسترس هست. و ما میتونیم برای model مون چند تا manager داشته باشیم یا manager خودمون رو در objects داشته باشیم.مراحلی که قراره طی بشه:۱- اضافه کردن فیلد mobile_number به مدل user و تعیین فیلد اصلی برای user که الان فیلد username هست و ما میخوایم بگیم بشه فیلد mobile_number۲- تغییر user manager تا به جای چک کردن فیلد username mobile number چک کنه۳- پیاده سازی لاگین و رجیستر که در چند مرحله بصورت زیر پیاده سازی میکنیم:گرفتن شماره موبایل تا بررسی کند کاربر قبلا ثبت نام کرده یا نه در صورتی که قبلا ثبت نام نکرده باشد کد فعال سازی ارسال خواهد شد. در غیر این صورت وارد مراحل بعد(وارد کردن پسورد می شود که مورد بحث ما نیست)ارسال کد به سرور و validate کردن کد سپس مراحل بعدی که مورد بحث ما نیست.مراحل بالا رو بصورت کد پیاده شده پیش میریم.مرحله اول: اضافه کردن فیلد mobile_number به مدل user و تعیین USERNAME_FIELDclass User(AbstractUser): mobile_number_validator = UnicodeMobileNumberValidator( username = models.CharField( _('username'), max_length=150, unique=False, null=True, blank=True, help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.') ) mobile_number = models.CharField(max_length=50, unique=True, verbose_name=_('Mobile Number'), validators=[mobile_number_validator], error_messages={ 'unique': _("A user with that mobile number already exists."), }, ) USERNAME_FIELD = 'mobile_number' REQUIRED_FIELDS = [] objects = MyUserManager()@deconstructible class UnicodeMobileNumberValidator(validators.RegexValidator): regex = r'09(\d{9})$' message = _( 'Enter a valid mobile number. This value may contain only numbers.' ) flags = 0مرحله دوم: تغییر user manager class MyUserManager(BaseUserManager): use_in_migrations = True def _create_user(self, mobile_number, password, **extra_fields): if not mobile_number: raise ValueError('The given username must be set') # email = self.normalize_email(email) mobile_number = self.normalize_mobile_number(mobile_number) user = self.model(mobile_number=mobile_number, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_user(self, mobile_number, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(mobile_number, password, **extra_fields) def create_superuser(self, mobile_number, password, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.') return self._create_user(mobile_number, password, **extra_fields) def normalize_mobile_number(self, mobile_number): return mobile_numberمرحله سوم:‌ نکته :‌ برای ارسال کد از یک Adapter فرضی استفاده کردم که میشه داخلش با هر نوع sms پنلی پیاده بشه.نکته: روش متنوعی میشه برای نگه داری کد فعال سازی پیاده سازی کرد که بسته به Domain Logic یا نیاز های پروژه میتونه متفاوت باشه. در این مقاله بنده از Redis استفاده میکنم.۱- پیاده سازی API گرفتن mobile number و بررسی exist بودن کاربر ۲- پیاده سازی API ارسال کد فعال سازیdef request_send_verify_sms(request): mobile_number = request.data['mobile_number'] if User.objects.filter(mobile_number=mobile_number).exclude(pk=request.user.pk).exists(): raise APIException('mobile number already added', code=HTTP_406_NOT_ACCEPTABLE) verification_code = StringUtils.id_generator(4, string.digits) adapter = SMSAdapter() response = adapter.send_verify_sms( destination_number=mobile_number, verification_code=verification_code ) if response['status'] != 200: # failed send sms raise APIException('failed to send sms', HTTP_400_BAD_REQUEST) redisClient.setex(verification_code, SMS_VERIFICATION_CODE_EXPIRED_TIME, mobile_number) return Response({"message": "ok"})۳- پیاده سازی API validate کردن کد فعال سازیdef verify_mobile_number(request): verification_code = request.data['verification_code'] if redisClient.exists(verification_code): mobile_number = redisClient.get(verification_code).decode("utf-8") redisClient.setex(mobile_number, CREATE_USER_EXPIRED_TIME, mobile_number) else: raise APIException('wrong verification code', code=HTTP_406_NOT_ACCEPTABLE) return Response({"message": "ok"})اگر فرصت کنم یک کد سمپل رو github منتشر میکنم.