Plain is headed towards 1.0! Subscribe for development updates →

plain.loginlink

Link-based authentication for Plain.

 1from plain.auth import login, logout
 2from plain.http import ResponseRedirect
 3from plain.runtime import settings
 4from plain.urls import reverse, reverse_lazy
 5from plain.views import FormView, TemplateView, View
 6
 7from .forms import LoginLinkForm
 8from .links import (
 9    LoginLinkChanged,
10    LoginLinkExpired,
11    LoginLinkInvalid,
12    get_link_token_user,
13)
14
15
16class LoginLinkFormView(FormView):
17    form_class = LoginLinkForm
18    success_url = reverse_lazy("loginlink:sent")
19
20    def get(self):
21        # Redirect if the user is already logged in
22        if self.request.user:
23            form = self.get_form()
24            return ResponseRedirect(self.get_success_url(form))
25
26        return super().get()
27
28    def form_valid(self, form):
29        form.maybe_send_link(self.request)
30        return super().form_valid(form)
31
32    def get_success_url(self, form):
33        if next_url := form.cleaned_data.get("next"):
34            # Keep the next URL in the query string so the sent
35            # view can redirect to it if reloaded and logged in already.
36            return f"{self.success_url}?next={next_url}"
37        else:
38            return self.success_url
39
40
41class LoginLinkSentView(TemplateView):
42    template_name = "loginlink/sent.html"
43
44    def get(self):
45        # Redirect if the user is already logged in
46        if self.request.user:
47            next_url = self.request.GET.get("next", "/")
48            return ResponseRedirect(next_url)
49
50        return super().get()
51
52
53class LoginLinkFailedView(TemplateView):
54    template_name = "loginlink/failed.html"
55
56    def get_template_context(self):
57        context = super().get_template_context()
58        context["error"] = self.request.GET.get("error")
59        context["login_url"] = reverse(settings.AUTH_LOGIN_URL)
60        return context
61
62
63class LoginLinkLoginView(View):
64    success_url = "/"
65
66    def get(self):
67        # If they're logged in, log them out and process the link again
68        if self.request.user:
69            logout(self.request)
70
71        token = self.url_kwargs["token"]
72
73        try:
74            user = get_link_token_user(token)
75        except LoginLinkExpired:
76            return ResponseRedirect(reverse("loginlink:failed") + "?error=expired")
77        except LoginLinkInvalid:
78            return ResponseRedirect(reverse("loginlink:failed") + "?error=invalid")
79        except LoginLinkChanged:
80            return ResponseRedirect(reverse("loginlink:failed") + "?error=changed")
81
82        login(self.request, user)
83
84        if next_url := self.request.GET.get("next"):
85            return ResponseRedirect(next_url)
86
87        return ResponseRedirect(self.success_url)