aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/utils/account.py
blob: 2d699c88014c57b8e910fcad677dbe32676a27f3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from typing import Any, Dict

from allauth.account.adapter import DefaultAccountAdapter
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialLogin
from django.contrib.auth.models import User as DjangoUser
from django.contrib.messages import ERROR, add_message
from django.http import HttpRequest
from django.shortcuts import redirect
from django.urls import reverse

from pydis_site.apps.api.models import User as DiscordUser

ERROR_CONNECT_DISCORD = ("You must login with Discord before connecting another account. "
                         "Your account details have not been saved.")
ERROR_JOIN_DISCORD = ("Please join the Discord server and verify that you accept the rules and "
                      "privacy policy.")


class AccountAdapter(DefaultAccountAdapter):
    """An Allauth account adapter that prevents signups via form submission."""

    def is_open_for_signup(self, request: HttpRequest) -> bool:
        """
        Checks whether or not the site is open for signups.

        We override this to always return False so that users may never sign up using
        Allauth's signup form endpoints, to be on the safe side - since we only want users
        to sign up using their Discord account.
        """
        return False


class SocialAccountAdapter(DefaultSocialAccountAdapter):
    """An Allauth SocialAccount adapter that prevents signups via non-Discord connections."""

    def is_open_for_signup(self, request: HttpRequest, social_login: SocialLogin) -> bool:
        """
        Checks whether or not the site is open for signups.

        We override this method in order to prevent users from creating a new account using
        a non-Discord connection, as we require this connection for our users.
        """
        if social_login.account.provider != "discord":
            add_message(request, ERROR, ERROR_CONNECT_DISCORD)

            raise ImmediateHttpResponse(redirect(reverse("home")))

        try:
            user = DiscordUser.objects.get(id=int(social_login.account.uid))
        except DiscordUser.DoesNotExist:
            add_message(request, ERROR, ERROR_JOIN_DISCORD)

            raise ImmediateHttpResponse(redirect(reverse("home")))

        if user.roles.count() <= 1:
            add_message(request, ERROR, ERROR_JOIN_DISCORD)

            raise ImmediateHttpResponse(redirect(reverse("home")))

        return True

    def populate_user(self, request: HttpRequest,
                      social_login: SocialLogin,
                      data: Dict[str, Any]) -> DjangoUser:
        """
        Method used to populate a Django User with data.

        We override this so that the Django user is created with the username#discriminator,
        instead of just the username, as Django users must have unique usernames. For display
        purposes, we also set the `name` key, which is used for `first_name` in the database.
        """
        if social_login.account.provider == "discord":
            discriminator = social_login.account.extra_data["discriminator"]
            data["username"] = f"{data['username']}#{discriminator:0>4}"
            data["name"] = data["username"]

        return super().populate_user(request, social_login, data)