Если с причиной смены адреса электронной почты все просто — пользователь решил пользоваться другим почтовым ящиком, то желание поменять пароль может возникнуть по нескольким причинам.
Например, пользователь в целях профилактики может периодически изменять пароль будучи авторизированном в системе. Или пароль пользователя мог быть скомпрометирован или попросту забыт.
В последнем случае обычно система высылает новый сгенерированный сервисом пароль на адрес электронной почты, а затем "заботливо" советует сменить его в личном кабинете после успешной авторизации.
От подобных пагубных практик необходимо отходить, пропуская ненужные шаги и давая пользователю возможность сразу задать новый пароль с подтверждением данной операции по электронной почте, указанной при регистрации данного аккаунта.
Формы
Переходим в каталог accounts и открываем forms.py для добавления следующих строк:
class ChangePasswordForm(forms.Form):
password = forms.CharField(label='Новый пароль', max_length=255, strip=False,
widget=forms.PasswordInput(attrs={'autofocus': ''}))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(ChangePasswordForm, self).__init__(*args, **kwargs)
def clean_password(self):
# проверим пароль с помощью списка валидаторов объявленного в settings.AUTH_PASSWORD_VALIDATORS
password = self.cleaned_data.get('password')
validate_password(password, self.user)
return password
def send_vcode(self, request):
Vcode(request).send(self.user, 'change_password', {
'password': self.cleaned_data['password'],
})
class AnonymousChangePasswordForm(ChangePasswordForm):
email = forms.EmailField(label='Адрес электронной почты', max_length=254,
widget=forms.EmailInput(attrs={'autofocus': ''}))
field_order = ['email', 'password']
def clean_email(self):
email = self.cleaned_data.get('email')
try:
self.user = User.active.get(email=email)
except User.DoesNotExist:
raise forms.ValidationError('Пользователя с данным адресом электронной почты не существует '
'или он неактивен')
return email
class ChangeEmailForm(forms.ModelForm):
email = forms.EmailField(label='Новый адрес электронной почты', max_length=254,
widget=forms.EmailInput(attrs={'autofocus': ''}),
error_messages={'unique': 'Данный адрес электронной почты уже используется'})
class Meta:
model = User
fields = ['email']
def send_vcode(self, request, user):
Vcode(request).send(user, 'change_email', {
'email': self.cleaned_data['email'],
})
ChangePasswordForm предлагает задать новый пароль и данную форму следует использовать, когда человек прошел процедуру авторизации.
AnonymousChangePasswordForm мы разработали для смены пароля, когда доступ к аккаунту по каким-то причинам был утерян. Мы наследуемся от предыдущей формы, поскольку функционал схож с прошлой, за исключением того, что необходимо добавить поле адреса электронной почты и проверить является ли пользователь активным на данный момент.
И в заключении ChangeEmailForm предлагает сменить адрес электронной почты на новый соответственно.
Во всех трех формах, как и при регистрации нового пользователя мы используем заранее разработанный функционал по отправке ссылок для подтверждения операций на закрепленный адрес электронной почты.
Представления
Открываем файл views.py и добавляем следующее:
from .decorators import auth_required
def change_password_view(request):
if request.user.is_authenticated:
form = forms.ChangePasswordForm(request.POST or None, user=request.user)
else:
form = forms.AnonymousChangePasswordForm(request.POST or None)
if form.is_valid():
form.send_vcode(request)
return redirect('accounts:change_password_confirm')
return render(request, 'accounts/change_password.html', {
'form': form,
})
def change_password_confirm_view(request, code):
status = 'waiting'
if code:
vcode = Vcode(request)
if vcode.is_valid(code, 'change_password'):
status = 'success'
user, data = vcode.get_content()
# меняем пароль
user.set_password(data['password'])
user.save()
# авторизуем пользователя
login(request, user, settings.ACCOUNTS_EMAILBACKEND)
else:
status = 'invalid'
return render(request, 'accounts/change_password_confirm.html', {
'status': status,
})
@auth_required
def change_email_view(request):
form = forms.ChangeEmailForm(request.POST or None)
if form.is_valid():
form.send_vcode(request, request.user)
return redirect('accounts:change_email_confirm')
return render(request, 'accounts/change_email.html', {
'form': form,
})
@auth_required
def change_email_confirm_view(request, code):
status = 'waiting'
if code:
vcode = Vcode(request)
if vcode.is_valid(code, 'change_email'):
status = 'success'
user, data = vcode.get_content()
# меняем email
user.email = data['email']
user.save()
else:
status = 'invalid'
return render(request, 'accounts/change_email_confirm.html', {
'status': status,
})
В первом представлении мы объединяем функционал смены пароля и для того, чтобы отобразить нужную форму, мы проверяем авторизирован ли пользователь на данный момент или нет. В случае правильности заполнения — отправляем подтверждение на почту или выводим шаблон с рекомендациями как исправить допущенные ошибки.
Если в представлении мы удачно подали запрос на смену тогда мы перенаправляем пользователя на change_password_confirm_view где сверяем коды, заканчиваем процедуру смены и авторизуем пользователя в системе.
Процедура авторизации нужна даже в том случае если пользователь на момент смены пароля был в системе. Все дело в том, что Django, в целях безопасности при смене пароля, приостанавливает сессию пользователя.
Представления по смене адреса электронной почты действуют аналогично.
Декоратор
Переходим к файлу decorators.py:
def auth_required(view_func):
def wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated:
return view_func(request, *args, **kwargs)
return render(request, 'accounts/auth_required.html')
return wrapped_view
Декоратор просто проверяет авторизован ли пользователь на данный момент, поскольку представления по смене адреса электронной почты следуют показывать только зашедшим в систему.
Маршруты
Файл urls.py теперь должен соответствовать следующему:
from django.conf.urls import url
from . import views
app_name = 'accounts'
urlpatterns = [
url(r'^join/$', views.join_view, name='join'),
url(r'^join/confirm/(?:(?P<code>\w+)/)?$', views.join_confirm_view, name='join_confirm'),
url(r'^join/resend/$', views.join_resend_view, name='join_resend'),
url(r'^login/$', views.login_view, name='login'),
url(r'^logout/$', views.logout_view, name='logout'),
url(r'^change-password/$', views.change_password_view, name='change_password'),
url(r'^change-password/confirm/(?:(?P<code>\w+)/)?$', views.change_password_confirm_view,
name='change_password_confirm'),
url(r'^change-email/$', views.change_email_view, name='change_email'),
url(r'^change-email/confirm/(?:(?P<code>\w+)/)?$', views.change_email_confirm_view,
name='change_email_confirm'),
]