generated from freelawproject/new-project-template
-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #258 from freelawproject/feat-handle-user-authenti…
…cation feat(user): Adds logic to handle authentication.
- Loading branch information
Showing
34 changed files
with
1,227 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<a href={{href}} class="underline text-yellow-600 hover:text-yellow-800" aria-label="{{text}}">{{text}}</a> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from django.conf import settings | ||
from django.http import HttpRequest | ||
from django.urls import reverse | ||
from django.utils.http import url_has_allowed_host_and_scheme | ||
|
||
|
||
def get_redirect_or_login_url(request: HttpRequest, field_name: str) -> str: | ||
"""Get the redirect if it's safe, or send the user to the login page | ||
:param request: The HTTP request | ||
:param field_name: The field where the redirect is located | ||
:return: Either the value requested or the default LOGIN_REDIRECT_URL, if | ||
a sanity or security check failed. | ||
""" | ||
url = request.GET.get(field_name, "") | ||
is_safe = is_safe_url(url, request) | ||
if not is_safe: | ||
return settings.LOGIN_REDIRECT_URL | ||
return url | ||
|
||
|
||
def is_safe_url(url: str, request: HttpRequest) -> bool: | ||
"""Check whether a redirect URL is safe | ||
Much of this code was grabbed from Django: | ||
1. Prevent open redirect attacks. Imagine getting an email: | ||
Subject: Your account is conformed | ||
Body: Click here to continue: https://www.courtlistener.com/?next=https://cortlistener.com/evil/thing | ||
Without proper redirect sanitation, a user might click that link, and get | ||
redirected to courtlistener.com, which could be a spoof of the real thing. | ||
1. Prevent illogical redirects. Like, don't let people redirect back to | ||
the sign-in or register page. | ||
1. Prevent garbage URLs (like empty ones or ones with spaces) | ||
1. Prevent dangerous URLs (like JavaScript) | ||
:param url: The URL to check | ||
:param request: The user request | ||
:return True if safe, else False | ||
""" | ||
sign_in_url = reverse("sign-in") in url | ||
register_in_url = reverse("register") in url | ||
# Fixes security vulnerability reported upstream to Python, where | ||
# whitespace can be provided in the scheme like "java\nscript:alert(bad)" | ||
garbage_url = any([c in url for c in ["\n", "\r", " "]]) | ||
no_url = not url | ||
not_safe_url = not url_has_allowed_host_and_scheme( | ||
url, | ||
allowed_hosts={request.get_host()}, | ||
require_https=request.is_secure(), | ||
) | ||
if any([sign_in_url, register_in_url, garbage_url, no_url, not_safe_url]): | ||
return False | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import environ | ||
|
||
env = environ.FileAwareEnv() | ||
|
||
from ..project.testing import TESTING | ||
|
||
if TESTING: | ||
HCAPTCHA_SITEKEY = "10000000-ffff-ffff-ffff-000000000001" | ||
HCAPTCHA_SECRET = "0x0000000000000000000000000000000000000000" | ||
else: | ||
HCAPTCHA_SITEKEY = env("HCAPTCHA_SITEKEY", default="") | ||
HCAPTCHA_SECRET = env("HCAPTCHA_SECRET", default="") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from disposable_email_domains import blocklist | ||
from django import forms | ||
from django.contrib.auth import get_user_model | ||
from django.contrib.auth.forms import ( | ||
AuthenticationForm, | ||
PasswordResetForm, | ||
UserCreationForm, | ||
) | ||
from django.contrib.auth.models import AbstractBaseUser | ||
from django.core.mail import send_mail | ||
from django.urls import reverse | ||
from hcaptcha.fields import hCaptchaField | ||
|
||
from .utils.email import EmailType, emails | ||
|
||
|
||
class ConfirmedEmailAuthenticationForm(AuthenticationForm): | ||
""" | ||
Tweak the AuthenticationForm class to ensure that only users with | ||
confirmed email addresses can log in. | ||
""" | ||
|
||
def __init__(self, *args, **kwargs) -> None: | ||
super().__init__(*args, **kwargs) | ||
|
||
def confirm_login_allowed(self, user: AbstractBaseUser) -> None: | ||
"""Make sure the user is active and has a confirmed email address | ||
If the given user cannot log in, this method should raise a | ||
``forms.ValidationError``. | ||
If the given user may log in, this method should return None. | ||
""" | ||
if not user.is_active: # type: ignore | ||
raise forms.ValidationError( | ||
self.error_messages["inactive"], | ||
code="inactive", | ||
) | ||
|
||
if not user.email_confirmed: # type: ignore | ||
raise forms.ValidationError( | ||
"Please validate your email address to log in." | ||
) | ||
|
||
|
||
class RegisterForm(UserCreationForm): | ||
class Meta: | ||
model = get_user_model() | ||
fields = [ | ||
"username", | ||
"email", | ||
"password1", | ||
"password2", | ||
"first_name", | ||
"last_name", | ||
] | ||
|
||
def clean_email(self): | ||
email = self.cleaned_data.get("email") | ||
_, domain_part = email.rsplit("@", 1) | ||
if domain_part in blocklist: | ||
raise forms.ValidationError( | ||
f"{domain_part} is a blocked email provider", | ||
code="bad_email_domain", | ||
) | ||
return email | ||
|
||
|
||
class OptInConsentForm(forms.Form): | ||
consent = forms.BooleanField( | ||
error_messages={ | ||
"required": "To create a new account, you must agree below.", | ||
}, | ||
required=True, | ||
) | ||
hcaptcha = hCaptchaField() | ||
|
||
|
||
class EmailConfirmationForm(forms.Form): | ||
email = forms.EmailField( | ||
widget=forms.EmailInput( | ||
attrs={ | ||
"placeholder": "Your Email Address", | ||
"autocomplete": "email", | ||
"autofocus": "on", | ||
} | ||
), | ||
required=True, | ||
) | ||
|
||
|
||
class CustomPasswordResetForm(PasswordResetForm): | ||
""" | ||
Tweaks the PasswordResetForm class to ensure we send a message | ||
even if we don't find an account with the recipient address | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
def save(self, *args, **kwargs) -> None: | ||
"""Override the usual save method to send a message if we don't find | ||
any accounts | ||
""" | ||
recipient_addr = self.cleaned_data["email"] | ||
users = self.get_users(recipient_addr) | ||
if not len(list(users)): | ||
email: EmailType = emails["no_account_found"] | ||
body = email["body"] % ("password reset", reverse("register")) | ||
send_mail( | ||
email["subject"], body, email["from_email"], [recipient_addr] | ||
) | ||
else: | ||
super().save(*args, **kwargs) |
20 changes: 20 additions & 0 deletions
20
bc/users/migrations/0004_remove_user_activation_key_remove_user_key_expires.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Generated by Django 4.1.7 on 2023-06-21 14:28 | ||
|
||
from django.db import migrations | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("users", "0003_user_activation_key_user_email_confirmed_and_more"), | ||
] | ||
|
||
operations = [ | ||
migrations.RemoveField( | ||
model_name="user", | ||
name="activation_key", | ||
), | ||
migrations.RemoveField( | ||
model_name="user", | ||
name="key_expires", | ||
), | ||
] |
Oops, something went wrong.