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