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)