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