mirror of https://github.com/jumpserver/jumpserver
feat: 忘记密码支持手机短信找回,并修改邮箱方式和手机方式统一 (#8960)
* feat: 忘记密码支持通过手机找回,邮箱方式修改为和手机方式一致 * feat: 翻译 * feat: 修改翻译 * fix: 还原 Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>pull/8941/head^2
parent
262d070f3c
commit
1e97a23bc5
|
@ -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
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
from .endpoint import SMS, BACKENDS
|
from .endpoint import SMS, BACKENDS
|
||||||
from .utils import SendAndVerifySMSUtil
|
|
||||||
|
|
|
@ -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))
|
|
@ -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)
|
|
|
@ -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}')
|
|
@ -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 "パスワードの成功をリセットし、ログインページに戻る"
|
||||||
|
|
||||||
|
|
|
@ -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 "重置密码成功,返回到登录页面"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue