1from plain.auth import get_user_model
2from plain.signing import BadSignature, SignatureExpired
3from plain.urls import reverse
4
5from . import signing
6
7
8class LoginLinkExpired(Exception):
9 pass
10
11
12class LoginLinkInvalid(Exception):
13 pass
14
15
16class LoginLinkChanged(Exception):
17 pass
18
19
20def generate_link_url(*, request, user, email, expires_in):
21 """
22 Generate a login link using both the user's PK
23 and email address, so links break if the user email changes or is assigned to another user.
24 """
25 token = signing.dumps({"user_pk": user.pk, "email": email}, expires_in=expires_in)
26
27 return request.build_absolute_uri(reverse("loginlink:login", token))
28
29
30def get_link_token_user(token):
31 """
32 Validate a link token and get the user from it.
33 """
34 try:
35 signed_data = signing.loads(token)
36 except SignatureExpired:
37 raise LoginLinkExpired()
38 except BadSignature:
39 raise LoginLinkInvalid()
40
41 user_model = get_user_model()
42 user_pk = signed_data["user_pk"]
43 email = signed_data["email"]
44
45 try:
46 return user_model.objects.get(pk=user_pk, email__iexact=email)
47 except user_model.DoesNotExist:
48 raise LoginLinkChanged()