Как быстро настроить email-аутентификацию в Django
- суббота, 11 ноября 2017 г. в 03:13:33
Всем привет!
Аутентификация пользователей уже давно является типовой задачей. В Django, как и в любом современном вэб-фреймворке, есть отличный механизм аутентификации пользователей.
Однако, этот механизм по умолчанию использует логин в качестве идентификатора, в то время как все мы уже привыкли использовать для входа email.
Когда мне понадобилось реализовать этот функционал, оказалось что существует не так много туториалов, особенно на русском, в которых бы описывалось как сделать регистрацию по email, отправку верифицирующего письма, сброс пароля и другие, в общем то вполне обычные вещи.
Я решил исправить эту несправедливость.
Покопавшись немного в интернетах, мой выбор пал на модуль django-user-accounts.
Этот модуль — часть экосистемы Pinax, имеет неплохую документацию и, что самое приятное, немного легко-читаемого кода.
Модуль умеет:
Для установки выполняем команду:
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'
Для начала добавляем соответсвующий 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.
Надеюсь, эта статья поможет вам сэкономить время. Просьба поделиться в комментариях, какими инструментами пользуетесь вы.