perf: Custom SMS (files) support obtaining more user information. (#14486)

* perf: Custom SMS (files) support obtaining more user information.

* perf: Remove the useless modules

* perf: modify

---------

Co-authored-by: jiangweidong <1053570670@qq.com>
pull/14490/head
fit2bot 2024-11-20 10:29:14 +08:00 committed by GitHub
parent f92c557235
commit e2904ab042
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 44 additions and 16 deletions

View File

@ -1,5 +1,6 @@
import time import time
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import reverse from django.shortcuts import reverse
@ -40,12 +41,15 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
return user, None return user, None
@staticmethod @staticmethod
def safe_send_code(token, code, target, form_type, content): def safe_send_code(token, code, target, form_type, content, user_info):
token_sent_key = '{}_send_at'.format(token) token_sent_key = '{}_send_at'.format(token)
token_send_at = cache.get(token_sent_key, 0) token_send_at = cache.get(token_sent_key, 0)
if token_send_at: if token_send_at:
raise IntervalTooShort(60) raise IntervalTooShort(60)
SendAndVerifyCodeUtil(target, code, backend=form_type, **content).gen_and_send_async() tooler = SendAndVerifyCodeUtil(
target, code, backend=form_type, user_info=user_info, **content
)
tooler.gen_and_send_async()
cache.set(token_sent_key, int(time.time()), 60) cache.set(token_sent_key, int(time.time()), 60)
def prepare_code_data(self, user_info, serializer): def prepare_code_data(self, user_info, serializer):
@ -61,7 +65,7 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
if not user: if not user:
raise ValueError(err) raise ValueError(err)
code = random_string(6, lower=False, upper=False) code = random_string(settings.SMS_CODE_LENGTH, lower=False, upper=False)
subject = '%s: %s' % (get_login_title(), _('Forgot password')) subject = '%s: %s' % (get_login_title(), _('Forgot password'))
context = { context = {
'user': user, 'title': subject, 'code': code, 'user': user, 'title': subject, 'code': code,
@ -82,7 +86,7 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
code, target, form_type, content = self.prepare_code_data(user_info, serializer) code, target, form_type, content = self.prepare_code_data(user_info, serializer)
except ValueError as e: except ValueError as e:
return Response({'error': str(e)}, status=400) return Response({'error': str(e)}, status=400)
self.safe_send_code(token, code, target, form_type, content) self.safe_send_code(token, code, target, form_type, content, user_info)
return Response({'data': 'ok'}, status=200) return Response({'data': 'ok'}, status=200)

View File

@ -2,6 +2,7 @@ from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.utils.verify_code import SendAndVerifyCodeUtil from common.utils.verify_code import SendAndVerifyCodeUtil
from users.serializers import SmsUserSerializer
from .base import BaseMFA from .base import BaseMFA
sms_failed_msg = _("SMS verify code invalid") sms_failed_msg = _("SMS verify code invalid")
@ -14,8 +15,13 @@ class MFASms(BaseMFA):
def __init__(self, user): def __init__(self, user):
super().__init__(user) super().__init__(user)
phone = user.phone if self.is_authenticated() else '' phone, user_info = '', None
self.sms = SendAndVerifyCodeUtil(phone, backend=self.name) if self.is_authenticated():
phone = user.phone
user_info = SmsUserSerializer(user).data
self.sms = SendAndVerifyCodeUtil(
phone, backend=self.name, user_info=user_info
)
def check_code(self, code): def check_code(self, code):
assert self.is_authenticated() assert self.is_authenticated()

View File

@ -43,7 +43,7 @@ class SMS:
**kwargs **kwargs
) )
def send_verify_code(self, phone_number, code): def send_verify_code(self, phone_number, code, **kwargs):
prefix = getattr(self.client, 'SIGN_AND_TMPL_SETTING_FIELD_PREFIX', '') prefix = getattr(self.client, 'SIGN_AND_TMPL_SETTING_FIELD_PREFIX', '')
sign_name = getattr(settings, f'{prefix}_VERIFY_SIGN_NAME', None) sign_name = getattr(settings, f'{prefix}_VERIFY_SIGN_NAME', None)
template_code = getattr(settings, f'{prefix}_VERIFY_TEMPLATE_CODE', None) template_code = getattr(settings, f'{prefix}_VERIFY_TEMPLATE_CODE', None)
@ -53,4 +53,7 @@ class SMS:
code='verify_code_sign_tmpl_invalid', code='verify_code_sign_tmpl_invalid',
detail=_('SMS verification code signature or template invalid') detail=_('SMS verification code signature or template invalid')
) )
return self.send_sms([phone_number], sign_name, template_code, OrderedDict(code=code)) return self.send_sms(
[phone_number], sign_name, template_code,
OrderedDict(code=code), **kwargs
)

View File

@ -20,16 +20,20 @@ logger = get_logger(__file__)
be executed to send SMS messages""" be executed to send SMS messages"""
) )
) )
def send_sms_async(target, code): def send_sms_async(target, code, user_info):
SMS().send_verify_code(target, code) SMS().send_verify_code(target, code, user_info=user_info)
class SendAndVerifyCodeUtil(object): class SendAndVerifyCodeUtil(object):
KEY_TMPL = 'auth-verify-code-{}' KEY_TMPL = 'auth-verify-code-{}'
def __init__(self, target, code=None, key=None, backend='email', timeout=None, **kwargs): def __init__(
self, target, code=None, key=None, backend='email',
user_info=None, timeout=None, **kwargs
):
self.code = code self.code = code
self.target = target self.target = target
self.user_info = user_info
self.backend = backend self.backend = backend
self.key = key or self.KEY_TMPL.format(target) self.key = key or self.KEY_TMPL.format(target)
self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout
@ -78,7 +82,7 @@ class SendAndVerifyCodeUtil(object):
return code return code
def __send_with_sms(self): def __send_with_sms(self):
send_sms_async.apply_async(args=(self.target, self.code), priority=100) send_sms_async.apply_async(args=(self.target, self.code, self.user_info), priority=100)
def __send_with_email(self): def __send_with_email(self):
subject = self.other_args.get('subject', '') subject = self.other_args.get('subject', '')

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django import forms from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
@ -103,7 +104,9 @@ class UserForgotPasswordForm(forms.Form):
label=_('SMS'), required=False, label=_('SMS'), required=False,
help_text=_('The phone number must contain an area code, for example, +86') help_text=_('The phone number must contain an area code, for example, +86')
) )
code = forms.CharField(label=_('Verify code'), max_length=6, required=False) code = forms.CharField(
label=_('Verify code'), max_length=settings.SMS_CODE_LENGTH, required=False
)
form_type = forms.ChoiceField( form_type = forms.ChoiceField(
choices=[('sms', _('SMS')), ('email', _('Email'))], choices=[('sms', _('SMS')), ('email', _('Email'))],
widget=forms.HiddenInput({'value': 'email'}) widget=forms.HiddenInput({'value': 'email'})

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from functools import partial from functools import partial
from django.conf import settings from django.conf import settings
@ -27,6 +26,7 @@ from ..models import User
__all__ = [ __all__ = [
"UserSerializer", "UserSerializer",
"SmsUserSerializer",
"MiniUserSerializer", "MiniUserSerializer",
"InviteSerializer", "InviteSerializer",
"ServiceAccountSerializer", "ServiceAccountSerializer",
@ -411,6 +411,14 @@ class UserRetrieveSerializer(UserSerializer):
fields = UserSerializer.Meta.fields + ["login_confirm_settings"] fields = UserSerializer.Meta.fields + ["login_confirm_settings"]
class SmsUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id', 'username', 'name', 'email', 'phone', 'source', 'is_active', 'comment'
]
class MiniUserSerializer(serializers.ModelSerializer): class MiniUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User

View File

@ -16,6 +16,7 @@ from authentication.utils import check_user_property_is_correct
from common.const.choices import COUNTRY_CALLING_CODES from common.const.choices import COUNTRY_CALLING_CODES
from common.utils import FlashMessageUtil, get_object_or_none, random_string from common.utils import FlashMessageUtil, get_object_or_none, random_string
from common.utils.verify_code import SendAndVerifyCodeUtil from common.utils.verify_code import SendAndVerifyCodeUtil
from users.serializers import SmsUserSerializer
from users.notifications import ResetPasswordSuccessMsg from users.notifications import ResetPasswordSuccessMsg
from ... import forms from ... import forms
from ...models import User from ...models import User
@ -51,8 +52,7 @@ class UserForgotPasswordPreviewingView(FormView):
if token_sent_at: if token_sent_at:
raise IntervalTooShort(sent_ttl) raise IntervalTooShort(sent_ttl)
token = random_string(36) token = random_string(36)
user_info = {'username': user.username, 'phone': user.phone, 'email': user.email} cache.set(token, SmsUserSerializer(user).data, 5 * 60)
cache.set(token, user_info, 5 * 60)
cache.set(token_sent_at_key, time.time(), sent_ttl) cache.set(token_sent_at_key, time.time(), sent_ttl)
return token return token