diff --git a/ws/settings.py b/ws/settings.py index 1515b719..9c931691 100644 --- a/ws/settings.py +++ b/ws/settings.py @@ -81,7 +81,6 @@ ] LOGIN_REDIRECT_URL = '/' -ACCOUNT_LOGOUT_REDIRECT_URL = '/' INSTALLED_APPS = [ 'django.contrib.admin', @@ -253,6 +252,21 @@ # Always "remember me" ACCOUNT_SESSION_REMEMBER = True +SOCIALACCOUNT_AUTO_SIGNUP = True +SOCIALACCOUNT_ADAPTER = "ws.social.TrustGoogleEmailOwnershipAdapter" + +SOCIALACCOUNT_PROVIDERS = { + "google": { + "APP": { + "client_id": "105568993872-llfunbenb7fndfl17b7bk4mv7uq1jgd5.apps.googleusercontent.com", + "secret": os.environ.get('GOOGLE_OAUTH_SECRET_KEY', ''), + "key": "", + }, + "SCOPE": ["email"], + "AUTH_PARAMS": {"access_type": "online"}, + } +} + BURSAR_NAME = os.getenv('BURSAR_NAME', 'MITOC Bursar') ROOT_URLCONF = 'ws.urls' diff --git a/ws/social.py b/ws/social.py new file mode 100644 index 00000000..77f7c3e4 --- /dev/null +++ b/ws/social.py @@ -0,0 +1,59 @@ +import logging + +from allauth.account.models import EmailAddress +from allauth.account.utils import user_email +from allauth.socialaccount.adapter import DefaultSocialAccountAdapter +from allauth.socialaccount.models import SocialLogin +from django.http import HttpRequest + +from ws import settings + +logger = logging.getLogger(__name__) + + +class TrustGoogleEmailOwnershipAdapter(DefaultSocialAccountAdapter): + """Let users with an existing account grant Google login access. + + This adapter exists to provide a better UX in the following scenario: + + 1. `alice@gmail.com` signs up for `mitoc-trips` with a email & password + 2. Alice signs out, time passes. + 3. Later, Alice "logs in with Google" + 4. Because an account exists under `alice@gmail.com`, we can't complete login + - Alice must first log in with her password, then associate Google + + By default, `django-allauth` will not let you automatically claim ownership + of an account just because a social provider vouches that you exist under + that email address. This makes sense. If you own a Facebook account under + `alice@gmail.com`, there's no guarantees that you're necessarily the same + person. + + However, because Google is an email provider, I think it's fair to assume + that if Google vouches for your identity, auto sign-in can be completed. + + Related: https://github.com/pennersr/django-allauth/issues/418 + """ + + def pre_social_login(self, request: HttpRequest, sociallogin: SocialLogin) -> None: + """Connect any Google-asserted email to accounts if existing.""" + if sociallogin.is_existing: # Social account exists (normal login) + return + + # I don't think there's an easy way to identify the provider in use... + # `request.path` should be at least be '/accounts/google/login/callback/' + assert set(settings.SOCIALACCOUNT_PROVIDERS) == {'google'} + + email: str = user_email(sociallogin.user) + + try: + verified_email = EmailAddress.objects.get( + email__iexact=email, + # This is critical. If we didn't require a *verified* email, then + # we could end up linking this to an existing account which belongs + # to a malicious user hoping that somebody will link their Google account. + verified=True, + ) + except EmailAddress.DoesNotExist: + return + + sociallogin.connect(request, verified_email.user) diff --git a/ws/static/google.svg b/ws/static/google.svg new file mode 100644 index 00000000..531aef61 --- /dev/null +++ b/ws/static/google.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ws/templates/account/login.html b/ws/templates/account/login.html index c24e5ddb..987836f4 100644 --- a/ws/templates/account/login.html +++ b/ws/templates/account/login.html @@ -1,15 +1,58 @@ {% extends "base_no_scripts.html" %} +{% load static %} {% load crispy_forms_tags %} +{% load socialaccount %} {% block head_title %}Log In{% endblock head_title %} +{% block css %} + {{ block.super }} + {# Google's guidelines require Roboto: https://developers.google.com/identity/branding-guidelines #} + + +{% endblock css %} + {% block login_button %}{% endblock login_button%} {% block content %} -

Log In

+
+
+
+ {% csrf_token %} + +
+ +
+
+
+ + +

Or log in by email

-

+

If you have not created an account yet, then please sign up first. +
+ (We do not automatically create an account after you pay dues or sign a waiver)