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 RedirectResponse, Response
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 RedirectResponse(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 return RedirectResponse(self.request.query_params.get("next", "/"))
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(AuthView, 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 RedirectResponse(reverse("loginlink:failed") + "?error=expired")
82 except LoginLinkInvalid:
83 return RedirectResponse(reverse("loginlink:failed") + "?error=invalid")
84 except LoginLinkChanged:
85 return RedirectResponse(reverse("loginlink:failed") + "?error=changed")
86
87 login(self.request, user)
88
89 if next_url := self.request.query_params.get("next"):
90 return RedirectResponse(next_url)
91
92 return RedirectResponse(self.success_url)