diff --git a/apps/authentication/api/password.py b/apps/authentication/api/password.py index f721ed2a8..3453a6d8b 100644 --- a/apps/authentication/api/password.py +++ b/apps/authentication/api/password.py @@ -1,5 +1,6 @@ import time +from django.conf import settings from django.core.cache import cache from django.http import HttpResponseRedirect from django.shortcuts import reverse @@ -40,12 +41,15 @@ class UserResetPasswordSendCodeApi(CreateAPIView): return user, None @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_send_at = cache.get(token_sent_key, 0) if token_send_at: 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) def prepare_code_data(self, user_info, serializer): @@ -61,7 +65,7 @@ class UserResetPasswordSendCodeApi(CreateAPIView): if not user: 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')) context = { '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) except ValueError as e: 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) diff --git a/apps/authentication/mfa/sms.py b/apps/authentication/mfa/sms.py index 7cf71985e..f88e56d35 100644 --- a/apps/authentication/mfa/sms.py +++ b/apps/authentication/mfa/sms.py @@ -2,6 +2,7 @@ from django.conf import settings from django.utils.translation import gettext_lazy as _ from common.utils.verify_code import SendAndVerifyCodeUtil +from users.serializers import SmsUserSerializer from .base import BaseMFA sms_failed_msg = _("SMS verify code invalid") @@ -14,8 +15,13 @@ class MFASms(BaseMFA): def __init__(self, user): super().__init__(user) - phone = user.phone if self.is_authenticated() else '' - self.sms = SendAndVerifyCodeUtil(phone, backend=self.name) + phone, user_info = '', None + 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): assert self.is_authenticated() diff --git a/apps/common/sdk/sms/endpoint.py b/apps/common/sdk/sms/endpoint.py index 7618d6da9..d0bdb1bcf 100644 --- a/apps/common/sdk/sms/endpoint.py +++ b/apps/common/sdk/sms/endpoint.py @@ -43,7 +43,7 @@ class SMS: **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', '') sign_name = getattr(settings, f'{prefix}_VERIFY_SIGN_NAME', None) template_code = getattr(settings, f'{prefix}_VERIFY_TEMPLATE_CODE', None) @@ -53,4 +53,7 @@ class SMS: code='verify_code_sign_tmpl_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 + ) diff --git a/apps/common/utils/verify_code.py b/apps/common/utils/verify_code.py index a5f6c668c..00a6b75bd 100644 --- a/apps/common/utils/verify_code.py +++ b/apps/common/utils/verify_code.py @@ -20,16 +20,20 @@ logger = get_logger(__file__) be executed to send SMS messages""" ) ) -def send_sms_async(target, code): - SMS().send_verify_code(target, code) +def send_sms_async(target, code, user_info): + SMS().send_verify_code(target, code, user_info=user_info) class SendAndVerifyCodeUtil(object): 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.target = target + self.user_info = user_info self.backend = backend self.key = key or self.KEY_TMPL.format(target) self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout @@ -78,7 +82,7 @@ class SendAndVerifyCodeUtil(object): return code 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): subject = self.other_args.get('subject', '') diff --git a/apps/users/forms/profile.py b/apps/users/forms/profile.py index 9a99c74fd..2b2e9a703 100644 --- a/apps/users/forms/profile.py +++ b/apps/users/forms/profile.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # from django import forms +from django.conf import settings from django.utils.translation import gettext_lazy as _ from common.utils import validate_ssh_public_key @@ -103,7 +104,9 @@ class UserForgotPasswordForm(forms.Form): label=_('SMS'), required=False, 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( choices=[('sms', _('SMS')), ('email', _('Email'))], widget=forms.HiddenInput({'value': 'email'}) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 9343af38f..7d4632485 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # - from functools import partial from django.conf import settings @@ -27,6 +26,7 @@ from ..models import User __all__ = [ "UserSerializer", + "SmsUserSerializer", "MiniUserSerializer", "InviteSerializer", "ServiceAccountSerializer", @@ -411,6 +411,14 @@ class UserRetrieveSerializer(UserSerializer): 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 Meta: model = User diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index e62d043b1..561816f03 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -16,6 +16,7 @@ from authentication.utils import check_user_property_is_correct from common.const.choices import COUNTRY_CALLING_CODES from common.utils import FlashMessageUtil, get_object_or_none, random_string from common.utils.verify_code import SendAndVerifyCodeUtil +from users.serializers import SmsUserSerializer from users.notifications import ResetPasswordSuccessMsg from ... import forms from ...models import User @@ -51,8 +52,7 @@ class UserForgotPasswordPreviewingView(FormView): if token_sent_at: raise IntervalTooShort(sent_ttl) token = random_string(36) - user_info = {'username': user.username, 'phone': user.phone, 'email': user.email} - cache.set(token, user_info, 5 * 60) + cache.set(token, SmsUserSerializer(user).data, 5 * 60) cache.set(token_sent_at_key, time.time(), sent_ttl) return token