Plain is headed towards 1.0! Subscribe for development updates →

plain.password

Password authentication for Plain.

Usage

To enable password authentication in your Plain application, add the PasswordLoginView to your urls.py:

# app/urls.py
from plain.urls import path
from plain.passwords.views import PasswordLoginView

urlpatterns = [
    path('login/', PasswordLoginView.as_view(), name='login'),
    # ...
]

This sets up a basic login view where users can authenticate using their username and password.

FAQs

How do I customize the login form?

To customize the login form, you can subclass PasswordLoginForm and override its fields or methods as needed. Then, set the form_class attribute in your PasswordLoginView to use your custom form.

# app/forms.py
from plain.passwords.forms import PasswordLoginForm

class MyCustomLoginForm(PasswordLoginForm):
    # Add custom fields or override methods here
    pass
# app/views.py
from plain.passwords.views import PasswordLoginView
from .forms import MyCustomLoginForm

class MyPasswordLoginView(PasswordLoginView):
    form_class = MyCustomLoginForm

Update your urls.py to use your custom view:

# app/urls.py
from plain.urls import path
from .views import MyPasswordLoginView

urlpatterns = [
    path('login/', MyPasswordLoginView.as_view(), name='login'),
    # ...
]
  1# Avoid shadowing the login() and logout() views below.
  2from plain.auth.sessions import USER_HASH_SESSION_KEY, get_session_auth_hash
  3from plain.auth.sessions import login as auth_login
  4from plain.http import (
  5    ResponseRedirect,
  6)
  7from plain.views import CreateView, FormView
  8
  9from .forms import (
 10    # PasswordChangeForm,
 11    # PasswordResetForm,
 12    # SetPasswordForm,
 13    PasswordLoginForm,
 14    PasswordSignupForm,
 15)
 16
 17
 18def update_session_auth_hash(request, user):
 19    """
 20    Updating a user's password logs out all sessions for the user.
 21
 22    Take the current request and the updated user object from which the new
 23    session hash will be derived and update the session hash appropriately to
 24    prevent a password change from logging out the session from which the
 25    password was changed.
 26    """
 27    request.session.cycle_key()
 28    if request.user == user:
 29        request.session[USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
 30
 31
 32# Class-based password reset views
 33# - PasswordResetView sends the mail
 34# - PasswordResetDoneView shows a success message for the above
 35# - PasswordResetConfirmView checks the link the user clicked and
 36#   prompts for a new password
 37# - PasswordResetCompleteView shows a success message for the above
 38
 39
 40# class PasswordContextMixin:
 41#     extra_context = None
 42
 43#     def get_template_context(self):
 44#         context = super().get_template_context()
 45#         context.update(
 46#             {"title": self.title, "subtitle": None, **(self.extra_context or {})}
 47#         )
 48#         return context
 49
 50
 51# class PasswordResetView(PasswordContextMixin, FormView):
 52#     email_template_name = "auth/password_reset_email.html"
 53#     extra_email_context = None
 54#     form_class = PasswordResetForm
 55#     from_email = None
 56#     html_email_template_name = None
 57#     subject_template_name = "auth/password_reset_subject.txt"
 58#     success_url = reverse_lazy("password_reset_done")
 59#     template_name = "auth/password_reset_form.html"
 60#     title = "Password reset"
 61#     token_generator = default_token_generator
 62
 63#     def form_valid(self, form):
 64#         opts = {
 65#             "use_https": self.request.is_https(),
 66#             "token_generator": self.token_generator,
 67#             "from_email": self.from_email,
 68#             "email_template_name": self.email_template_name,
 69#             "subject_template_name": self.subject_template_name,
 70#             "html_email_template_name": self.html_email_template_name,
 71#             "extra_email_context": self.extra_email_context,
 72#         }
 73#         form.save(**opts)
 74#         return super().form_valid(form)
 75
 76
 77# INTERNAL_RESET_SESSION_TOKEN = "_password_reset_token"
 78
 79
 80# class PasswordResetDoneView(PasswordContextMixin, TemplateView):
 81#     template_name = "auth/password_reset_done.html"
 82#     title = "Password reset sent"
 83
 84
 85# class PasswordResetConfirmView(PasswordContextMixin, FormView):
 86#     form_class = SetPasswordForm
 87#     post_reset_login = False
 88#     post_reset_login_backend = None
 89#     reset_url_token = "set-password"
 90#     success_url = reverse_lazy("password_reset_complete")
 91#     template_name = "auth/password_reset_confirm.html"
 92#     title = "Enter new password"
 93#     token_generator = default_token_generator
 94
 95#     def get_response(self):
 96#         if "uidb64" not in self.url_kwargs or "token" not in self.url_kwargs:
 97#             raise ImproperlyConfigured(
 98#                 "The URL path must contain 'uidb64' and 'token' parameters."
 99#             )
100
101#         self.validlink = False
102#         self.user = self.get_user(self.url_kwargs["uidb64"])
103
104#         if self.user is not None:
105#             token = self.url_kwargs["token"]
106#             if token == self.reset_url_token:
107#                 session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN)
108#                 if self.token_generator.check_token(self.user, session_token):
109#                     # If the token is valid, display the password reset form.
110#                     self.validlink = True
111#                     response = super().get_response()
112#                     add_never_cache_headers(response)
113#                     return response
114#             else:
115#                 if self.token_generator.check_token(self.user, token):
116#                     # Store the token in the session and redirect to the
117#                     # password reset form at a URL without the token. That
118#                     # avoids the possibility of leaking the token in the
119#                     # HTTP Referer header.
120#                     self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token
121#                     redirect_url = self.request.path.replace(
122#                         token, self.reset_url_token
123#                     )
124#                     response = ResponseRedirect(redirect_url)
125#                     add_never_cache_headers(response)
126#                     return response
127
128#         # Display the "Password reset unsuccessful" page.
129#         response = self.render_to_response(self.get_template_context())
130#         add_never_cache_headers(response)
131#         return response
132
133#     def get_user(self, uidb64):
134#         UserModel = get_user_model()
135#         try:
136#             # urlsafe_base64_decode() decodes to bytestring
137#             uid = urlsafe_base64_decode(uidb64).decode()
138#             user = UserModel._default_manager.get(pk=uid)
139#         except (
140#             TypeError,
141#             ValueError,
142#             OverflowError,
143#             UserModel.DoesNotExist,
144#             ValidationError,
145#         ):
146#             user = None
147#         return user
148
149#     def get_form_kwargs(self):
150#         kwargs = super().get_form_kwargs()
151#         kwargs["user"] = self.user
152#         return kwargs
153
154#     def form_valid(self, form):
155#         user = form.save()
156#         del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
157#         if self.post_reset_login:
158#             auth_login(self.request, user, self.post_reset_login_backend)
159#         return super().form_valid(form)
160
161#     def get_template_context(self):
162#         context = super().get_template_context()
163#         if self.validlink:
164#             context["validlink"] = True
165#         else:
166#             context.update(
167#                 {
168#                     "form": None,
169#                     "title": "Password reset unsuccessful",
170#                     "validlink": False,
171#                 }
172#             )
173#         return context
174
175
176# class PasswordResetCompleteView(PasswordContextMixin, TemplateView):
177#     template_name = "auth/password_reset_complete.html"
178#     title = "Password reset complete"
179
180#     def get_template_context(self):
181#         context = super().get_template_context()
182#         context["login_url"] = resolve_url(settings.AUTH_LOGIN_URL)
183#         return context
184
185
186# class PasswordChangeView(PasswordContextMixin, AuthViewMixin, FormView):
187#     form_class = PasswordChangeForm
188#     success_url = reverse_lazy("password_change_done")
189#     template_name = "auth/password_change_form.html"
190#     title = "Password change"
191#     login_required = True
192
193#     def get_form_kwargs(self):
194#         kwargs = super().get_form_kwargs()
195#         kwargs["user"] = self.request.user
196#         return kwargs
197
198#     def form_valid(self, form):
199#         form.save()
200#         # Updating the password logs out all other sessions for the user
201#         # except the current one.
202#         update_session_auth_hash(self.request, form.user)
203#         return super().form_valid(form)
204
205
206# class PasswordChangeDoneView(PasswordContextMixin, AuthViewMixin, TemplateView):
207#     template_name = "auth/password_change_done.html"
208#     title = "Password change successful"
209#     login_required = True
210
211
212class PasswordLoginView(FormView):
213    form_class = PasswordLoginForm
214    success_url = "/"
215
216    def get(self):
217        # Redirect if the user is already logged in
218        if self.request.user:
219            return ResponseRedirect(self.success_url)
220
221        return super().get()
222
223    def form_valid(self, form):
224        # Log the user in and redirect
225        auth_login(self.request, form.get_user())
226
227        return super().form_valid(form)
228
229
230class PasswordSignupView(CreateView):
231    form_class = PasswordSignupForm
232    success_url = "/"
233
234    def form_valid(self, form):
235        # # Log the user in and redirect
236        # auth_login(self.request, form.save())
237
238        return super().form_valid(form)