diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 186f46911..9b81d17fe 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -7,13 +7,14 @@ import time from django.conf import settings from django.contrib import auth +from django.utils.translation import ugettext as _ from django.contrib.auth import ( BACKEND_SESSION_KEY, _get_backends, PermissionDenied, user_login_failed, _clean_credentials ) from django.shortcuts import reverse -from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get +from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get, FlashMessageUtil from users.models import User from users.utils import LoginBlockUtil, MFABlockUtils from . import errors @@ -204,41 +205,40 @@ class AuthMixin: return user @classmethod - def generate_reset_password_url_with_flash_msg(cls, user: User, flash_view_name): + def generate_reset_password_url_with_flash_msg(cls, user, message): reset_passwd_url = reverse('authentication:reset-password') query_str = urlencode({ 'token': user.generate_reset_token() }) reset_passwd_url = f'{reset_passwd_url}?{query_str}' - flash_page_url = reverse(flash_view_name) - query_str = urlencode({ - 'redirect_url': reset_passwd_url - }) - return f'{flash_page_url}?{query_str}' + message_data = { + 'title': _('Please change your password'), + 'message': message, + 'interval': 3, + 'redirect_url': reset_passwd_url, + } + return FlashMessageUtil.gen_message_url(message_data) @classmethod def _check_passwd_is_too_simple(cls, user: User, password): if user.is_superuser and password == 'admin': - url = cls.generate_reset_password_url_with_flash_msg( - user, 'authentication:passwd-too-simple-flash-msg' - ) + message = _('Your password is too simple, please change it for security') + url = cls.generate_reset_password_url_with_flash_msg(user, message=message) raise errors.PasswdTooSimple(url) @classmethod def _check_passwd_need_update(cls, user: User): if user.need_update_password: - url = cls.generate_reset_password_url_with_flash_msg( - user, 'authentication:passwd-need-update-flash-msg' - ) + message = _('You should to change your password before login') + url = cls.generate_reset_password_url_with_flash_msg(user, message) raise errors.PasswdNeedUpdate(url) @classmethod def _check_password_require_reset_or_not(cls, user: User): if user.password_has_expired: - url = cls.generate_reset_password_url_with_flash_msg( - user, 'authentication:passwd-has-expired-flash-msg' - ) + message = _('Your password has expired, please reset before logging in') + url = cls.generate_reset_password_url_with_flash_msg(user, message) raise errors.PasswordRequireResetError(url) def check_user_auth_if_need(self, decrypt_passwd=False): diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 062044047..c4e4de00a 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -18,13 +18,7 @@ urlpatterns = [ # 原来在users中的 path('password/forgot/', users_view.UserForgotPasswordView.as_view(), name='forgot-password'), - path('password/forgot/sendmail-success/', users_view.UserForgotPasswordSendmailSuccessView.as_view(), - name='forgot-password-sendmail-success'), path('password/reset/', users_view.UserResetPasswordView.as_view(), name='reset-password'), - path('password/too-simple-flash-msg/', views.FlashPasswdTooSimpleMsgView.as_view(), name='passwd-too-simple-flash-msg'), - path('password/need-update-flash-msg/', views.FlashPasswdNeedUpdateMsgView.as_view(), name='passwd-need-update-flash-msg'), - path('password/has-expired-msg/', views.FlashPasswdHasExpiredMsgView.as_view(), name='passwd-has-expired-flash-msg'), - path('password/reset/success/', users_view.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), path('password/verify/', users_view.UserVerifyPasswordView.as_view(), name='user-verify-password'), # Profile diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index b57d8b5aa..2412ef54a 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -19,7 +19,7 @@ from django.conf import settings from django.urls import reverse_lazy from django.contrib.auth import BACKEND_SESSION_KEY -from common.utils import get_request_ip, get_object_or_none +from common.utils import get_request_ip, FlashMessageUtil from users.utils import ( redirect_user_first_login_or_index ) @@ -31,8 +31,6 @@ from ..forms import get_user_login_form_cls __all__ = [ 'UserLoginView', 'UserLogoutView', 'UserLoginGuardView', 'UserLoginWaitConfirmView', - 'FlashPasswdTooSimpleMsgView', 'FlashPasswdHasExpiredMsgView', - 'FlashPasswdNeedUpdateMsgView' ] @@ -44,12 +42,42 @@ class UserLoginView(mixins.AuthMixin, FormView): redirect_field_name = 'next' template_name = 'authentication/login.html' + def redirect_third_party_auth_if_need(self, request): + # show jumpserver login page if request http://{JUMP-SERVER}/?admin=1 + if self.request.GET.get("admin", 0): + return None + auth_type = '' + auth_url = '' + if settings.AUTH_OPENID: + auth_type = 'OIDC' + auth_url = reverse(settings.AUTH_OPENID_AUTH_LOGIN_URL_NAME) + elif settings.AUTH_CAS: + auth_type = 'CAS' + auth_url = reverse(settings.CAS_LOGIN_URL_NAME) + if not auth_url: + return None + + message_data = { + 'title': _('Redirecting'), + 'message': _("Redirecting to {} authentication").format(auth_type), + 'redirect_url': auth_url, + 'has_cancel': True, + 'cancel_url': reverse('authentication:login') + '?admin=1' + } + redirect_url = FlashMessageUtil.gen_message_url(message_data) + query_string = request.GET.urlencode() + redirect_url = "{}&{}".format(redirect_url, query_string) + return redirect_url + def get(self, request, *args, **kwargs): if request.user.is_staff: first_login_url = redirect_user_first_login_or_index( request, self.redirect_field_name ) return redirect(first_login_url) + redirect_url = self.redirect_third_party_auth_if_need(request) + if redirect_url: + return redirect(redirect_url) request.session.set_test_cookie() return super().get(request, *args, **kwargs) @@ -225,50 +253,3 @@ class UserLogoutView(TemplateView): } kwargs.update(context) return super().get_context_data(**kwargs) - - -@method_decorator(never_cache, name='dispatch') -class FlashPasswdTooSimpleMsgView(TemplateView): - template_name = 'flash_message_standalone.html' - - def get(self, request, *args, **kwargs): - context = { - 'title': _('Please change your password'), - 'messages': _('Your password is too simple, please change it for security'), - 'interval': 3, - 'redirect_url': request.GET.get('redirect_url'), - 'auto_redirect': True, - } - return self.render_to_response(context) - - -@method_decorator(never_cache, name='dispatch') -class FlashPasswdNeedUpdateMsgView(TemplateView): - template_name = 'flash_message_standalone.html' - - def get(self, request, *args, **kwargs): - context = { - 'title': _('Please change your password'), - 'messages': _('You should to change your password before login'), - 'interval': 3, - 'redirect_url': request.GET.get('redirect_url'), - 'auto_redirect': True, - 'confirm_button': _('Confirm') - } - return self.render_to_response(context) - - -@method_decorator(never_cache, name='dispatch') -class FlashPasswdHasExpiredMsgView(TemplateView): - template_name = 'flash_message_standalone.html' - - def get(self, request, *args, **kwargs): - context = { - 'title': _('Please change your password'), - 'messages': _('Your password has expired, please reset before logging in'), - 'interval': 3, - 'redirect_url': request.GET.get('redirect_url'), - 'auto_redirect': True, - 'confirm_button': _('Confirm') - } - return self.render_to_response(context) diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index 13f365662..1590f48ba 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -5,7 +5,7 @@ from django.contrib.auth.mixins import UserPassesTestMixin from django.utils import timezone -__all__ = ["DatetimeSearchMixin"] +__all__ = ["DatetimeSearchMixin", "PermissionsMixin"] from rest_framework import permissions @@ -51,4 +51,4 @@ class PermissionsMixin(UserPassesTestMixin): for permission_class in permission_classes: if not permission_class().has_permission(self.request, self): return False - return True \ No newline at end of file + return True diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py new file mode 100644 index 000000000..bfbd97053 --- /dev/null +++ b/apps/common/urls/view_urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from .. import views + +app_name = 'common' + +urlpatterns = [ + # login + path('flash-message/', views.FlashMessageMsgView.as_view(), name='flash-message'), +] \ No newline at end of file diff --git a/apps/common/utils/__init__.py b/apps/common/utils/__init__.py index 8b4576221..0fdcf0dd1 100644 --- a/apps/common/utils/__init__.py +++ b/apps/common/utils/__init__.py @@ -8,3 +8,4 @@ from .http import * from .ipip import * from .crypto import * from .random import * +from .jumpserver import * \ No newline at end of file diff --git a/apps/common/utils/jumpserver.py b/apps/common/utils/jumpserver.py new file mode 100644 index 000000000..1ae806366 --- /dev/null +++ b/apps/common/utils/jumpserver.py @@ -0,0 +1,31 @@ +from django.core.cache import cache +from django.shortcuts import reverse + +from .random import random_string + + +__all__ = ['FlashMessageUtil'] + + +class FlashMessageUtil: + @staticmethod + def get_key(code): + key = 'MESSAGE_{}'.format(code) + return key + + @classmethod + def get_message_code(cls, message_data): + code = random_string(12) + key = cls.get_key(code) + cache.set(key, message_data, 60) + return code + + @classmethod + def get_message_by_code(cls, code): + key = cls.get_key(code) + return cache.get(key) + + @classmethod + def gen_message_url(cls, message_data): + code = cls.get_message_code(message_data) + return reverse('common:flash-message') + f'?code={code}' diff --git a/apps/common/views.py b/apps/common/views.py new file mode 100644 index 000000000..79d628248 --- /dev/null +++ b/apps/common/views.py @@ -0,0 +1,40 @@ +# +from django.utils.decorators import method_decorator +from django.views.decorators.cache import never_cache +from django.http import HttpResponse +from django.views.generic.base import TemplateView + +from common.utils import bulk_get, FlashMessageUtil + + +@method_decorator(never_cache, name='dispatch') +class FlashMessageMsgView(TemplateView): + template_name = 'flash_message_standalone.html' + + def get(self, request, *args, **kwargs): + code = request.GET.get('code') + if not code: + return HttpResponse('Not found the code') + + message_data = FlashMessageUtil.get_message_by_code(code) + if not message_data: + return HttpResponse('Message code error') + + title, message, redirect_url, confirm_button, cancel_url = bulk_get( + message_data, 'title', 'message', 'redirect_url', 'confirm_button', 'cancel_url' + ) + + interval = message_data.get('interval', 3) + auto_redirect = message_data.get('auto_redirect', True) + has_cancel = message_data.get('has_cancel', False) + context = { + 'title': title, + 'messages': message, + 'interval': interval, + 'redirect_url': redirect_url, + 'auto_redirect': auto_redirect, + 'confirm_button': confirm_button, + 'has_cancel': has_cancel, + 'cancel_url': cancel_url, + } + return self.render_to_response(context) \ No newline at end of file diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 759c6f271..ca9870043 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -29,6 +29,7 @@ api_v1 = [ app_view_patterns = [ path('auth/', include('authentication.urls.view_urls'), name='auth'), path('ops/', include('ops.urls.view_urls'), name='ops'), + path('common/', include('common.urls.view_urls'), name='common'), re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), ] diff --git a/apps/jumpserver/views/other.py b/apps/jumpserver/views/other.py index 9ab561c4c..293177615 100644 --- a/apps/jumpserver/views/other.py +++ b/apps/jumpserver/views/other.py @@ -1,17 +1,15 @@ # -*- coding: utf-8 -*- # import re -import time from django.http import HttpResponseRedirect, JsonResponse, Http404 from django.conf import settings from django.views.generic import View from django.shortcuts import redirect from django.utils.translation import ugettext_lazy as _ -from rest_framework.views import APIView -from rest_framework.permissions import AllowAny from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse +from rest_framework.views import APIView from common.http import HttpResponseTemporaryRedirect @@ -85,3 +83,4 @@ class KokoView(View): "
Koko is a separately deployed program, you need to deploy Koko, configure nginx for url distribution,
" "If you see this page, prove that you are not accessing the nginx listening port. Good luck.") return HttpResponse(msg) + diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c4b35117e..2bc2e78e2 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index cbe271ba3..ee97bf8fd 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-04-29 12:17+0800\n" +"POT-Creation-Date: 2021-04-29 16:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -354,7 +354,9 @@ msgid "You can't delete the root node ({})" msgstr "不能删除根节点 ({})" #: assets/api/node.py:75 -msgid "Deletion failed and the node contains children or assets" +#, fuzzy +#| msgid "Deletion failed and the node contains children or assets" +msgid "Deletion failed and the node contains assets" msgstr "删除失败,节点包含子节点或资产" #: assets/backends/db.py:244 @@ -650,31 +652,31 @@ msgstr "资产组" msgid "Default asset group" msgstr "默认资产组" -#: assets/models/label.py:19 assets/models/node.py:547 settings/models.py:30 +#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:30 msgid "Value" msgstr "值" -#: assets/models/node.py:152 +#: assets/models/node.py:151 msgid "New node" msgstr "新节点" -#: assets/models/node.py:475 users/templates/users/_granted_assets.html:130 +#: assets/models/node.py:474 users/templates/users/_granted_assets.html:130 msgid "empty" msgstr "空" -#: assets/models/node.py:546 perms/models/asset_permission.py:176 +#: assets/models/node.py:545 perms/models/asset_permission.py:176 msgid "Key" msgstr "键" -#: assets/models/node.py:548 +#: assets/models/node.py:547 msgid "Full value" msgstr "全称" -#: assets/models/node.py:551 perms/models/asset_permission.py:177 +#: assets/models/node.py:550 perms/models/asset_permission.py:177 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:560 assets/serializers/system_user.py:191 +#: assets/models/node.py:559 assets/serializers/system_user.py:191 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -1360,15 +1362,15 @@ msgstr "来源 IP 不被允许登录" msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:271 authentication/views/login.py:237 +#: authentication/errors.py:271 authentication/views/login.py:267 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:280 authentication/views/login.py:252 +#: authentication/errors.py:280 authentication/views/login.py:282 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:289 authentication/views/login.py:268 +#: authentication/errors.py:289 authentication/views/login.py:298 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" @@ -1453,8 +1455,9 @@ msgid "Need MFA for view auth" msgstr "需要多因子认证来查看账号信息" #: authentication/templates/authentication/_mfa_confirm_modal.html:20 -#: authentication/views/login.py:256 authentication/views/login.py:272 -#: templates/_modal.html:23 users/templates/users/user_password_verify.html:20 +#: authentication/views/login.py:286 authentication/views/login.py:302 +#: authentication/views/login.py:318 templates/_modal.html:23 +#: users/templates/users/user_password_verify.html:20 msgid "Confirm" msgstr "确认" @@ -1519,7 +1522,6 @@ msgid "Copy link" msgstr "复制链接" #: authentication/templates/authentication/login_wait_confirm.html:51 -#: templates/flash_message_standalone.html:38 msgid "Return" msgstr "返回" @@ -1527,11 +1529,19 @@ msgstr "返回" msgid "Copy success" msgstr "复制成功" -#: authentication/views/login.py:58 +#: authentication/views/login.py:63 authentication/views/login.py:313 +msgid "Redirecting" +msgstr "跳转中" + +#: authentication/views/login.py:64 +msgid "Redirecting to {} authentication" +msgstr "正在跳转到 {} 认证" + +#: authentication/views/login.py:88 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:183 +#: authentication/views/login.py:213 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -1539,23 +1549,27 @@ msgstr "" "等待 {} 确认, 你也可以复制链接发给他/她
\n" " 不要关闭本页面" -#: authentication/views/login.py:188 +#: authentication/views/login.py:218 msgid "No ticket found" msgstr "没有发现工单" -#: authentication/views/login.py:220 +#: authentication/views/login.py:250 msgid "Logout success" msgstr "退出登录成功" -#: authentication/views/login.py:221 +#: authentication/views/login.py:251 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: authentication/views/login.py:236 authentication/views/login.py:251 -#: authentication/views/login.py:267 +#: authentication/views/login.py:266 authentication/views/login.py:281 +#: authentication/views/login.py:297 msgid "Please change your password" msgstr "请修改密码" +#: authentication/views/login.py:314 +msgid "Redirect to third party auth" +msgstr "" + #: common/const/__init__.py:6 #, python-format msgid "%(name)s was created successfully" @@ -1671,7 +1685,7 @@ msgstr "JumpServer 开源堡垒机" msgid "

Flow service unavailable, check it

" msgstr "" -#: jumpserver/views/other.py:27 +#: jumpserver/views/other.py:25 msgid "" "
Luna is a separately deployed program, you need to deploy Luna, koko, " "configure nginx for url distribution,
If you see this page, " @@ -1680,11 +1694,11 @@ msgstr "" "
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" "这个页面,证明你访问的不是nginx监听的端口,祝你好运
" -#: jumpserver/views/other.py:71 +#: jumpserver/views/other.py:69 msgid "Websocket server run on port: {}, you should proxy it on nginx" msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" -#: jumpserver/views/other.py:85 +#: jumpserver/views/other.py:83 msgid "" "
Koko is a separately deployed program, you need to deploy Koko, " "configure nginx for url distribution,
If you see this page, " @@ -2760,6 +2774,14 @@ msgstr "确认删除" msgid "Are you sure delete" msgstr "您确定删除吗?" +#: templates/flash_message_standalone.html:28 +msgid "Cancel" +msgstr "取消" + +#: templates/flash_message_standalone.html:37 +msgid "Go" +msgstr "跳转" + #: templates/index.html:11 msgid "Total users" msgstr "用户总数" @@ -4978,9 +5000,6 @@ msgstr "社区版" #~ msgid "This will reset the user password and send a reset mail" #~ msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" -#~ msgid "Cancel" -#~ msgstr "取消" - #~ msgid "" #~ "The reset-ssh-public-key E-mail has been sent successfully. Please inform " #~ "the user to update his new ssh public key." diff --git a/apps/templates/flash_message_standalone.html b/apps/templates/flash_message_standalone.html index bc27fa614..5265f14f9 100644 --- a/apps/templates/flash_message_standalone.html +++ b/apps/templates/flash_message_standalone.html @@ -4,14 +4,6 @@ {% block html_title %} {{ title }} {% endblock %} {% block title %} {{ title }}{% endblock %} -{% block custom_head_css_js %} - -{% endblock %} - {% block content %}
{% if errors %} @@ -30,12 +22,19 @@

{% endif %}
-
+ {% if has_cancel %} + + {% endif %} + diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index 8b1d9a102..356694020 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -1,18 +1,15 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from django.shortcuts import render from django.views.generic import RedirectView -from django.core.files.storage import default_storage from django.shortcuts import reverse, redirect from django.utils.translation import ugettext as _ from django.views.generic.base import TemplateView from django.conf import settings from django.urls import reverse_lazy -from formtools.wizard.views import SessionWizardView from django.views.generic import FormView -from common.utils import get_object_or_none +from common.utils import get_object_or_none, FlashMessageUtil from common.permissions import IsValidUser from common.mixins.views import PermissionsMixin from ...models import User @@ -24,9 +21,7 @@ from ... import forms __all__ = [ - 'UserLoginView', 'UserForgotPasswordSendmailSuccessView', - 'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView', - 'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView', + 'UserLoginView', 'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView', ] @@ -39,6 +34,17 @@ class UserForgotPasswordView(FormView): template_name = 'users/forgot_password.html' form_class = forms.UserForgotPasswordForm + @staticmethod + def get_redirect_message_url(): + message_data = { + 'title': _('Send reset password message'), + 'message': _('Send reset password mail success, ' + '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): email = form.cleaned_data['email'] user = get_object_or_none(User, email=email) @@ -53,37 +59,9 @@ class UserForgotPasswordView(FormView): ).format(user.get_source_display()) form.add_error('email', error) return self.form_invalid(form) - send_reset_password_mail(user) - return redirect('authentication:forgot-password-sendmail-success') - - -class UserForgotPasswordSendmailSuccessView(TemplateView): - template_name = 'flash_message_standalone.html' - - def get_context_data(self, **kwargs): - context = { - 'title': _('Send reset password message'), - 'messages': _('Send reset password mail success, ' - 'login your mail box and follow it '), - 'redirect_url': reverse('authentication:login'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserResetPasswordSuccessView(TemplateView): - template_name = 'flash_message_standalone.html' - - def get_context_data(self, **kwargs): - context = { - 'title': _('Reset password success'), - 'messages': _('Reset password success, return to login page'), - 'redirect_url': reverse('authentication:login'), - 'auto_redirect': True, - } - kwargs.update(context) - return super().get_context_data(**kwargs) + url = self.get_redirect_message_url() + return redirect(url) class UserResetPasswordView(FormView): @@ -139,7 +117,18 @@ class UserResetPasswordView(FormView): user.reset_password(password) User.expired_reset_password_token(token) send_reset_password_success_mail(self.request, user) - return redirect('authentication:reset-password-success') + url = self.get_redirect_url() + return redirect(url) + + @staticmethod + def get_redirect_url(): + message_data = { + 'title': _('Reset password success'), + 'message': _('Reset password success, return to login page'), + 'redirect_url': reverse('authentication:login'), + 'auto_redirect': True, + } + return FlashMessageUtil.gen_message_url(message_data) class UserFirstLoginView(PermissionsMixin, TemplateView):