feat: 忘记密码支持手机短信找回,并修改邮箱方式和手机方式统一 (#8960)

* feat: 忘记密码支持通过手机找回,邮箱方式修改为和手机方式一致

* feat: 翻译

* feat: 修改翻译

* fix: 还原

Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
pull/8941/head^2
jiangweidong 2022-11-04 13:56:55 +08:00 committed by GitHub
parent 262d070f3c
commit 1e97a23bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 583 additions and 281 deletions

View File

@ -1,13 +1,73 @@
from rest_framework.generics import CreateAPIView from rest_framework.generics import CreateAPIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from authentication.serializers import PasswordVerifySerializer from common.utils.verify_code import SendAndVerifyCodeUtil
from common.permissions import IsValidUser from common.permissions import IsValidUser
from common.utils.random import random_string
from common.utils import get_object_or_none
from authentication.serializers import (
PasswordVerifySerializer, ResetPasswordCodeSerializer
)
from settings.utils import get_login_title
from users.models import User
from authentication.mixins import authenticate from authentication.mixins import authenticate
from authentication.errors import PasswordInvalid from authentication.errors import PasswordInvalid
from authentication.mixins import AuthMixin from authentication.mixins import AuthMixin
class UserResetPasswordSendCodeApi(CreateAPIView):
permission_classes = (AllowAny,)
serializer_class = ResetPasswordCodeSerializer
@staticmethod
def is_valid_user( **kwargs):
user = get_object_or_none(User, **kwargs)
if not user:
err_msg = _('User does not exist: {}').format(_("No user matched"))
return None, err_msg
if not user.is_local:
err_msg = _(
'The user is from {}, please go to the corresponding system to change the password'
).format(user.get_source_display())
return None, err_msg
return user, None
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
form_type = serializer.validated_data['form_type']
username = serializer.validated_data['username']
code = random_string(6, lower=False, upper=False)
other_args = {}
if form_type == 'phone':
backend = 'sms'
target = serializer.validated_data['phone']
user, err = self.is_valid_user(username=username, phone=target)
if not user:
return Response({'error': err}, status=400)
else:
backend = 'email'
target = serializer.validated_data['email']
user, err = self.is_valid_user(username=username, email=target)
if not user:
return Response({'error': err}, status=400)
subject = '%s: %s' % (get_login_title(), _('Forgot password'))
context = {
'user': user, 'title': subject, 'code': code,
}
message = render_to_string('authentication/_msg_reset_password_code.html', context)
other_args['subject'] = subject
other_args['message'] = message
SendAndVerifyCodeUtil(target, code, backend=backend, **other_args).gen_and_send_async()
return Response({'data': 'ok'}, status=200)
class UserPasswordVerifyApi(AuthMixin, CreateAPIView): class UserPasswordVerifyApi(AuthMixin, CreateAPIView):
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
serializer_class = PasswordVerifySerializer serializer_class = PasswordVerifySerializer

View File

@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from .base import BaseMFA from .base import BaseMFA
from common.sdk.sms import SendAndVerifySMSUtil from common.utils.verify_code import SendAndVerifyCodeUtil
sms_failed_msg = _("SMS verify code invalid") sms_failed_msg = _("SMS verify code invalid")
@ -15,7 +15,7 @@ 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.phone if self.is_authenticated() else ''
self.sms = SendAndVerifySMSUtil(phone) self.sms = SendAndVerifyCodeUtil(phone, backend=self.name)
def check_code(self, code): def check_code(self, code):
assert self.is_authenticated() assert self.is_authenticated()
@ -37,7 +37,7 @@ class MFASms(BaseMFA):
return True return True
def send_challenge(self): def send_challenge(self):
self.sms.gen_and_send() self.sms.gen_and_send_async()
@staticmethod @staticmethod
def global_enabled(): def global_enabled():

View File

@ -1,15 +1,41 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.drf.fields import EncryptedField from common.drf.fields import EncryptedField
__all__ = [ __all__ = [
'MFAChallengeSerializer', 'MFASelectTypeSerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer',
'PasswordVerifySerializer', 'PasswordVerifySerializer', 'ResetPasswordCodeSerializer',
] ]
class ResetPasswordCodeSerializer(serializers.Serializer):
form_type = serializers.CharField(default='email')
username = serializers.CharField()
email = serializers.CharField(allow_blank=True)
phone = serializers.CharField(allow_blank=True)
def create(self, attrs):
error = []
form_type = attrs.get('form_type', 'email')
username = attrs.get('username')
if not username:
error.append(_('The {} cannot be empty').format(_('Username')))
if form_type == 'phone':
phone = attrs.get('phone')
if not phone:
error.append(_('The {} cannot be empty').format(_('Phone')))
else:
email = attrs.get('email')
if not email:
error.append(_('The {} cannot be empty').format(_('Email')))
if error:
raise serializers.ValidationError(error)
class PasswordVerifySerializer(serializers.Serializer): class PasswordVerifySerializer(serializers.Serializer):
password = EncryptedField() password = EncryptedField()

View File

@ -0,0 +1,21 @@
{% load i18n %}
<div style="width: 100%; text-align: center">
<table style="margin: 0 auto; border: 1px solid #ccc; border-collapse: collapse; width: 60%">
<tr style="background-color: #1ab394; color: white">
<th style="height: 80px;">{{ title }}</th>
</tr>
<tr style="border: 1px solid #eee;">
<td style="height: 50px;">{% trans 'Hello' %} {{ user.name }},</td>
</tr>
<tr style="border: 1px solid #eee">
<td style="height: 50px;">{% trans 'Verify code' %}: <span style="font-weight: bold;">{{ code }}</span></td>
</tr>
<tr style="border: 1px solid #eee;">
<td style="height: 30px;"> {% trans 'Copy the verification code to the Reset Password page to reset the password.' %} </td>
</tr>
<tr style="border: 1px solid #eee">
<td style="height: 30px;">{% trans 'The validity period of the verification code is one minute' %}</td>
</tr>
</table>
</div>

View File

@ -32,7 +32,8 @@ urlpatterns = [
path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'), path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'),
path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'), path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'),
path('mfa/select/', api.MFASendCodeApi.as_view(), name='mfa-select'), path('mfa/select/', api.MFASendCodeApi.as_view(), name='mfa-select'),
path('mfa/send-code/', api.MFASendCodeApi.as_view(), name='mfa-send-codej'), path('mfa/send-code/', api.MFASendCodeApi.as_view(), name='mfa-send-code'),
path('password/reset-code/', api.UserResetPasswordSendCodeApi.as_view(), name='reset-password-code'),
path('password/verify/', api.UserPasswordVerifyApi.as_view(), name='user-password-verify'), path('password/verify/', api.UserPasswordVerifyApi.as_view(), name='user-password-verify'),
path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'), path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'),
] ]

View File

@ -1,2 +1 @@
from .endpoint import SMS, BACKENDS from .endpoint import SMS, BACKENDS
from .utils import SendAndVerifySMSUtil

View File

@ -0,0 +1,21 @@
from django.utils.translation import gettext_lazy as _
from common.exceptions import JMSException
class CodeExpired(JMSException):
default_code = 'verify_code_expired'
default_detail = _('The verification code has expired. Please resend it')
class CodeError(JMSException):
default_code = 'verify_code_error'
default_detail = _('The verification code is incorrect')
class CodeSendTooFrequently(JMSException):
default_code = 'code_send_too_frequently'
default_detail = _('Please wait {} seconds before sending')
def __init__(self, ttl):
super().__init__(detail=self.default_detail.format(ttl))

View File

@ -1,90 +0,0 @@
import random
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _
from .endpoint import SMS
from common.utils import get_logger
from common.exceptions import JMSException
logger = get_logger(__file__)
class CodeExpired(JMSException):
default_code = 'verify_code_expired'
default_detail = _('The verification code has expired. Please resend it')
class CodeError(JMSException):
default_code = 'verify_code_error'
default_detail = _('The verification code is incorrect')
class CodeSendTooFrequently(JMSException):
default_code = 'code_send_too_frequently'
default_detail = _('Please wait {} seconds before sending')
def __init__(self, ttl):
super().__init__(detail=self.default_detail.format(ttl))
class SendAndVerifySMSUtil:
KEY_TMPL = 'auth-verify-code-{}'
TIMEOUT = 60
def __init__(self, phone, key_suffix=None, timeout=None):
self.phone = phone
self.code = ''
self.timeout = timeout or self.TIMEOUT
self.key_suffix = key_suffix or str(phone)
self.key = self.KEY_TMPL.format(self.key_suffix)
def gen_and_send(self):
"""
生成保存发送
"""
ttl = self.ttl()
if ttl > 0:
logger.error('Send sms too frequently, delay {}'.format(ttl))
raise CodeSendTooFrequently(ttl)
try:
code = self.generate()
self.send(code)
except JMSException:
self.clear()
raise
def generate(self):
code = ''.join(random.sample('0123456789', 4))
self.code = code
return code
def clear(self):
cache.delete(self.key)
def send(self, code):
"""
发送信息的方法如果有错误直接抛出 api 异常
"""
sms = SMS()
sms.send_verify_code(self.phone, code)
cache.set(self.key, self.code, self.timeout)
logger.info(f'Send sms verify code to {self.phone}: {code}')
def verify(self, code):
right = cache.get(self.key)
if not right:
raise CodeExpired
if right != code:
raise CodeError
self.clear()
return True
def ttl(self):
return cache.ttl(self.key)
def get_code(self):
return cache.get(self.key)

View File

@ -0,0 +1,94 @@
from django.core.cache import cache
from django.conf import settings
from django.core.mail import send_mail
from celery import shared_task
from common.sdk.sms.exceptions import CodeError, CodeExpired, CodeSendTooFrequently
from common.sdk.sms.endpoint import SMS
from common.exceptions import JMSException
from common.utils.random import random_string
from common.utils import get_logger
logger = get_logger(__file__)
@shared_task
def send_async(sender):
sender.gen_and_send()
class SendAndVerifyCodeUtil(object):
KEY_TMPL = 'auth-verify-code-{}'
def __init__(self, target, code=None, key=None, backend='email', timeout=60, **kwargs):
self.target = target
self.code = code
self.timeout = timeout
self.backend = backend
self.key = key or self.KEY_TMPL.format(target)
self.other_args = kwargs
def gen_and_send_async(self):
return send_async.delay(self)
def gen_and_send(self):
ttl = self.__ttl()
if ttl > 0:
logger.error('Send sms too frequently, delay {}'.format(ttl))
raise CodeSendTooFrequently(ttl)
try:
if not self.code:
self.code = self.__generate()
self.__send(self.code)
except JMSException:
self.__clear()
raise
def verify(self, code):
right = cache.get(self.key)
if not right:
raise CodeExpired
if right != code:
raise CodeError
self.__clear()
return True
def __clear(self):
cache.delete(self.key)
def __ttl(self):
return cache.ttl(self.key)
def __get_code(self):
return cache.get(self.key)
def __generate(self):
code = random_string(4, lower=False, upper=False)
self.code = code
return code
def __send_with_sms(self):
sms = SMS()
sms.send_verify_code(self.target, self.code)
def __send_with_email(self):
subject = self.other_args.get('subject')
message = self.other_args.get('message')
from_email = settings.EMAIL_FROM or settings.EMAIL_HOST_USER
send_mail(subject, message, from_email, [self.target], html_message=message)
def __send(self, code):
"""
发送信息的方法如果有错误直接抛出 api 异常
"""
if self.backend == 'sms':
self.__send_with_sms()
else:
self.__send_with_email()
cache.set(self.key, self.code, self.timeout)
logger.info(f'Send verify code to {self.target}: {code}')

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-28 09:17+0800\n" "POT-Creation-Date: 2022-11-04 11:37+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -157,11 +157,13 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: assets/models/gathered_user.py:15 audits/models.py:121 #: assets/models/gathered_user.py:15 audits/models.py:121
#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 #: authentication/models.py:260 authentication/serializers/password_mfa.py:25
#: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:667 #: ops/models/adhoc.py:159 users/forms/profile.py:32 users/forms/profile.py:102
#: users/templates/users/_msg_user_created.html:12 #: users/models/user.py:667 users/templates/users/_msg_user_created.html:12
#: users/templates/users/forgot_password.html:51
#: users/templates/users/forgot_password.html:98
#: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:34
#: xpack/plugins/change_auth_plan/models/asset.py:195 #: xpack/plugins/change_auth_plan/models/asset.py:195
#: xpack/plugins/cloud/serializers/account_attrs.py:26 #: xpack/plugins/cloud/serializers/account_attrs.py:26
@ -837,7 +839,10 @@ msgstr "帯域幅"
msgid "Contact" msgid "Contact"
msgstr "連絡先" msgstr "連絡先"
#: assets/models/cluster.py:22 users/models/user.py:689 #: assets/models/cluster.py:22 authentication/serializers/password_mfa.py:29
#: users/forms/profile.py:104 users/models/user.py:689
#: users/templates/users/forgot_password.html:59
#: users/templates/users/forgot_password.html:103
msgid "Phone" msgid "Phone"
msgstr "電話" msgstr "電話"
@ -1824,6 +1829,30 @@ msgstr "この操作には、MFAを検証する必要があります"
msgid "Current user not support mfa type: {}" msgid "Current user not support mfa type: {}"
msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" msgstr "現在のユーザーはmfaタイプをサポートしていません: {}"
#: authentication/api/password.py:29 terminal/api/session.py:224
#: users/views/profile/reset.py:74
msgid "User does not exist: {}"
msgstr "ユーザーが存在しない: {}"
#: authentication/api/password.py:29
msgid "No user matched"
msgstr "ユーザーにマッチしなかった"
#: authentication/api/password.py:33
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
msgstr ""
"ユーザーは {}からです。対応するシステムにアクセスしてパスワードを変更してくだ"
"さい。"
#: authentication/api/password.py:59
#: authentication/templates/authentication/login.html:256
#: users/templates/users/forgot_password.html:33
#: users/templates/users/forgot_password.html:34
msgid "Forgot password"
msgstr "パスワードを忘れた"
#: authentication/apps.py:7 #: authentication/apps.py:7
msgid "Authentication" msgid "Authentication"
msgstr "認証" msgstr "認証"
@ -2246,6 +2275,20 @@ msgstr "期限切れ時間"
msgid "Asset or application required" msgid "Asset or application required"
msgstr "アセットまたはアプリが必要" msgstr "アセットまたはアプリが必要"
#: authentication/serializers/password_mfa.py:25
#: authentication/serializers/password_mfa.py:29
#: authentication/serializers/password_mfa.py:33
#: users/templates/users/forgot_password.html:95
msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない"
#: authentication/serializers/password_mfa.py:33
#: notifications/backends/__init__.py:10 users/forms/profile.py:103
#: users/models/user.py:671 users/templates/users/forgot_password.html:55
#: users/templates/users/forgot_password.html:108
msgid "Email"
msgstr "メール"
#: authentication/serializers/token.py:79 #: authentication/serializers/token.py:79
#: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:20
#: perms/serializers/application/permission.py:41 #: perms/serializers/application/permission.py:41
@ -2309,7 +2352,6 @@ msgid "Play CAPTCHA as audio file"
msgstr "CAPTCHAをオーディオファイルとして再生する" msgstr "CAPTCHAをオーディオファイルとして再生する"
#: authentication/templates/authentication/_captcha_field.html:15 #: authentication/templates/authentication/_captcha_field.html:15
#: users/forms/profile.py:103
msgid "Captcha" msgid "Captcha"
msgstr "キャプチャ" msgstr "キャプチャ"
@ -2335,6 +2377,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_different_city.html:3 #: authentication/templates/authentication/_msg_different_city.html:3
#: authentication/templates/authentication/_msg_oauth_bind.html:3 #: authentication/templates/authentication/_msg_oauth_bind.html:3
#: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:402 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:402 ops/tasks.py:145 ops/tasks.py:148
@ -2394,6 +2437,21 @@ msgstr "このリンクは1時間有効です。有効期限が切れた後"
msgid "request new one" msgid "request new one"
msgstr "新しいものを要求する" msgstr "新しいものを要求する"
#: authentication/templates/authentication/_msg_reset_password_code.html:12
#: terminal/models/sharing.py:26 terminal/models/sharing.py:80
#: users/forms/profile.py:105 users/templates/users/forgot_password.html:63
msgid "Verify code"
msgstr "コードの確認"
#: authentication/templates/authentication/_msg_reset_password_code.html:15
msgid ""
"Copy the verification code to the Reset Password page to reset the password."
msgstr "パスワードリセットページにcaptchaをコピーし、パスワードをリセットする"
#: authentication/templates/authentication/_msg_reset_password_code.html:18
msgid "The validity period of the verification code is one minute"
msgstr "認証コードの有効時間は 1 分"
#: authentication/templates/authentication/_msg_rest_password_success.html:5 #: authentication/templates/authentication/_msg_rest_password_success.html:5
msgid "Your password has just been successfully updated" msgid "Your password has just been successfully updated"
msgstr "パスワードが正常に更新されました" msgstr "パスワードが正常に更新されました"
@ -2438,12 +2496,6 @@ msgid "Welcome back, please enter username and password to login"
msgstr "" msgstr ""
"おかえりなさい、ログインするためにユーザー名とパスワードを入力してください" "おかえりなさい、ログインするためにユーザー名とパスワードを入力してください"
#: authentication/templates/authentication/login.html:256
#: users/templates/users/forgot_password.html:16
#: users/templates/users/forgot_password.html:17
msgid "Forgot password"
msgstr "パスワードを忘れた"
#: authentication/templates/authentication/login.html:264 #: authentication/templates/authentication/login.html:264
#: templates/_header_bar.html:89 #: templates/_header_bar.html:89
msgid "Login" msgid "Login"
@ -2776,15 +2828,15 @@ msgstr "SMSプロバイダーはサポートしていません: {}"
msgid "SMS verification code signature or template invalid" msgid "SMS verification code signature or template invalid"
msgstr "SMS検証コードの署名またはテンプレートが無効" msgstr "SMS検証コードの署名またはテンプレートが無効"
#: common/sdk/sms/utils.py:15 #: common/sdk/sms/exceptions.py:8
msgid "The verification code has expired. Please resend it" msgid "The verification code has expired. Please resend it"
msgstr "確認コードの有効期限が切れています。再送信してください" msgstr "確認コードの有効期限が切れています。再送信してください"
#: common/sdk/sms/utils.py:20 #: common/sdk/sms/exceptions.py:13
msgid "The verification code is incorrect" msgid "The verification code is incorrect"
msgstr "確認コードが正しくありません" msgstr "確認コードが正しくありません"
#: common/sdk/sms/utils.py:25 #: common/sdk/sms/exceptions.py:18
msgid "Please wait {} seconds before sending" msgid "Please wait {} seconds before sending"
msgstr "{} 秒待ってから送信してください" msgstr "{} 秒待ってから送信してください"
@ -2852,11 +2904,6 @@ msgstr ""
msgid "Notifications" msgid "Notifications"
msgstr "通知" msgstr "通知"
#: notifications/backends/__init__.py:10 users/forms/profile.py:102
#: users/models/user.py:671
msgid "Email"
msgstr "メール"
#: notifications/backends/__init__.py:13 #: notifications/backends/__init__.py:13
msgid "Site message" msgid "Site message"
msgstr "サイトメッセージ" msgstr "サイトメッセージ"
@ -4750,14 +4797,17 @@ msgstr ""
" " " "
#: templates/_mfa_login_field.html:28 #: templates/_mfa_login_field.html:28
#: users/templates/users/forgot_password.html:65
msgid "Send verification code" msgid "Send verification code"
msgstr "確認コードを送信" msgstr "確認コードを送信"
#: templates/_mfa_login_field.html:106 #: templates/_mfa_login_field.html:106
#: users/templates/users/forgot_password.html:122
msgid "Wait: " msgid "Wait: "
msgstr "待つ:" msgstr "待つ:"
#: templates/_mfa_login_field.html:116 #: templates/_mfa_login_field.html:116
#: users/templates/users/forgot_password.html:138
msgid "The verification code has been sent" msgid "The verification code has been sent"
msgstr "確認コードが送信されました" msgstr "確認コードが送信されました"
@ -4822,10 +4872,6 @@ msgstr "セッションが存在しません: {}"
msgid "Session is finished or the protocol not supported" msgid "Session is finished or the protocol not supported"
msgstr "セッションが終了したか、プロトコルがサポートされていません" msgstr "セッションが終了したか、プロトコルがサポートされていません"
#: terminal/api/session.py:224
msgid "User does not exist: {}"
msgstr "ユーザーが存在しない: {}"
#: terminal/api/session.py:232 #: terminal/api/session.py:232
msgid "User does not have permission" msgid "User does not have permission"
msgstr "ユーザーに権限がありません" msgstr "ユーザーに権限がありません"
@ -5030,10 +5076,6 @@ msgstr "セッションアクションのパーマを検証できます"
msgid "Creator" msgid "Creator"
msgstr "作成者" msgstr "作成者"
#: terminal/models/sharing.py:26 terminal/models/sharing.py:80
msgid "Verify code"
msgstr "コードの確認"
#: terminal/models/sharing.py:31 #: terminal/models/sharing.py:31
msgid "Expired time (min)" msgid "Expired time (min)"
msgstr "期限切れ時間 (分)" msgstr "期限切れ時間 (分)"
@ -5320,7 +5362,7 @@ msgstr ""
"使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の" "使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の"
"制限を確認して変更してください. " "制限を確認して変更してください. "
#: terminal/utils/db_port_mapper.py:92 #: terminal/utils/db_port_mapper.py:83
msgid "All available port count: {}, Already use port count: {}" msgid "All available port count: {}, Already use port count: {}"
msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}" msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}"
@ -5764,40 +5806,40 @@ msgstr "パスワードの確認"
msgid "Password does not match" msgid "Password does not match"
msgstr "パスワードが一致しない" msgstr "パスワードが一致しない"
#: users/forms/profile.py:109 #: users/forms/profile.py:112
msgid "Old password" msgid "Old password"
msgstr "古いパスワード" msgstr "古いパスワード"
#: users/forms/profile.py:119 #: users/forms/profile.py:122
msgid "Old password error" msgid "Old password error"
msgstr "古いパスワードエラー" msgstr "古いパスワードエラー"
#: users/forms/profile.py:129 #: users/forms/profile.py:132
msgid "Automatically configure and download the SSH key" msgid "Automatically configure and download the SSH key"
msgstr "SSHキーの自動設定とダウンロード" msgstr "SSHキーの自動設定とダウンロード"
#: users/forms/profile.py:131 #: users/forms/profile.py:134
msgid "ssh public key" msgid "ssh public key"
msgstr "ssh公開キー" msgstr "ssh公開キー"
#: users/forms/profile.py:132 #: users/forms/profile.py:135
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "ssh-rsa AAAA.." msgstr "ssh-rsa AAAA.."
#: users/forms/profile.py:133 #: users/forms/profile.py:136
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "ここにid_rsa.pubを貼り付けます。" msgstr "ここにid_rsa.pubを貼り付けます。"
#: users/forms/profile.py:146 #: users/forms/profile.py:149
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "公開鍵は古いものと同じであってはなりません。" msgstr "公開鍵は古いものと同じであってはなりません。"
#: users/forms/profile.py:150 users/serializers/profile.py:100 #: users/forms/profile.py:153 users/serializers/profile.py:100
#: users/serializers/profile.py:183 users/serializers/profile.py:210 #: users/serializers/profile.py:183 users/serializers/profile.py:210
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "有効なssh公開鍵ではありません" msgstr "有効なssh公開鍵ではありません"
#: users/forms/profile.py:161 users/models/user.py:700 #: users/forms/profile.py:164 users/models/user.py:700
msgid "Public key" msgid "Public key"
msgstr "公開キー" msgstr "公開キー"
@ -5868,7 +5910,7 @@ msgstr "ユーザーパスワード履歴"
msgid "Reset password" msgid "Reset password"
msgstr "パスワードのリセット" msgstr "パスワードのリセット"
#: users/notifications.py:85 users/views/profile/reset.py:127 #: users/notifications.py:85 users/views/profile/reset.py:139
msgid "Reset password success" msgid "Reset password success"
msgstr "パスワードのリセット成功" msgstr "パスワードのリセット成功"
@ -6064,14 +6106,22 @@ msgstr "あなたのssh公開鍵はサイト管理者によってリセットさ
msgid "click here to set your password" msgid "click here to set your password"
msgstr "ここをクリックしてパスワードを設定してください" msgstr "ここをクリックしてパスワードを設定してください"
#: users/templates/users/forgot_password.html:24 #: users/templates/users/forgot_password.html:38
msgid "Input your email, that will send a mail to your" msgid "Input your email, that will send a mail to your"
msgstr "あなたのメールを入力し、それはあなたにメールを送信します" msgstr "あなたのメールを入力し、それはあなたにメールを送信します"
#: users/templates/users/forgot_password.html:33 #: users/templates/users/forgot_password.html:68
msgid "Submit" msgid "Submit"
msgstr "送信" msgstr "送信"
#: users/templates/users/forgot_password.html:71
msgid "Use the phone number to retrieve the password"
msgstr "携帯電話番号を使ってパスワードを探す"
#: users/templates/users/forgot_password.html:72
msgid "Use email to retrieve the password"
msgstr "メールアドレスを使ってパスワードを取り戻す"
#: users/templates/users/mfa_setting.html:24 #: users/templates/users/mfa_setting.html:24
msgid "Enable MFA" msgid "Enable MFA"
msgstr "MFAの有効化" msgstr "MFAの有効化"
@ -6218,45 +6268,23 @@ msgstr "OTP無効化成功、ログインページを返す"
msgid "Password invalid" msgid "Password invalid"
msgstr "パスワード無効" msgstr "パスワード無効"
#: users/views/profile/reset.py:40 #: users/views/profile/reset.py:96 users/views/profile/reset.py:107
msgid "Send reset password message"
msgstr "リセットパスワードメッセージを送信"
#: users/views/profile/reset.py:41
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"リセットパスワードメールの成功を送信し、メールボックスにログインしてそれに従"
"う"
#: users/views/profile/reset.py:52
msgid "Email address invalid, please input again"
msgstr "メールアドレスが無効です。再度入力してください"
#: users/views/profile/reset.py:58
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
msgstr ""
"ユーザーは {}からです。対応するシステムにアクセスしてパスワードを変更してくだ"
"さい。"
#: users/views/profile/reset.py:84 users/views/profile/reset.py:95
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "トークンが無効または期限切れ" msgstr "トークンが無効または期限切れ"
#: users/views/profile/reset.py:100 #: users/views/profile/reset.py:112
msgid "User auth from {}, go there change password" msgid "User auth from {}, go there change password"
msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください" msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください"
#: users/views/profile/reset.py:107 #: users/views/profile/reset.py:119
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* パスワードが要件を満たしていない" msgstr "* パスワードが要件を満たしていない"
#: users/views/profile/reset.py:113 #: users/views/profile/reset.py:125
msgid "* The new password cannot be the last {} passwords" msgid "* The new password cannot be the last {} passwords"
msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません" msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません"
#: users/views/profile/reset.py:128 #: users/views/profile/reset.py:140
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "パスワードの成功をリセットし、ログインページに戻る" msgstr "パスワードの成功をリセットし、ログインページに戻る"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-28 09:17+0800\n" "POT-Creation-Date: 2022-11-04 11:37+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -156,11 +156,13 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: assets/models/gathered_user.py:15 audits/models.py:121 #: assets/models/gathered_user.py:15 audits/models.py:121
#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 #: authentication/models.py:260 authentication/serializers/password_mfa.py:25
#: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:667 #: ops/models/adhoc.py:159 users/forms/profile.py:32 users/forms/profile.py:102
#: users/templates/users/_msg_user_created.html:12 #: users/models/user.py:667 users/templates/users/_msg_user_created.html:12
#: users/templates/users/forgot_password.html:51
#: users/templates/users/forgot_password.html:98
#: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:34
#: xpack/plugins/change_auth_plan/models/asset.py:195 #: xpack/plugins/change_auth_plan/models/asset.py:195
#: xpack/plugins/cloud/serializers/account_attrs.py:26 #: xpack/plugins/cloud/serializers/account_attrs.py:26
@ -832,7 +834,10 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:689 #: assets/models/cluster.py:22 authentication/serializers/password_mfa.py:29
#: users/forms/profile.py:104 users/models/user.py:689
#: users/templates/users/forgot_password.html:59
#: users/templates/users/forgot_password.html:103
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
@ -1812,6 +1817,28 @@ msgstr "此操作需要验证您的 MFA"
msgid "Current user not support mfa type: {}" msgid "Current user not support mfa type: {}"
msgstr "当前用户不支持 MFA 类型: {}" msgstr "当前用户不支持 MFA 类型: {}"
#: authentication/api/password.py:29 terminal/api/session.py:224
#: users/views/profile/reset.py:74
msgid "User does not exist: {}"
msgstr "用户不存在: {}"
#: authentication/api/password.py:29
msgid "No user matched"
msgstr "没有匹配到用户"
#: authentication/api/password.py:33
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
msgstr "用户来自 {} 请去相应系统修改密码"
#: authentication/api/password.py:59
#: authentication/templates/authentication/login.html:256
#: users/templates/users/forgot_password.html:33
#: users/templates/users/forgot_password.html:34
msgid "Forgot password"
msgstr "忘记密码"
#: authentication/apps.py:7 #: authentication/apps.py:7
msgid "Authentication" msgid "Authentication"
msgstr "认证" msgstr "认证"
@ -2221,6 +2248,20 @@ msgstr "过期时间"
msgid "Asset or application required" msgid "Asset or application required"
msgstr "资产或应用必填" msgstr "资产或应用必填"
#: authentication/serializers/password_mfa.py:25
#: authentication/serializers/password_mfa.py:29
#: authentication/serializers/password_mfa.py:33
#: users/templates/users/forgot_password.html:95
msgid "The {} cannot be empty"
msgstr "{} 不能为空"
#: authentication/serializers/password_mfa.py:33
#: notifications/backends/__init__.py:10 users/forms/profile.py:103
#: users/models/user.py:671 users/templates/users/forgot_password.html:55
#: users/templates/users/forgot_password.html:108
msgid "Email"
msgstr "邮件"
#: authentication/serializers/token.py:79 #: authentication/serializers/token.py:79
#: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:20
#: perms/serializers/application/permission.py:41 #: perms/serializers/application/permission.py:41
@ -2284,7 +2325,6 @@ msgid "Play CAPTCHA as audio file"
msgstr "语言播放验证码" msgstr "语言播放验证码"
#: authentication/templates/authentication/_captcha_field.html:15 #: authentication/templates/authentication/_captcha_field.html:15
#: users/forms/profile.py:103
msgid "Captcha" msgid "Captcha"
msgstr "验证码" msgstr "验证码"
@ -2310,6 +2350,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_different_city.html:3 #: authentication/templates/authentication/_msg_different_city.html:3
#: authentication/templates/authentication/_msg_oauth_bind.html:3 #: authentication/templates/authentication/_msg_oauth_bind.html:3
#: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:402 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:402 ops/tasks.py:145 ops/tasks.py:148
@ -2365,6 +2406,21 @@ msgstr "这个链接有效期1小时, 超过时间您可以"
msgid "request new one" msgid "request new one"
msgstr "重新申请" msgstr "重新申请"
#: authentication/templates/authentication/_msg_reset_password_code.html:12
#: terminal/models/sharing.py:26 terminal/models/sharing.py:80
#: users/forms/profile.py:105 users/templates/users/forgot_password.html:63
msgid "Verify code"
msgstr "验证码"
#: authentication/templates/authentication/_msg_reset_password_code.html:15
msgid ""
"Copy the verification code to the Reset Password page to reset the password."
msgstr "将验证码复制到重置密码页面,重置密码。"
#: authentication/templates/authentication/_msg_reset_password_code.html:18
msgid "The validity period of the verification code is one minute"
msgstr "验证码有效期为 1 分钟"
#: authentication/templates/authentication/_msg_rest_password_success.html:5 #: authentication/templates/authentication/_msg_rest_password_success.html:5
msgid "Your password has just been successfully updated" msgid "Your password has just been successfully updated"
msgstr "你的密码刚刚成功更新" msgstr "你的密码刚刚成功更新"
@ -2404,12 +2460,6 @@ msgstr "取消"
msgid "Welcome back, please enter username and password to login" msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录" msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/templates/authentication/login.html:256
#: users/templates/users/forgot_password.html:16
#: users/templates/users/forgot_password.html:17
msgid "Forgot password"
msgstr "忘记密码"
#: authentication/templates/authentication/login.html:264 #: authentication/templates/authentication/login.html:264
#: templates/_header_bar.html:89 #: templates/_header_bar.html:89
msgid "Login" msgid "Login"
@ -2742,15 +2792,15 @@ msgstr "短信服务商不支持:{}"
msgid "SMS verification code signature or template invalid" msgid "SMS verification code signature or template invalid"
msgstr "短信验证码签名或模版无效" msgstr "短信验证码签名或模版无效"
#: common/sdk/sms/utils.py:15 #: common/sdk/sms/exceptions.py:8
msgid "The verification code has expired. Please resend it" msgid "The verification code has expired. Please resend it"
msgstr "验证码已过期,请重新发送" msgstr "验证码已过期,请重新发送"
#: common/sdk/sms/utils.py:20 #: common/sdk/sms/exceptions.py:13
msgid "The verification code is incorrect" msgid "The verification code is incorrect"
msgstr "验证码错误" msgstr "验证码错误"
#: common/sdk/sms/utils.py:25 #: common/sdk/sms/exceptions.py:18
msgid "Please wait {} seconds before sending" msgid "Please wait {} seconds before sending"
msgstr "请在 {} 秒后发送" msgstr "请在 {} 秒后发送"
@ -2813,11 +2863,6 @@ msgstr ""
msgid "Notifications" msgid "Notifications"
msgstr "通知" msgstr "通知"
#: notifications/backends/__init__.py:10 users/forms/profile.py:102
#: users/models/user.py:671
msgid "Email"
msgstr "邮件"
#: notifications/backends/__init__.py:13 #: notifications/backends/__init__.py:13
msgid "Site message" msgid "Site message"
msgstr "站内信" msgstr "站内信"
@ -4678,14 +4723,17 @@ msgstr ""
" " " "
#: templates/_mfa_login_field.html:28 #: templates/_mfa_login_field.html:28
#: users/templates/users/forgot_password.html:65
msgid "Send verification code" msgid "Send verification code"
msgstr "发送验证码" msgstr "发送验证码"
#: templates/_mfa_login_field.html:106 #: templates/_mfa_login_field.html:106
#: users/templates/users/forgot_password.html:122
msgid "Wait: " msgid "Wait: "
msgstr "等待:" msgstr "等待:"
#: templates/_mfa_login_field.html:116 #: templates/_mfa_login_field.html:116
#: users/templates/users/forgot_password.html:138
msgid "The verification code has been sent" msgid "The verification code has been sent"
msgstr "验证码已发送" msgstr "验证码已发送"
@ -4745,10 +4793,6 @@ msgstr "会话不存在: {}"
msgid "Session is finished or the protocol not supported" msgid "Session is finished or the protocol not supported"
msgstr "会话已经完成或协议不支持" msgstr "会话已经完成或协议不支持"
#: terminal/api/session.py:224
msgid "User does not exist: {}"
msgstr "用户不存在: {}"
#: terminal/api/session.py:232 #: terminal/api/session.py:232
msgid "User does not have permission" msgid "User does not have permission"
msgstr "用户没有权限" msgstr "用户没有权限"
@ -4953,10 +4997,6 @@ msgstr "可以验证会话动作权限"
msgid "Creator" msgid "Creator"
msgstr "创建者" msgstr "创建者"
#: terminal/models/sharing.py:26 terminal/models/sharing.py:80
msgid "Verify code"
msgstr "验证码"
#: terminal/models/sharing.py:31 #: terminal/models/sharing.py:31
msgid "Expired time (min)" msgid "Expired time (min)"
msgstr "过期时间 (分)" msgstr "过期时间 (分)"
@ -5678,40 +5718,40 @@ msgstr "确认密码"
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms/profile.py:109 #: users/forms/profile.py:112
msgid "Old password" msgid "Old password"
msgstr "原来密码" msgstr "原来密码"
#: users/forms/profile.py:119 #: users/forms/profile.py:122
msgid "Old password error" msgid "Old password error"
msgstr "原来密码错误" msgstr "原来密码错误"
#: users/forms/profile.py:129 #: users/forms/profile.py:132
msgid "Automatically configure and download the SSH key" msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥" msgstr "自动配置并下载SSH密钥"
#: users/forms/profile.py:131 #: users/forms/profile.py:134
msgid "ssh public key" msgid "ssh public key"
msgstr "SSH公钥" msgstr "SSH公钥"
#: users/forms/profile.py:132 #: users/forms/profile.py:135
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "ssh-rsa AAAA..." msgstr "ssh-rsa AAAA..."
#: users/forms/profile.py:133 #: users/forms/profile.py:136
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" msgstr "复制你的公钥到这里"
#: users/forms/profile.py:146 #: users/forms/profile.py:149
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms/profile.py:150 users/serializers/profile.py:100 #: users/forms/profile.py:153 users/serializers/profile.py:100
#: users/serializers/profile.py:183 users/serializers/profile.py:210 #: users/serializers/profile.py:183 users/serializers/profile.py:210
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法" msgstr "SSH密钥不合法"
#: users/forms/profile.py:161 users/models/user.py:700 #: users/forms/profile.py:164 users/models/user.py:700
msgid "Public key" msgid "Public key"
msgstr "SSH公钥" msgstr "SSH公钥"
@ -5782,7 +5822,7 @@ msgstr "用户密码历史"
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
#: users/notifications.py:85 users/views/profile/reset.py:127 #: users/notifications.py:85 users/views/profile/reset.py:139
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
@ -5976,14 +6016,22 @@ msgstr "你的 SSH 密钥已经被管理员重置"
msgid "click here to set your password" msgid "click here to set your password"
msgstr "点击这里设置密码" msgstr "点击这里设置密码"
#: users/templates/users/forgot_password.html:24 #: users/templates/users/forgot_password.html:38
msgid "Input your email, that will send a mail to your" msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
#: users/templates/users/forgot_password.html:33 #: users/templates/users/forgot_password.html:68
msgid "Submit" msgid "Submit"
msgstr "提交" msgstr "提交"
#: users/templates/users/forgot_password.html:71
msgid "Use the phone number to retrieve the password"
msgstr "使用手机号找回密码"
#: users/templates/users/forgot_password.html:72
msgid "Use email to retrieve the password"
msgstr "使用邮箱找回密码"
#: users/templates/users/mfa_setting.html:24 #: users/templates/users/mfa_setting.html:24
msgid "Enable MFA" msgid "Enable MFA"
msgstr "启用 MFA 多因子认证" msgstr "启用 MFA 多因子认证"
@ -6122,42 +6170,23 @@ msgstr "MFA(OTP) 禁用成功,返回登录页面"
msgid "Password invalid" msgid "Password invalid"
msgstr "用户名或密码无效" msgstr "用户名或密码无效"
#: users/views/profile/reset.py:40 #: users/views/profile/reset.py:96 users/views/profile/reset.py:107
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/profile/reset.py:41
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/profile/reset.py:52
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/profile/reset.py:58
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
msgstr "用户来自 {} 请去相应系统修改密码"
#: users/views/profile/reset.py:84 users/views/profile/reset.py:95
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/profile/reset.py:100 #: users/views/profile/reset.py:112
msgid "User auth from {}, go there change password" msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码" msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/views/profile/reset.py:107 #: users/views/profile/reset.py:119
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/views/profile/reset.py:113 #: users/views/profile/reset.py:125
msgid "* The new password cannot be the last {} passwords" msgid "* The new password cannot be the last {} passwords"
msgstr "* 新密码不能是最近 {} 次的密码" msgstr "* 新密码不能是最近 {} 次的密码"
#: users/views/profile/reset.py:128 #: users/views/profile/reset.py:140
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"

View File

@ -1,6 +1,4 @@
from django.conf import settings from common.sdk.sms.endpoint import SMS
from common.sdk.sms.alibaba import AlibabaSMS as Client
from .base import BackendBase from .base import BackendBase
@ -9,13 +7,7 @@ class SMS(BackendBase):
is_enable_field_in_settings = 'SMS_ENABLED' is_enable_field_in_settings = 'SMS_ENABLED'
def __init__(self): def __init__(self):
""" self.client = SMS()
暂时只对接阿里之后再扩展
"""
self.client = Client(
access_key_id=settings.ALIBABA_ACCESS_KEY_ID,
access_key_secret=settings.ALIBABA_ACCESS_KEY_SECRET
)
def send_msg(self, users, sign_name: str, template_code: str, template_param: dict): def send_msg(self, users, sign_name: str, template_code: str, template_param: dict):
accounts, __, __ = self.get_accounts(users) accounts, __, __ = self.get_accounts(users)

View File

@ -99,8 +99,11 @@ class UserTokenResetPasswordForm(forms.Form):
class UserForgotPasswordForm(forms.Form): class UserForgotPasswordForm(forms.Form):
email = forms.EmailField(label=_("Email")) username = forms.CharField(label=_("Username"))
captcha = CaptchaField(label=_("Captcha")) email = forms.CharField(label=_("Email"), required=False)
phone = forms.CharField(label=_('Phone'), required=False, max_length=11)
code = forms.CharField(label=_('Verify code'), max_length=6, required=False)
form_type = forms.CharField(widget=forms.HiddenInput({'value': 'email'}))
class UserPasswordForm(UserTokenResetPasswordForm): class UserPasswordForm(UserTokenResetPasswordForm):

View File

@ -4,12 +4,29 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<style> <style>
.captcha { .margin-bottom {
float: right; margin-bottom: 15px;
width: 50%;
} }
#id_captcha_1 { .input-style {
width: 50%; width: 100%;
display: inline-block;
}
.challenge-required .input-style {
width: calc(100% - 120px);
display: inline-block;
}
.btn-challenge {
width: 110px !important;
height: 100%;
vertical-align: top;
}
.display-fade {
display: none;
}
.display-show {
display: block;
} }
</style> </style>
{% endblock %} {% endblock %}
@ -17,22 +34,111 @@
{% block title %} {% trans 'Forgot password' %}?{% endblock %} {% block title %} {% trans 'Forgot password' %}?{% endblock %}
{% block content %} {% block content %}
{% if errors %} <p class="{% if form_type == 'phone' %} display-fade {% else %} display-show {% endif %}">
<p class="red-fonts">{{ errors }}</p>
{% endif %}
<p>
{% trans 'Input your email, that will send a mail to your' %} {% trans 'Input your email, that will send a mail to your' %}
</p> </p>
<p class="{% if form_type == 'email' %} display-fade {% else %} display-show {% endif %}">
Enter your phone number and a verification code will be sent to your phone
</p>
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<form role="form" class="form-horizontal" action="" method="post"> <form role="form" class="form-horizontal" action="" method="post">
{% csrf_token %} {% csrf_token %}
{% bootstrap_field form.email layout="horizontal" %} {% bootstrap_field form.form_type layout="horizontal" %}
{% bootstrap_field form.captcha layout="horizontal" %} <div class="margin-bottom {% if 'username' in form.errors %} has-error {% endif %}">
<label class="control-label" for="username">{{ form.username.errors.as_text }}</label>
<input type="text" id="username" name="username" class="form-control input-style" placeholder="{% trans 'Username' %}" value="{{ username }}">
</div>
<div class="margin-bottom {% if form_type == 'phone' %} display-fade {% else %} display-show {% endif %} {% if 'email' in form.errors %} has-error {% endif %}">
<label class="control-label" for="email">{{ form.email.errors.as_text }}</label>
<input type="email" id="email" name="email" class="form-control input-style" placeholder="{% trans 'Email' %}" value="{{ email }}">
</div>
<div class="margin-bottom {% if form_type == 'email' %} display-fade {% else %} display-show {% endif %} {% if 'phone' in form.errors %} has-error {% endif %}">
<label class="control-label" for="phone">{{ form.phone.errors.as_text }}</label>
<input type="tel" id="phone" name="phone" class="form-control input-style" placeholder="{% trans 'Phone' %}" value="{{ phone }}">
</div>
<div class="margin-bottom challenge-required {% if 'code' in form.errors %} has-error {% endif %}" style="margin-top: 30px">
<label class="control-label" for="code">{{ form.code.errors.as_text }}</label>
<input type="text" id="code" name="code" class="form-control input-style" placeholder="{% trans 'Verify code' %}">
<button class="btn btn-primary full-width btn-challenge"
type='button' onclick="sendChallengeCode(this)">{% trans 'Send verification code' %}</button>
</div>
<div class="margin-bottom">
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button> <button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
</div>
{% if XPACK_ENABLED %}
<a id="switch-email" class="{% if form_type == 'phone' %} display-fade {% else %} display-show {% endif %}" onclick="changeType()">{% trans 'Use the phone number to retrieve the password' %}</a>
<a id="switch-phone" class="{% if form_type == 'email' %} display-fade {% else %} display-show {% endif %}" onclick="changeType()">{% trans 'Use email to retrieve the password' %}</a>
{% endif %}
</form> </form>
</div> </div>
</div> </div>
<script>
function changeType() {
// 把所有 display-fade display-show 互换
const showElements = $('.display-show')
const fadeElements = $('.display-fade')
const formTypeElement = $('input[name="form_type"]')
showElements.addClass('display-fade').removeClass('display-show')
fadeElements.addClass('display-show').removeClass('display-fade')
formTypeElement.attr('value', formTypeElement.val() === 'email' ? 'phone': 'email')
}
function sendChallengeCode(currentBtn) {
let time = 60;
const url = "{% url 'api-auth:reset-password-code' %}";
const formType = $('input[name="form_type"]').val()
const username = $('#username').val()
const email = $('#email').val()
const phone = $('#phone').val()
const errMsg = "{% trans 'The {} cannot be empty' %}"
if (username === "") {
toastr.error(errMsg.replace('{}', "{% trans 'Username' %}"))
return
}
if (formType === 'phone') {
if (phone === "") {
toastr.error(errMsg.replace('{}', "{% trans 'Phone' %}"))
return
}
} else {
if (email === "") {
toastr.error(errMsg.replace('{}', "{% trans 'Email' %}"))
return
}
}
const data = {
username: username, form_type: formType,
email: email, phone: phone,
}
function onSuccess() {
const originBtnText = currentBtn.innerHTML;
currentBtn.disabled = true
const interval = setInterval(function () {
currentBtn.innerHTML = `{% trans 'Wait: ' %} ${time}`;
time -= 1
if (time === 0) {
currentBtn.innerHTML = originBtnText
currentBtn.disabled = false
clearInterval(interval)
}
}, 1000)
}
requestApi({
url: url,
method: "POST",
body: JSON.stringify(data),
success: onSuccess,
success_message: "{% trans 'The verification code has been sent' %}",
flash_message: true
})
}
</script>
{% endblock %} {% endblock %}

View File

@ -4,15 +4,13 @@ from __future__ import unicode_literals
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.shortcuts import reverse, redirect from django.shortcuts import reverse, redirect
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateView
from django.conf import settings from django.conf import settings
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import FormView from django.views.generic import FormView
from users.notifications import ResetPasswordSuccessMsg, ResetPasswordMsg from users.notifications import ResetPasswordSuccessMsg
from common.utils import get_object_or_none, FlashMessageUtil from common.utils import get_object_or_none, FlashMessageUtil
from common.permissions import IsValidUser from common.utils.verify_code import SendAndVerifyCodeUtil
from common.mixins.views import PermissionsMixin
from ...models import User from ...models import User
from ...utils import ( from ...utils import (
get_password_check_rules, check_password_rules, get_password_check_rules, check_password_rules,
@ -34,35 +32,49 @@ class UserForgotPasswordView(FormView):
template_name = 'users/forgot_password.html' template_name = 'users/forgot_password.html'
form_class = forms.UserForgotPasswordForm form_class = forms.UserForgotPasswordForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = context['form']
if getattr(form, 'errors', None):
e = getattr(form, 'errors')
context['errors'] = e
context['form_type'] = 'email'
context['XPACK_ENABLED'] = settings.XPACK_ENABLED
cleaned_data = getattr(form, 'cleaned_data', {})
for k, v in cleaned_data.items():
if v:
context[k] = v
return context
@staticmethod @staticmethod
def get_redirect_message_url(): def get_redirect_url(user):
message_data = { query_params = '?token=%s' % user.generate_reset_token()
'title': _('Send reset password message'), reset_password_url = reverse('authentication:reset-password')
'message': _('Send reset password mail success, ' return reset_password_url + query_params
'login your mail box and follow it '),
'redirect_url': reverse('authentication:login'),
}
url = FlashMessageUtil.gen_message_url(message_data)
return url
def form_valid(self, form): def form_valid(self, form):
email = form.cleaned_data['email'] form_type = form.cleaned_data['form_type']
user = get_object_or_none(User, email=email) code = form.cleaned_data['code']
username = form.cleaned_data['username']
if settings.XPACK_ENABLED and form_type == 'phone':
backend = 'sms'
target = form.cleaned_data['phone']
else:
backend = 'email'
target = form.cleaned_data['email']
try:
sender_util = SendAndVerifyCodeUtil(target, backend=backend)
sender_util.verify(code)
except Exception as e:
form.add_error('code', str(e))
return super().form_invalid(form)
user = get_object_or_none(User, **{'username': username, form_type: target})
if not user: if not user:
error = _('Email address invalid, please input again') form.add_error('username', _('User does not exist: {}').format(username))
form.add_error('email', error) return super().form_invalid(form)
return self.form_invalid(form)
if not user.is_local: return redirect(self.get_redirect_url(user))
error = _(
'The user is from {}, please go to the corresponding system to change the password'
).format(user.get_source_display())
form.add_error('email', error)
return self.form_invalid(form)
ResetPasswordMsg(user).publish_async()
url = self.get_redirect_message_url()
return redirect(url)
class UserResetPasswordView(FormView): class UserResetPasswordView(FormView):