Plain is headed towards 1.0! Subscribe for development updates →

 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()