Skip to content

Latest commit

 

History

History
366 lines (259 loc) · 12.6 KB

File metadata and controls

366 lines (259 loc) · 12.6 KB

Модуль 9. Урок 63. Восстановление пароля пользователя в Django

Алгоритм восстановления и практическая реализация

Даже в хорошо спроектированном сервисе пользователи регулярно сталкиваются с одной и той же проблемой — забытый пароль. Проект Cinemahub не является исключением: пользователь может долго не заходить на сайт, активно пользоваться каталогом фильмов, а затем внезапно потерять доступ к аккаунту.

В этом уроке мы:

  • разберём идею алгоритма восстановления пароля;
  • изучим, как Django реализует этот механизм под капотом;
  • шаг за шагом подключим стандартные представления Django;
  • создадим собственные шаблоны;
  • настроим отправку писем;
  • проверим весь процесс от формы до входа с новым паролем.

Общая идея восстановления пароля

Восстановление пароля — это не просто форма с email. Это цепочка логически связанных шагов, каждый из которых решает свою задачу безопасности.

Алгоритм восстановления пароля выглядит так:

  1. Пользователь нажимает ссылку «Забыли пароль?» на странице входа.

  2. Открывается форма, где пользователь вводит email, указанный при регистрации.

  3. Django проверяет, существует ли пользователь с таким email.

  4. Если пользователь найден:

    • создаётся одноразовый токен;
    • формируется ссылка с этим токеном;
    • ссылка отправляется на email.
  5. Пользователь переходит по ссылке из письма.

  6. Django проверяет:

    • корректность токена;
    • срок его действия;
    • соответствие пользователю.
  7. Пользователь вводит новый пароль.

  8. Пароль сохраняется, токен становится недействительным.

  9. Пользователь видит сообщение об успешном изменении пароля.

Важно: 👉 На любом этапе пользователь не получает информации о том, существует ли email в системе. Это защита от подбора аккаунтов.


Почтовый бэкенд: подготовка окружения

Прежде чем писать код, нужно решить одну практическую задачу: куда будут отправляться письма во время разработки?

На этапе обучения и локальной разработки мы не используем реальный SMTP-сервер. Django предоставляет удобный инструмент — консольный email-бэкенд.

Файл: settings.py

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

Теперь все письма:

  • не отправляются в реальный почтовый ящик;
  • выводятся прямо в терминал, где запущен сервер.

Проверка почтового бэкенда

Перед тем как использовать механизм восстановления пароля, убедимся, что отправка писем работает.

Проверка через Django shell

python manage.py shell
from django.core.mail import send_mail

send_mail(
    subject="Cinemahub: восстановление пароля",
    message="Тестовое письмо",
    from_email="no-reply@cinemahub.local",
    recipient_list=["user@example.com"],
)

Если настройка выполнена правильно, письмо появится в консоли.


Используем встроенные классы Django

Django уже реализовал весь алгоритм восстановления пароля. Наша задача — правильно подключить и адаптировать его.

Мы будем использовать:

  • PasswordResetView
  • PasswordResetDoneView
  • PasswordResetConfirmView
  • PasswordResetCompleteView

Шаг 1. Маршруты восстановления пароля

Файл: users/urls.py

from django.urls import path
from django.urls import reverse_lazy
from django.contrib.auth.views import (
    PasswordResetView,
    PasswordResetDoneView,
    PasswordResetConfirmView,
    PasswordResetCompleteView,
)

app_name = "users"

urlpatterns = [
    path(
        "password-reset/",
        PasswordResetView.as_view(
            template_name="users/password_reset_form.html",
            email_template_name="users/password_reset_email.html",
            success_url=reverse_lazy("users:password_reset_done"),
        ),
        name="password_reset",
    ),
    path(
        "password-reset/done/",
        PasswordResetDoneView.as_view(
            template_name="users/password_reset_done.html"
        ),
        name="password_reset_done",
    ),
    path(
        "password-reset/<uidb64>/<token>/",
        PasswordResetConfirmView.as_view(
            template_name="users/password_reset_confirm.html",
            success_url=reverse_lazy("users:password_reset_complete"),
        ),
        name="password_reset_confirm",
    ),
    path(
        "password-reset/complete/",
        PasswordResetCompleteView.as_view(
            template_name="users/password_reset_complete.html"
        ),
        name="password_reset_complete",
    ),
]

Шаг 2. Форма запроса восстановления пароля

Файл: users/templates/users/password_reset_form.html

{% extends "base.html" %} {% block content %}
<h1>Восстановление пароля</h1>

<p>Укажите email, связанный с аккаунтом Cinemahub.</p>

<form method="post">
  {% csrf_token %} {% for field in form %}
  <p>
    <label for="{{ field.id_for_label }}">{{ field.label }}:</label>
    {{ field }}
  </p>
  <div class="form-error">{{ field.errors }}</div>
  {% endfor %}

  <button type="submit">Отправить письмо</button>
</form>
{% endblock %}

Шаг 3. Страница после отправки письма

Файл: users/templates/users/password_reset_done.html

{% extends "base.html" %} {% block content %}
<h1>Письмо отправлено</h1>

<p>
  Если аккаунт с таким email существует, инструкции по восстановлению пароля
  отправлены на почту.
</p>

<p>Проверьте папку «Спам», если письмо не пришло.</p>
{% endblock %}

Шаг 4. Шаблон письма

Файл: users/templates/users/password_reset_email.html

Вы получили это письмо, потому что был запрошен сброс пароля для аккаунта
Cinemahub. Перейдите по ссылке, чтобы задать новый пароль: {{ protocol }}://{{
domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %} Если вы
не запрашивали восстановление пароля — просто проигнорируйте это письмо.

Шаг 5. Форма ввода нового пароля

Файл: users/templates/users/password_reset_confirm.html

{% extends "base.html" %} {% block content %}
<h1>Новый пароль</h1>

<form method="post">
  {% csrf_token %}

  <div class="form-error">{{ form.non_field_errors }}</div>

  {% for field in form %}
  <p>
    <label for="{{ field.id_for_label }}">{{ field.label }}:</label>
    {{ field }}
  </p>
  <div class="form-error">{{ field.errors }}</div>
  {% endfor %}

  <button type="submit">Сохранить пароль</button>
</form>
{% endblock %}

Шаг 6. Страница завершения восстановления

Файл: users/templates/users/password_reset_complete.html

{% extends "base.html" %} {% block content %}
<h1>Пароль изменён</h1>

<p>Пароль вашего аккаунта Cinemahub успешно обновлён.</p>

<p>Теперь вы можете <a href="{% url 'users:login' %}">войти в систему</a>.</p>
{% endblock %}

Добавление ссылки «Забыли пароль?»

Файл: users/templates/users/login.html

<p>
  <a href="{% url 'users:password_reset' %}"> Забыли пароль? </a>
</p>

Файл для декодирования

Письмо будет выглядеть примерно так:

0JLRiyDQv9C+0LvRg9GH0LjQu9C4INGN0YLQviDQv9C40YHRjNC80L4sINC/0L7RgtC+0LzRgyDR
h9GC0L4g0LHRi9C7INC30LDQv9GA0L7RiNC10L0g0YHQsdGA0L7RgSDQv9Cw0YDQvtC70Y8K0LTQ
...

Тело письма закодировано в base64. Django сделал это автоматически, потому что:

  • используется UTF-8;
  • письмо потенциально содержит не ASCII-символы (русский текст);
  • консольный backend не декодирует письмо, а показывает «сырой» MIME-контент.

В реальном почтовом клиенте (Gmail, Mail.ru и т.п.) это письмо декодировалось бы автоматически и выглядело бы нормально.

Как удобно проверять письма в консоли

Во время обучения и разработки рекомендую делать так:

  • Скопировать всё письмо из консоли
  • Вставить его в любой base64 decoder
import base64

encoded = """
0JLRiyDQv9C+0LvRg9GH0LjQu9C4INGN0YLQviDQv9C40YHRjNC80L4sINC/0L7RgtC+0LzRgyDR
h9GC0L4g0LHRi9C7INC30LDQv9GA0L7RiNC10L0g0YHQsdGA0L7RgSDQv9Cw0YDQvtC70Y8K0LTQ
...
"""

print(base64.b64decode(encoded).decode("utf-8"))

Проверка работы в браузере

  1. Открой страницу входа.
  2. Нажми «Забыли пароль?».
  3. Введи email пользователя.
  4. Посмотри письмо в консоли.
  5. Перейди по ссылке из письма.
  6. Задай новый пароль.
  7. Авторизуйся с новым паролем.

Типичные ошибки и их причины

NoReverseMatch: password_reset_confirm

Причина: — забыто пространство имён users: в email-шаблоне.

Письмо не отправляется

Причина: — не настроен EMAIL_BACKEND.

Форма открывается, но пароль не меняется

Причина: — неверный или просроченный токен.


Практические задания

Повторить реализацию восстановления пароля.


Вопросы для самопроверки

  1. Почему для восстановления используется токен?
  2. Где формируется ссылка для сброса пароля?
  3. Почему нельзя показывать, существует ли email в системе?
  4. Чем отличаются PasswordResetView и PasswordChangeView?
  5. Где настраивается шаблон письма?
  6. Зачем нужен uidb64?
  7. Что произойдёт при повторном использовании токена?
  8. Почему Django рекомендует встроенный механизм?

Предыдущий урок | Следующий урок