django

Как быстро настроить email-аутентификацию в Django

  • суббота, 11 ноября 2017 г. в 03:13:33
https://habrahabr.ru/post/341704/
  • Python
  • Open source
  • Django


Всем привет!


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


Когда мне понадобилось реализовать этот функционал, оказалось что существует не так много туториалов, особенно на русском, в которых бы описывалось как сделать регистрацию по email, отправку верифицирующего письма, сброс пароля и другие, в общем то вполне обычные вещи.
Я решил исправить эту несправедливость.


Django


Покопавшись немного в интернетах, мой выбор пал на модуль django-user-accounts.
Этот модуль — часть экосистемы Pinax, имеет неплохую документацию и, что самое приятное, немного легко-читаемого кода.


Модуль умеет:


  • Регистрировать пользователя по Email;
  • Отправлять письмо с подтверждающей ссылокой;
  • Аутентифицировать пользователя при помощи Email и пароля;
  • Изменять пароль из интерфейса;
  • Сбрасывать и восстанавливать пароль;
  • Отслеживать "протухание" пароля;
  • Изменять параметры аккаунта (например локаль или часовой пояс);
  • Удалять аккаунт.

Инсталяция


Для установки выполняем команду:


pip install django-user-accounts

Не забываем добавить зависимость в файл requirements.txt:


django-user-accounts==2.0.3

В settings.py добавляем INSTALLED_APPS:


INSTALLED_APPS =[
    .....
    'django.contrib.sites',
    'account'
]

Важно добавить стандартный джанговский модуль sites, так как account от него зависит.
Дальше добавляем MIDDLEWARE_CLASSES:


MIDDLEWARE_CLASSES = [
    .....
    'account.middleware.LocaleMiddleware',
    'account.middleware.TimezoneMiddleware'
]

И context_processors:


TEMPLATES = [
    {
        .....
        'OPTIONS': {
            'context_processors': [
                .....
                'account.context_processors.account'
            ],
        },
    },
]

Для того, чтобы у нас email был уникальный и чтобы требовалось подтвеждение добавим два ключа в settings.py:


ACCOUNT_EMAIL_UNIQUE = True
ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True

Теперь можно выполнить миграции:


python manage.py migrate

В результате в БД появятся новые таблицы:


account_account
account_accountdeletion
account_emailaddress
account_emailconfirmation
account_passwordexpiry
account_passwordhistory
account_signupcode
account_signupcoderesult
django_site

Если у вас ранее не было подключенного модуля sites, то нужно создать сайт:


python manage.py shell
>>> from django.contrib.sites.models import Site
>>> site = Site(domain='localhost:8000', name='localhost:8000')
>>> site.save()
>>> site.id
2

И добавить в setting.py id нового сайта:


SITE_ID = 2

Все templates для необходимых страниц и текстов писем можно скачать из pinax-theme-bootstrap и просто поместить их по адресу yourproject/yourapp/templates/account.


Настройка


Если вы собираетесь подключать этот модуль на уровне Django-приложения, а не проекта, то для корректной работы маршрутизаций, добавим в settings.py следующие строки:


ACCOUNT_LOGIN_URL = 'yourapp:account_login'
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = ACCOUNT_LOGIN_URL
ACCOUNT_PASSWORD_RESET_REDIRECT_URL = ACCOUNT_LOGIN_URL
ACCOUNT_EMAIL_CONFIRMATION_URL = "yourapp:account_confirm_email"
ACCOUNT_SETTINGS_REDIRECT_URL = 'yourapp:account_settings'
ACCOUNT_PASSWORD_CHANGE_REDIRECT_URL = "yourapp:account_password"

И добавим урлы в файл yourapp/urls.py:


urlpatterns = [
    .....
    url(r"^account/", include("account.urls")),
    .....
]

Теперь доступны следующие адреса:


    account/signup/
    account/login/
    account/logout/
    account/confirm_email/<key>
    account/password/
    account/password/reset/
    account/password/reset/<token>
    account/settings/
    account/delete/

Осталось лишь добавить настройки для почтового сервера в settings.py:


DEFAULT_FROM_EMAIL = 'support@yoursite.ru'
EMAIL_HOST = "smtp.yoursmtpserver.ru"
EMAIL_PORT = 25
EMAIL_HOST_USER = "user"
EMAIL_HOST_PASSWORD = "pass"

Если вы всё правильно сделали, то по указанным урлам можно логиниться при помощи логина/пароля, а при создании учетки вам на почту будет отправлено письмо для подтверждения email-а.


Лирическое отступление


Была обнаружена бага, которая не позволяет корректно сформировать ссылку на восстановление пароля если вы добавили ссылки в приложение, а не в проект.
Пока не принят пулл-реквест можно вбить костыль в файл yourproject/urls.py


from account.views import PasswordResetTokenView

urlpatterns = [
    .....
    url(r"^account/password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$", PasswordResetTokenView.as_view(), name="account_password_reset_token"),
    .....
]

В пулл-реквесте появится новая настройка:


ACCOUNT_PASSWORD_RESET_TOKEN_URL = 'yourapp:account_password_reset_token'

Аутентификация по Email


Для начала добавляем соответсвующий backend в settings.py:


AUTHENTICATION_BACKENDS = [
    'account.auth_backends.EmailAuthenticationBackend',
]

Затем удаляем из формы регистрации username, для этого в файл yourapp/forms.py добавляем следующее:


import account.forms

class SignupForm(account.forms.SignupForm):

    def __init__(self, *args, **kwargs):
        super(SignupForm, self).__init__(*args, **kwargs)
        del self.fields["username"]

А в файл yourapp/views.py это:


import yourapp.forms
import account.forms
import account.views

class LoginView(account.views.LoginView):
    form_class = account.forms.LoginEmailForm

class SignupView(account.views.SignupView):

    form_class =  yourapp.forms.SignupForm

    def generate_username(self, form):
        username = form.cleaned_data["email"]
        return username

    def after_signup(self, form):
        # do something
        super(SignupView, self).after_signup(form)

Здесь мы для вьюшек задаем классы форм: LoginEmailForm и нашу SignupForm соответсвенно, а так же переопределяем метод after_signup, чтобы можно было подмешать в него какое-нибудь нужное нам поведение. По-умолчанию полю username присваивам значение email.


Осталось лишь переопределить наши url-ы в файле yourapp/urls.py:


from . import views

urlpatterns = [
    .....
    url(r"^account/login/$", views.LoginView.as_view(), name="account_login"),
    url(r"^account/signup/$", views.SignupView.as_view(), name="account_signup"),
    url(r"^account/", include("account.urls")),
    .....
]

Обращаю внимание, что вызов кастомных вьюшек должен идти перед подключением account.urls, иначе они не переопределятся.


Миграция старых данных


Для того чтобы текущие пользователи могли залогиниться нужно добавить их email адреса в таблицу account_emailaddress:


insert into account_emailaddress(email, verified, "primary", user_id) 
select au.email, True, True, au.id
from auth_user au
where au.email is not null

В данном случае в поле verified вставляется значение true, т.е. мы сразу их подтверждаем.


Для того, чтобы у всех пользователей появился аккаунт, заполнить таблицу account_account:


insert into account_account (timezone, "language", user_id)
select '','ru', id
from auth_user

Заключение


Подробнее о модуле django-user-account вы можете узнать в официальной документации.
Исходный код находится тут. Его полезно почитать, чтобы чуть лучше разобраться в том, как работает механизм auth в Django.


Надеюсь, эта статья поможет вам сэкономить время. Просьба поделиться в комментариях, какими инструментами пользуетесь вы.