import markdown_it from django import forms from django.conf import settings from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.password_validation import validate_password from django.contrib.auth.views import LoginView, LogoutView from django.http import Http404 from django.shortcuts import get_object_or_404, render from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from django.views.generic import FormView from core.models import Config from users.models import Invite, PasswordReset, User class Login(LoginView): class form_class(AuthenticationForm): error_messages = { "invalid_login": _("No account was found with that email and password."), "inactive": _("This account is inactive."), } template_name = "auth/login.html" class Logout(LogoutView): pass class Signup(FormView): template_name = "auth/signup.html" class form_class(forms.Form): email = forms.EmailField( help_text="We will send a link to this email to create your account.", ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Add the policies if they're defined policies = [] if Config.system.policy_rules: policies.append("Server Rules") if Config.system.policy_terms: policies.append("Terms of Service") if Config.system.policy_privacy: policies.append("Privacy Policy") if policies: links = "" for i, policy in enumerate(policies): if i == 0: links += policy elif i == len(policies) - 1: if len(policies) > 2: links += ", and " else: links += " and " links += policy else: links += ", " links += policy self.fields["policy"] = forms.BooleanField( label="Policies", help_text=f"Have you read the {links}, and agree to them?", widget=forms.Select( choices=[(False, "I do not agree"), (True, "I agree")] ), ) def clean_email(self): email = self.cleaned_data.get("email").lower() if not email: return if User.objects.filter(email=email).exists(): raise forms.ValidationError("This email already has an account") return email def dispatch(self, request, token=None, *args, **kwargs): # See if we have an invite token if token: self.invite = get_object_or_404(Invite, token=token) if not self.invite.valid: raise Http404() else: self.invite = None # Calculate if we're at or over the user limit self.at_max_users = False if ( Config.system.signup_max_users and User.objects.count() >= Config.system.signup_max_users ): self.at_max_users = True return super().dispatch(request, *args, **kwargs) def form_valid(self, form): # Don't allow anything if there's no invite and no signup allowed if (not Config.system.signup_allowed or self.at_max_users) and not self.invite: return self.render_to_response(self.get_context_data()) # Make the new user user = User.objects.create(email=form.cleaned_data["email"]) # Auto-promote the user to admin if that setting is set if settings.AUTO_ADMIN_EMAIL and user.email == settings.AUTO_ADMIN_EMAIL: user.admin = True user.save() PasswordReset.create_for_user(user) # Drop invite uses down if it has them if self.invite and self.invite.uses is not None: self.invite.uses -= 1 if self.invite.uses <= 0: self.invite.delete() else: self.invite.save() return render( self.request, "auth/signup_success.html", {"email": user.email}, ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if (not Config.system.signup_allowed or self.at_max_users) and not self.invite: del context["form"] if Config.system.signup_text: context["signup_text"] = mark_safe( markdown_it.MarkdownIt().render(Config.system.signup_text) ) return context class TriggerReset(FormView): template_name = "auth/trigger_reset.html" class form_class(forms.Form): email = forms.EmailField( help_text="We will send a reset link to this email", ) def clean_email(self): email = self.cleaned_data.get("email").lower() if not email: return if not User.objects.filter(email=email).exists(): raise forms.ValidationError("This email does not have an account") return email def form_valid(self, form): PasswordReset.create_for_user( User.objects.get(email=form.cleaned_data["email"]) ) return render( self.request, "auth/trigger_reset_success.html", {"email": form.cleaned_data["email"]}, ) class PerformReset(FormView): template_name = "auth/perform_reset.html" class form_class(forms.Form): password = forms.CharField( widget=forms.PasswordInput, help_text="Must be at least 8 characters, and contain both letters and numbers.", ) repeat_password = forms.CharField( widget=forms.PasswordInput, ) def clean_password(self): password = self.cleaned_data["password"] validate_password(password) return password def clean_repeat_password(self): if self.cleaned_data.get("password") != self.cleaned_data.get( "repeat_password" ): raise forms.ValidationError("Passwords do not match") return self.cleaned_data.get("repeat_password") def dispatch(self, request, token): self.reset = get_object_or_404(PasswordReset, token=token) return super().dispatch(request) def form_valid(self, form): self.reset.user.set_password(form.cleaned_data["password"]) self.reset.user.save() self.reset.delete() return render( self.request, "auth/perform_reset_success.html", {"email": self.reset.user.email}, ) def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context["reset"] = self.reset return context