Commit 56519354 authored by 八千流's avatar 八千流 Committed by 老广

Email reset password (#2547)

* [Update]更改英文登录界面标题,可两行

* [Update] 更改用户通过邮箱修改密码后,该链接就失效

* [Update]更改页面左上角logo_text图片

* [Update] 优化发送邮箱改密连接失效代码

* [Update] 优化发送邮箱改密连接失效代码(2)

* [Update] 优化发送邮箱改密连接失效代码(3)

* [Update] 更新interface一键恢复默认的翻译

* [Update] 更改登录失败 用户名密码错误信息的翻译

* [Update] 优化生成token并设置缓存的代码
parent 78e4e13f
...@@ -14,6 +14,14 @@ class UserLoginForm(AuthenticationForm): ...@@ -14,6 +14,14 @@ class UserLoginForm(AuthenticationForm):
max_length=128, strip=False max_length=128, strip=False
) )
error_messages = {
'invalid_login': _(
"Please enter a correct username and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def confirm_login_allowed(self, user): def confirm_login_allowed(self, user):
if not user.is_staff: if not user.is_staff:
raise forms.ValidationError( raise forms.ValidationError(
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</style> </style>
</head> </head>
<body style="height: 100%"> <body style="height: 100%;font-size: 13px">
<div> <div>
<div class="box-1"> <div class="box-1">
<div class="box-2"> <div class="box-2">
...@@ -60,19 +60,19 @@ ...@@ -60,19 +60,19 @@
</div> </div>
<div class="box-3"> <div class="box-3">
<div style="background-color: white"> <div style="background-color: white">
<div style="margin-top: 40px;padding-top: 50px;"> <div style="margin-top: 30px;padding-top: 40px;padding-left: 20px;padding-right: 20px;height: 80px">
<span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span> <span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span>
</div> </div>
<div style="font-size: 12px;color: #999999;letter-spacing: 0;line-height: 18px;margin-top: 10px"> <div style="font-size: 12px;color: #999999;letter-spacing: 0;line-height: 18px;margin-top: 18px">
{% trans 'Welcome back, please enter username and password to login' %} {% trans 'Welcome back, please enter username and password to login' %}
</div> </div>
<div style="margin-bottom: 10px"> <div style="margin-bottom: 10px">
<div> <div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
<div class="contact-form col-md-10" style="margin-top: 20px;height: 35px"> <div class="contact-form col-md-10" style="margin-top: 10px;height: 35px">
<form id="contact-form" action="" method="post" role="form" novalidate="novalidate"> <form id="contact-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %} {% csrf_token %}
<div style="height: 48px;color: red"> <div style="height: 45px;color: red;line-height: 17px;">
{% if block_login %} {% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p> <p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
{% elif password_expired %} {% elif password_expired %}
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required=""> <input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
</div> </div>
<div class="form-group" style="height: 50px;margin-bottom: 0"> <div class="form-group" style="height: 50px;margin-bottom: 0;font-size: 13px">
{{ form.captcha }} {{ form.captcha }}
</div> </div>
<div class="form-group" style="margin-top: 10px"> <div class="form-group" style="margin-top: 10px">
......
This diff is collapsed.
...@@ -3,24 +3,28 @@ ...@@ -3,24 +3,28 @@
# #
import uuid import uuid
import base64 import base64
import string
import random
from collections import OrderedDict from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core import signing
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.shortcuts import reverse from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default from common.utils import get_signer, date_expired_default, get_logger
__all__ = ['User'] __all__ = ['User']
signer = get_signer() signer = get_signer()
logger = get_logger(__file__)
class User(AbstractUser): class User(AbstractUser):
ROLE_ADMIN = 'Admin' ROLE_ADMIN = 'Admin'
...@@ -47,6 +51,9 @@ class User(AbstractUser): ...@@ -47,6 +51,9 @@ class User(AbstractUser):
(SOURCE_OPENID, 'OpenID'), (SOURCE_OPENID, 'OpenID'),
(SOURCE_RADIUS, 'Radius'), (SOURCE_RADIUS, 'Radius'),
) )
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField( username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username') max_length=128, unique=True, verbose_name=_('Username')
...@@ -346,9 +353,32 @@ class User(AbstractUser): ...@@ -346,9 +353,32 @@ class User(AbstractUser):
return user_default return user_default
def generate_reset_token(self): def generate_reset_token(self):
return signer.sign_t( letter = string.ascii_letters + string.digits
{'reset': str(self.id), 'email': self.email}, expires_in=3600 token =''.join([random.choice(letter) for _ in range(50)])
) self.set_cache(token)
return token
def set_cache(self, token):
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
@classmethod
def validate_reset_password_token(cls, token):
try:
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
value = cache.get(key)
user_id = value.get('id', '')
email = value.get('email', '')
user = cls.objects.get(id=user_id, email=email)
except (AttributeError, cls.DoesNotExist) as e:
logger.error(e, exc_info=True)
user = None
return user
@classmethod
def expired_reset_password_token(cls, token):
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.delete(key)
@property @property
def otp_enabled(self): def otp_enabled(self):
...@@ -400,18 +430,6 @@ class User(AbstractUser): ...@@ -400,18 +430,6 @@ class User(AbstractUser):
access_key = app.create_access_key() access_key = app.create_access_key()
return app, access_key return app, access_key
@classmethod
def validate_reset_token(cls, token):
try:
data = signer.unsign_t(token)
user_id = data.get('reset', None)
user_email = data.get('email', '')
user = cls.objects.get(id=user_id, email=user_email)
except (signing.BadSignature, cls.DoesNotExist):
user = None
return user
def reset_password(self, new_password): def reset_password(self, new_password):
self.set_password(new_password) self.set_password(new_password)
self.date_password_last_updated = timezone.now() self.date_password_last_updated = timezone.now()
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.cache import cache
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import RedirectView from django.views.generic import RedirectView
...@@ -84,7 +85,7 @@ class UserResetPasswordView(TemplateView): ...@@ -84,7 +85,7 @@ class UserResetPasswordView(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
token = request.GET.get('token', '') token = request.GET.get('token', '')
user = User.validate_reset_token(token) user = User.validate_reset_password_token(token)
if not user: if not user:
kwargs.update({'errors': _('Token invalid or expired')}) kwargs.update({'errors': _('Token invalid or expired')})
else: else:
...@@ -100,12 +101,12 @@ class UserResetPasswordView(TemplateView): ...@@ -100,12 +101,12 @@ class UserResetPasswordView(TemplateView):
if password != password_confirm: if password != password_confirm:
return self.get(request, errors=_('Password not same')) return self.get(request, errors=_('Password not same'))
user = User.validate_reset_token(token) user = User.validate_reset_password_token(token)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
if not user.can_update_password(): if not user.can_update_password():
error = _('User auth from {}, go there change password'.format(user.source)) error = _('User auth from {}, go there change password'.format(user.source))
return self.get(request, errors=error) return self.get(request, errors=error)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
is_ok = check_password_rules(password) is_ok = check_password_rules(password)
if not is_ok: if not is_ok:
...@@ -115,6 +116,7 @@ class UserResetPasswordView(TemplateView): ...@@ -115,6 +116,7 @@ class UserResetPasswordView(TemplateView):
) )
user.reset_password(password) user.reset_password(password)
User.expired_reset_password_token(token)
return HttpResponseRedirect(reverse('users:reset-password-success')) return HttpResponseRedirect(reverse('users:reset-password-success'))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment