Plain is headed towards 1.0! Subscribe for development updates →

plain.password

Password authentication for Plain.

  1from plain import forms
  2from plain.auth import get_user_model
  3from plain.exceptions import ValidationError
  4from plain.models.forms import ModelForm
  5
  6from .core import check_user_password
  7from .hashers import check_password
  8
  9# class PasswordResetForm(forms.Form):
 10#     email = forms.EmailField(
 11#         # label="Email",
 12#         max_length=254,
 13#         # widget=forms.EmailInput(attrs={"autocomplete": "email"}),
 14#     )
 15
 16#     def send_mail(
 17#         self,
 18#         subject_template_name,
 19#         email_template_name,
 20#         context,
 21#         from_email,
 22#         to_email,
 23#         html_email_template_name=None,
 24#     ):
 25#         from plain.mail import EmailMultiAlternatives
 26
 27#         """
 28#         Send a plain.mail.EmailMultiAlternatives to `to_email`.
 29#         """
 30#         template = Template(subject_template_name)
 31#         subject = template.render(context)
 32#         # Email subject *must not* contain newlines
 33#         subject = "".join(subject.splitlines())
 34#         template = Template(email_template_name)
 35#         body = template.render(context)
 36
 37#         email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
 38#         if html_email_template_name is not None:
 39#             template = Template(html_email_template_name)
 40#             html_email = template.render(context)
 41#             email_message.attach_alternative(html_email, "text/html")
 42
 43#         email_message.send()
 44
 45#     def get_users(self, email):
 46#         """Given an email, return matching user(s) who should receive a reset.
 47
 48#         This allows subclasses to more easily customize the default policies
 49#         that prevent inactive users and users with unusable passwords from
 50#         resetting their password.
 51#         """
 52#         active_users = get_user_model()._default_manager.filter(email__iexact=email)
 53#         return (u for u in active_users if _unicode_ci_compare(email, u.email))
 54
 55#     def save(
 56#         self,
 57#         subject_template_name="auth/password_reset_subject.txt",
 58#         email_template_name="auth/password_reset_email.html",
 59#         use_https=False,
 60#         token_generator=default_token_generator,
 61#         from_email=None,
 62#         html_email_template_name=None,
 63#         extra_email_context=None,
 64#     ):
 65#         """
 66#         Generate a one-use only link for resetting password and send it to the
 67#         user.
 68#         """
 69#         email = self.cleaned_data["email"]
 70#         for user in self.get_users(email):
 71#             context = {
 72#                 "email": user.email,
 73#                 "uid": urlsafe_base64_encode(force_bytes(user.pk)),
 74#                 "user": user,
 75#                 "token": token_generator.make_token(user),
 76#                 "protocol": "https" if use_https else "http",
 77#                 **(extra_email_context or {}),
 78#             }
 79#             self.send_mail(
 80#                 subject_template_name,
 81#                 email_template_name,
 82#                 context,
 83#                 from_email,
 84#                 user.email,
 85#                 html_email_template_name=html_email_template_name,
 86#             )
 87
 88
 89# class SetPasswordForm(forms.Form):
 90#     """
 91#     A form that lets a user set their password without entering the old
 92#     password
 93#     """
 94
 95#     error_messages = {
 96#         "password_mismatch": "The two password fields didn’t match.",
 97#     }
 98#     new_password1 = forms.CharField(
 99#         # label="New password",
100#         # widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
101#         strip=False,
102#         # help_text=validators.password_validators_help_text_html(),
103#     )
104#     new_password2 = forms.CharField(
105#         # label="New password confirmation",
106#         strip=False,
107#         # widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
108#     )
109
110#     def __init__(self, user, *args, **kwargs):
111#         self.user = user
112#         super().__init__(*args, **kwargs)
113
114#     def clean_new_password2(self):
115#         password1 = self.cleaned_data.get("new_password1")
116#         password2 = self.cleaned_data.get("new_password2")
117#         if password1 and password2 and password1 != password2:
118#             raise ValidationError(
119#                 self.error_messages["password_mismatch"],
120#                 code="password_mismatch",
121#             )
122#         validators.validate_password(password2, self.user)
123#         return password2
124
125#     def save(self, commit=True):
126#         password = self.cleaned_data["new_password1"]
127#         self.user.set_password(password)
128#         if commit:
129#             self.user.save()
130#         return self.user
131
132
133# class PasswordChangeForm(SetPasswordForm):
134#     """
135#     A form that lets a user change their password by entering their old
136#     password.
137#     """
138
139#     error_messages = {
140#         **SetPasswordForm.error_messages,
141#         "password_incorrect": "Your old password was entered incorrectly. Please enter it again.",
142#     }
143#     old_password = forms.CharField(
144#         # label="Old password",
145#         strip=False,
146#         # widget=forms.PasswordInput(
147#         #     attrs={"autocomplete": "current-password", "autofocus": True}
148#         # ),
149#     )
150
151#     field_order = ["old_password", "new_password1", "new_password2"]
152
153#     def clean_old_password(self):
154#         """
155#         Validate that the old_password field is correct.
156#         """
157#         old_password = self.cleaned_data["old_password"]
158#         if not self.user.check_password(old_password):
159#             raise ValidationError(
160#                 self.error_messages["password_incorrect"],
161#                 code="password_incorrect",
162#             )
163#         return old_password
164
165
166class PasswordLoginForm(forms.Form):
167    email = forms.EmailField(max_length=150)
168    password = forms.CharField(strip=False)
169
170    def clean(self):
171        User = get_user_model()
172
173        email = self.cleaned_data.get("email")
174        password = self.cleaned_data.get("password")
175
176        if email and password:
177            try:
178                # The vast majority of users won't have a case-sensitive email, so we act that way
179                user = User.objects.get(email__iexact=email)
180            except User.DoesNotExist:
181                # Run the default password hasher once to reduce the timing
182                # difference between an existing and a nonexistent user (django #20760).
183                check_password(password, "")
184
185                raise ValidationError(
186                    "Please enter a correct email and password. Note that both fields may be case-sensitive.",
187                    code="invalid_login",
188                )
189
190            if not check_user_password(user, password):
191                raise ValidationError(
192                    "Please enter a correct email and password. Note that both fields may be case-sensitive.",
193                    code="invalid_login",
194                )
195
196            self._user = user
197
198        return self.cleaned_data
199
200    def get_user(self):
201        return self._user
202
203
204class PasswordSignupForm(ModelForm):
205    confirm_password = forms.CharField(strip=False)
206
207    class Meta:
208        model = get_user_model()
209        fields = ("email", "password")
210
211    def clean(self):
212        cleaned_data = super().clean()
213        password = cleaned_data.get("password")
214        confirm_password = cleaned_data.get("confirm_password")
215        if password and confirm_password and password != confirm_password:
216            raise ValidationError(
217                "The two password fields didn't match.",
218                code="password_mismatch",
219            )
220        return cleaned_data