diff --git a/apps/authentication/api/__init__.py b/apps/authentication/api/__init__.py new file mode 100644 index 000000000..0e8a44178 --- /dev/null +++ b/apps/authentication/api/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# + +from .auth import * diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py new file mode 100644 index 000000000..0c7c1a16e --- /dev/null +++ b/apps/authentication/api/auth.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +# + +import uuid + +from django.core.cache import cache +from django.urls import reverse +from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext as _ + +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from rest_framework.views import APIView + +from common.utils import get_logger, get_request_ip +from common.permissions import IsOrgAdminOrAppUser +from orgs.mixins import RootOrgViewMixin +from authentication.signals import post_auth_success, post_auth_failed +from users.serializers import UserSerializer +from users.models import User, LoginLog +from users.utils import ( + check_user_valid, check_otp_code, increase_login_failed_count, + is_block_login, clean_failed_count +) +from users.hands import Asset, SystemUser + + +logger = get_logger(__name__) + + +class UserAuthApi(RootOrgViewMixin, APIView): + permission_classes = (AllowAny,) + serializer_class = UserSerializer + + def post(self, request): + # limit login + username = request.data.get('username') + ip = request.data.get('remote_addr', None) + ip = ip or get_request_ip(request) + + if is_block_login(username, ip): + msg = _("Log in frequently and try again later") + logger.warn(msg + ': ' + username + ':' + ip) + return Response({'msg': msg}, status=401) + + user, msg = self.check_user_valid(request) + if not user: + username = request.data.get('username', '') + exist = User.objects.filter(username=username).first() + reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST + self.send_auth_signal(success=False, username=username, reason=reason) + increase_login_failed_count(username, ip) + return Response({'msg': msg}, status=401) + + if user.password_has_expired: + self.send_auth_signal( + success=False, username=username, + reason=LoginLog.REASON_PASSWORD_EXPIRED + ) + msg = _("The user {} password has expired, please update.".format( + user.username)) + logger.info(msg) + return Response({'msg': msg}, status=401) + + if not user.otp_enabled: + self.send_auth_signal(success=True, user=user) + # 登陆成功,清除原来的缓存计数 + clean_failed_count(username, ip) + token = user.create_bearer_token(request) + return Response( + {'token': token, 'user': self.serializer_class(user).data} + ) + + seed = uuid.uuid4().hex + cache.set(seed, user, 300) + return Response( + { + 'code': 101, + 'msg': _('Please carry seed value and ' + 'conduct MFA secondary certification'), + 'otp_url': reverse('api-auth:user-otp-auth'), + 'seed': seed, + 'user': self.serializer_class(user).data + }, status=300 + ) + + @staticmethod + def check_user_valid(request): + username = request.data.get('username', '') + password = request.data.get('password', '') + public_key = request.data.get('public_key', '') + user, msg = check_user_valid( + username=username, password=password, + public_key=public_key + ) + return user, msg + + def send_auth_signal(self, success=True, user=None, username='', reason=''): + if success: + post_auth_success.send(sender=self.__class__, user=user, request=self.request) + else: + post_auth_failed.send( + sender=self.__class__, username=username, + request=self.request, reason=reason + ) + + +class UserConnectionTokenApi(RootOrgViewMixin, APIView): + permission_classes = (IsOrgAdminOrAppUser,) + + def post(self, request): + user_id = request.data.get('user', '') + asset_id = request.data.get('asset', '') + system_user_id = request.data.get('system_user', '') + token = str(uuid.uuid4()) + user = get_object_or_404(User, id=user_id) + asset = get_object_or_404(Asset, id=asset_id) + system_user = get_object_or_404(SystemUser, id=system_user_id) + value = { + 'user': user_id, + 'username': user.username, + 'asset': asset_id, + 'hostname': asset.hostname, + 'system_user': system_user_id, + 'system_user_name': system_user.name + } + cache.set(token, value, timeout=20) + return Response({"token": token}, status=201) + + def get(self, request): + token = request.query_params.get('token') + user_only = request.query_params.get('user-only', None) + value = cache.get(token, None) + + if not value: + return Response('', status=404) + + if not user_only: + return Response(value) + else: + return Response({'user': value['user']}) + + def get_permissions(self): + if self.request.query_params.get('user-only', None): + self.permission_classes = (AllowAny,) + return super().get_permissions() + + +class UserToken(APIView): + permission_classes = (AllowAny,) + + def post(self, request): + if not request.user.is_authenticated: + username = request.data.get('username', '') + email = request.data.get('email', '') + password = request.data.get('password', '') + public_key = request.data.get('public_key', '') + + user, msg = check_user_valid( + username=username, email=email, + password=password, public_key=public_key) + else: + user = request.user + msg = None + if user: + token = user.create_bearer_token(request) + return Response({'Token': token, 'Keyword': 'Bearer'}, status=200) + else: + return Response({'error': msg}, status=406) + + +class UserOtpAuthApi(RootOrgViewMixin, APIView): + permission_classes = (AllowAny,) + serializer_class = UserSerializer + + def post(self, request): + otp_code = request.data.get('otp_code', '') + seed = request.data.get('seed', '') + user = cache.get(seed, None) + if not user: + return Response( + {'msg': _('Please verify the user name and password first')}, + status=401 + ) + if not check_otp_code(user.otp_secret_key, otp_code): + self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA) + return Response({'msg': _('MFA certification failed')}, status=401) + self.send_auth_signal(success=True, user=user) + token = user.create_bearer_token(request) + data = {'token': token, 'user': self.serializer_class(user).data} + return Response(data) + + def send_auth_signal(self, success=True, user=None, username='', reason=''): + if success: + post_auth_success.send(sender=self.__class__, user=user, request=self.request) + else: + post_auth_failed.send( + sender=self.__class__, username=username, + request=self.request, reason=reason + ) diff --git a/apps/users/authentication.py b/apps/authentication/authentication.py similarity index 99% rename from apps/users/authentication.py rename to apps/authentication/authentication.py index 5faa7bb60..eab168a07 100644 --- a/apps/users/authentication.py +++ b/apps/authentication/authentication.py @@ -14,7 +14,7 @@ from rest_framework import authentication, exceptions from rest_framework.authentication import CSRFCheck from common.utils import get_object_or_none, make_signature, http_to_unixtime -from .models import User, AccessKey, PrivateToken +from users.models import User, AccessKey, PrivateToken def get_request_date_header(request): diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 8b1378917..f87f04613 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -1 +1,19 @@ +# coding:utf-8 +# +from __future__ import absolute_import + +from django.urls import path + +from .. import api + +app_name = 'authentication' + + +urlpatterns = [ + # path('token/', api.UserToken.as_view(), name='user-token'), + path('auth/', api.UserAuthApi.as_view(), name='user-auth'), + path('connection-token/', + api.UserConnectionTokenApi.as_view(), name='connection-token'), + path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), +] diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 9e3aae6d3..2b68a6f71 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -2,6 +2,7 @@ # from django.urls import path + from .. import views app_name = 'authentication' @@ -9,6 +10,11 @@ app_name = 'authentication' urlpatterns = [ # openid path('openid/login/', views.OpenIDLoginView.as_view(), name='openid-login'), - path('openid/login/complete/', views.OpenIDLoginCompleteView.as_view(), - name='openid-login-complete'), + path('openid/login/complete/', + views.OpenIDLoginCompleteView.as_view(), name='openid-login-complete'), + + # login + path('login/', views.UserLoginView.as_view(), name='login'), + path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'), + path('logout/', views.UserLogoutView.as_view(), name='logout'), ] diff --git a/apps/authentication/views/__init__.py b/apps/authentication/views/__init__.py index 1e55af137..b2659cbd7 100644 --- a/apps/authentication/views/__init__.py +++ b/apps/authentication/views/__init__.py @@ -2,3 +2,4 @@ # from .openid import * +from .login import * diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index ba37e3a60..815c010ce 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -1,15 +1,11 @@ # ~*~ coding: utf-8 ~*~ +# from __future__ import unicode_literals import os from django.core.cache import cache -from django.shortcuts import render -from django.utils import timezone from django.contrib.auth import login as auth_login, logout as auth_logout -from django.contrib.auth.mixins import LoginRequiredMixin -from django.views.generic import ListView -from django.core.files.storage import default_storage -from django.http import HttpResponseRedirect, HttpResponse +from django.http import HttpResponse from django.shortcuts import reverse, redirect from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ @@ -19,23 +15,20 @@ from django.views.decorators.debug import sensitive_post_parameters from django.views.generic.base import TemplateView from django.views.generic.edit import FormView from django.conf import settings -from formtools.wizard.views import SessionWizardView -from common.utils import get_object_or_none, get_request_ip +from common.utils import get_request_ip from authentication.signals import post_auth_success, post_auth_failed -from users.models import User, LoginLog -from users.utils import send_reset_password_mail, check_otp_code, \ - redirect_user_first_login_or_index, get_user_or_tmp_user, \ - set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \ - is_block_login, increase_login_failed_count, clean_failed_count from users import forms +from users.models import User, LoginLog +from users.utils import ( + check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user, + set_tmp_user_to_cache, increase_login_failed_count, + redirect_user_first_login_or_index, +) __all__ = [ 'UserLoginView', 'UserLoginOtpView', 'UserLogoutView', - 'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView', - 'UserResetPasswordView', 'UserResetPasswordSuccessView', - 'UserFirstLoginView', 'LoginLogListView' ] @@ -122,7 +115,7 @@ class UserLoginView(FormView): if user.otp_enabled and user.otp_secret_key: # 1,2,mfa_setting & T - return reverse('users:login-otp') + return reverse('authentication:login-otp') elif user.otp_enabled and not user.otp_secret_key: # 1,2,mfa_setting & F return reverse('users:user-otp-enable-authentication') @@ -169,7 +162,9 @@ class UserLoginOtpView(FormView): success=False, username=user.username, reason=LoginLog.REASON_MFA ) - form.add_error('otp_code', _('MFA code invalid, or ntp sync server time')) + form.add_error( + 'otp_code', _('MFA code invalid, or ntp sync server time') + ) return super().form_invalid(form) def get_success_url(self): @@ -202,7 +197,7 @@ class UserLogoutView(TemplateView): 'title': _('Logout success'), 'messages': _('Logout success, return login page'), 'interval': 1, - 'redirect_url': reverse('users:login'), + 'redirect_url': reverse('authentication:login'), 'auto_redirect': True, } kwargs.update(context) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 22e660bb1..4c0dac0fa 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -135,7 +135,7 @@ TEMPLATES = [ # WSGI_APPLICATION = 'jumpserver.wsgi.applications' LOGIN_REDIRECT_URL = reverse_lazy('index') -LOGIN_URL = reverse_lazy('users:login') +LOGIN_URL = reverse_lazy('authentication:login') SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN @@ -343,10 +343,10 @@ REST_FRAMEWORK = { ), 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework.authentication.BasicAuthentication', - 'users.authentication.AccessKeyAuthentication', - 'users.authentication.AccessTokenAuthentication', - 'users.authentication.PrivateTokenAuthentication', - 'users.authentication.SessionAuthentication', + 'authentication.authentication.AccessKeyAuthentication', + 'authentication.authentication.AccessTokenAuthentication', + 'authentication.authentication.PrivateTokenAuthentication', + 'authentication.authentication.SessionAuthentication', ), 'DEFAULT_FILTER_BACKENDS': ( 'django_filters.rest_framework.DjangoFilterBackend', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index bdae17a44..8cc9c6eb4 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -21,6 +21,7 @@ api_v1_patterns = [ path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')), path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')), path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')), + path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')), ])) ] diff --git a/apps/templates/_header_bar.html b/apps/templates/_header_bar.html index c2764cce3..7c7f696b3 100644 --- a/apps/templates/_header_bar.html +++ b/apps/templates/_header_bar.html @@ -94,10 +94,10 @@
  • {% trans 'User page' %}
  • {% endif %} {% endif %} -
  • {% trans 'Logout' %}
  • +
  • {% trans 'Logout' %}
  • {% else %} - + {% trans 'Login' %} {% endif %} diff --git a/apps/users/api/auth.py b/apps/users/api/auth.py index 25a7ee40a..3d98261b1 100644 --- a/apps/users/api/auth.py +++ b/apps/users/api/auth.py @@ -1,198 +1,3 @@ # -*- coding: utf-8 -*- # -import uuid -from django.core.cache import cache -from django.urls import reverse -from django.shortcuts import get_object_or_404 -from django.utils.translation import ugettext as _ - -from rest_framework.permissions import AllowAny -from rest_framework.response import Response -from rest_framework.views import APIView - -from common.utils import get_logger, get_request_ip -from common.permissions import IsOrgAdminOrAppUser -from orgs.mixins import RootOrgViewMixin -from authentication.signals import post_auth_success, post_auth_failed -from ..serializers import UserSerializer -from ..models import User, LoginLog -from ..utils import check_user_valid, check_otp_code, \ - increase_login_failed_count, is_block_login, \ - clean_failed_count -from ..hands import Asset, SystemUser - - -logger = get_logger(__name__) - - -class UserAuthApi(RootOrgViewMixin, APIView): - permission_classes = (AllowAny,) - serializer_class = UserSerializer - - def post(self, request): - # limit login - username = request.data.get('username') - ip = request.data.get('remote_addr', None) - ip = ip or get_request_ip(request) - - if is_block_login(username, ip): - msg = _("Log in frequently and try again later") - logger.warn(msg + ': ' + username + ':' + ip) - return Response({'msg': msg}, status=401) - - user, msg = self.check_user_valid(request) - if not user: - username = request.data.get('username', '') - exist = User.objects.filter(username=username).first() - reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST - self.send_auth_signal(success=False, username=username, reason=reason) - increase_login_failed_count(username, ip) - return Response({'msg': msg}, status=401) - - if user.password_has_expired: - self.send_auth_signal( - success=False, username=username, - reason=LoginLog.REASON_PASSWORD_EXPIRED - ) - msg = _("The user {} password has expired, please update.".format( - user.username)) - logger.info(msg) - return Response({'msg': msg}, status=401) - - if not user.otp_enabled: - self.send_auth_signal(success=True, user=user) - # 登陆成功,清除原来的缓存计数 - clean_failed_count(username, ip) - token = user.create_bearer_token(request) - return Response( - {'token': token, 'user': self.serializer_class(user).data} - ) - - seed = uuid.uuid4().hex - cache.set(seed, user, 300) - return Response( - { - 'code': 101, - 'msg': _('Please carry seed value and ' - 'conduct MFA secondary certification'), - 'otp_url': reverse('api-users:user-otp-auth'), - 'seed': seed, - 'user': self.serializer_class(user).data - }, status=300 - ) - - @staticmethod - def check_user_valid(request): - username = request.data.get('username', '') - password = request.data.get('password', '') - public_key = request.data.get('public_key', '') - user, msg = check_user_valid( - username=username, password=password, - public_key=public_key - ) - return user, msg - - def send_auth_signal(self, success=True, user=None, username='', reason=''): - if success: - post_auth_success.send(sender=self.__class__, user=user, request=self.request) - else: - post_auth_failed.send( - sender=self.__class__, username=username, - request=self.request, reason=reason - ) - - -class UserConnectionTokenApi(RootOrgViewMixin, APIView): - permission_classes = (IsOrgAdminOrAppUser,) - - def post(self, request): - user_id = request.data.get('user', '') - asset_id = request.data.get('asset', '') - system_user_id = request.data.get('system_user', '') - token = str(uuid.uuid4()) - user = get_object_or_404(User, id=user_id) - asset = get_object_or_404(Asset, id=asset_id) - system_user = get_object_or_404(SystemUser, id=system_user_id) - value = { - 'user': user_id, - 'username': user.username, - 'asset': asset_id, - 'hostname': asset.hostname, - 'system_user': system_user_id, - 'system_user_name': system_user.name - } - cache.set(token, value, timeout=20) - return Response({"token": token}, status=201) - - def get(self, request): - token = request.query_params.get('token') - user_only = request.query_params.get('user-only', None) - value = cache.get(token, None) - - if not value: - return Response('', status=404) - - if not user_only: - return Response(value) - else: - return Response({'user': value['user']}) - - def get_permissions(self): - if self.request.query_params.get('user-only', None): - self.permission_classes = (AllowAny,) - return super().get_permissions() - - -class UserToken(APIView): - permission_classes = (AllowAny,) - - def post(self, request): - if not request.user.is_authenticated: - username = request.data.get('username', '') - email = request.data.get('email', '') - password = request.data.get('password', '') - public_key = request.data.get('public_key', '') - - user, msg = check_user_valid( - username=username, email=email, - password=password, public_key=public_key) - else: - user = request.user - msg = None - if user: - token = user.create_bearer_token(request) - return Response({'Token': token, 'Keyword': 'Bearer'}, status=200) - else: - return Response({'error': msg}, status=406) - - -class UserOtpAuthApi(RootOrgViewMixin, APIView): - permission_classes = (AllowAny,) - serializer_class = UserSerializer - - def post(self, request): - otp_code = request.data.get('otp_code', '') - seed = request.data.get('seed', '') - user = cache.get(seed, None) - if not user: - return Response( - {'msg': _('Please verify the user name and password first')}, - status=401 - ) - if not check_otp_code(user.otp_secret_key, otp_code): - self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA) - return Response({'msg': _('MFA certification failed')}, status=401) - self.send_auth_signal(success=True, user=user) - token = user.create_bearer_token(request) - data = {'token': token, 'user': self.serializer_class(user).data} - return Response(data) - - def send_auth_signal(self, success=True, user=None, username='', reason=''): - if success: - post_auth_success.send(sender=self.__class__, user=user, request=self.request) - else: - post_auth_failed.send( - sender=self.__class__, username=username, - request=self.request, reason=reason - ) diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index 5a4ca80e3..7c512a2a3 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -15,11 +15,7 @@ router.register(r'groups', api.UserGroupViewSet, 'user-group') urlpatterns = [ - # path('token/', api.UserToken.as_view(), name='user-token'), - path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'), path('profile/', api.UserProfileApi.as_view(), name='user-profile'), - path('auth/', api.UserAuthApi.as_view(), name='user-auth'), - path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'), path('users//otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'), path('users//password/', api.UserChangePasswordApi.as_view(), name='change-user-password'), diff --git a/apps/users/urls/views_urls.py b/apps/users/urls/views_urls.py index 0032d71b1..38e8f5cb6 100644 --- a/apps/users/urls/views_urls.py +++ b/apps/users/urls/views_urls.py @@ -9,8 +9,6 @@ app_name = 'users' urlpatterns = [ # Login view path('login/', views.UserLoginView.as_view(), name='login'), - path('logout/', views.UserLogoutView.as_view(), name='logout'), - path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'), path('password/forgot/', views.UserForgotPasswordView.as_view(), name='forgot-password'), path('password/forgot/sendmail-success/', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'), path('password/reset/', views.UserResetPasswordView.as_view(), name='reset-password'), @@ -50,5 +48,5 @@ urlpatterns = [ # Login log # Abandon - path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'), + # path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'), ] diff --git a/apps/users/utils.py b/apps/users/utils.py index aaa236bd8..5a2985bc2 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -62,7 +62,7 @@ def send_user_created_mail(user): 'rest_password_token': user.generate_reset_token(), 'forget_password_url': reverse('users:forgot-password', external=True), 'email': user.email, - 'login_url': reverse('users:login', external=True), + 'login_url': reverse('authentication:login', external=True), } if settings.DEBUG: try: @@ -98,7 +98,7 @@ def send_reset_password_mail(user): 'rest_password_token': user.generate_reset_token(), 'forget_password_url': reverse('users:forgot-password', external=True), 'email': user.email, - 'login_url': reverse('users:login', external=True), + 'login_url': reverse('authentication:login', external=True), } if settings.DEBUG: logger.debug(message) @@ -136,7 +136,7 @@ def send_password_expiration_reminder_mail(user): 'update_password_url': reverse('users:user-password-update', external=True), 'forget_password_url': reverse('users:forgot-password', external=True), 'email': user.email, - 'login_url': reverse('users:login', external=True), + 'login_url': reverse('authentication:login', external=True), } if settings.DEBUG: logger.debug(message) @@ -158,7 +158,7 @@ def send_reset_ssh_key_mail(user):
    """) % { 'name': user.name, - 'login_url': reverse('users:login', external=True), + 'login_url': reverse('authentication:login', external=True), } if settings.DEBUG: logger.debug(message) diff --git a/apps/users/views/login.py b/apps/users/views/login.py index cafcf2b0d..0856a1022 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -24,7 +24,7 @@ from .. import forms __all__ = [ 'UserLoginView', 'UserForgotPasswordSendmailSuccessView', 'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView', - 'UserResetPasswordView', 'UserForgotPasswordView', + 'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView', ] @@ -58,7 +58,7 @@ class UserForgotPasswordSendmailSuccessView(TemplateView): 'title': _('Send reset password message'), 'messages': _('Send reset password mail success, ' 'login your mail box and follow it '), - 'redirect_url': reverse('users:login'), + 'redirect_url': reverse('authentication:login'), } kwargs.update(context) return super().get_context_data(**kwargs) @@ -71,7 +71,7 @@ class UserResetPasswordSuccessView(TemplateView): context = { 'title': _('Reset password success'), 'messages': _('Reset password success, return to login page'), - 'redirect_url': reverse('users:login'), + 'redirect_url': reverse('authentication:login'), 'auto_redirect': True, } kwargs.update(context) diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 60fb7cca3..2ffbd4688 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -574,7 +574,7 @@ class UserOtpSettingsSuccessView(TemplateView): 'title': title, 'messages': describe, 'interval': 1, - 'redirect_url': reverse('users:login'), + 'redirect_url': reverse('authentication:login'), 'auto_redirect': True, } kwargs.update(context)