Skip to content

Commit

Permalink
fix: auth flow (#120)
Browse files Browse the repository at this point in the history
* update paths

* try entrypoint script

* remove manage script

* load fixtures command

* fix: backends

* allow anyone to get a CSRF cookie

* rename session cookie

* rename cookie
  • Loading branch information
SKairinos authored Jun 19, 2024
1 parent e79403a commit c0ce515
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 24 deletions.
12 changes: 6 additions & 6 deletions codeforlife/settings/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ def get_databases(base_dir: Path): # pragma: no cover
# https://docs.djangoproject.com/en/3.2/ref/settings/#authentication-backends

AUTHENTICATION_BACKENDS = [
"codeforlife.user.auth.backends.EmailAndPasswordBackend",
"codeforlife.user.auth.backends.EmailBackend",
"codeforlife.user.auth.backends.OtpBackend",
"codeforlife.user.auth.backends.OtpBypassTokenBackend",
"codeforlife.user.auth.backends.UserIdAndLoginIdBackend",
"codeforlife.user.auth.backends.FirstNameAndPasswordAndClassIdBackend",
"codeforlife.user.auth.backends.StudentBackend",
"codeforlife.user.auth.backends.StudentAutoBackend",
]

# Sessions
Expand All @@ -79,7 +79,7 @@ def get_databases(base_dir: Path): # pragma: no cover
SESSION_ENGINE = "codeforlife.user.models.session"
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_NAME = "sessionid_httponly_true"
SESSION_COOKIE_NAME = "session_key"
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 60 * 60
SESSION_COOKIE_SECURE = True
Expand Down Expand Up @@ -144,12 +144,12 @@ def get_databases(base_dir: Path): # pragma: no cover
# URLs
# https://docs.djangoproject.com/en/3.2/ref/settings/#root-urlconf

ROOT_URLCONF = "src.service.urls"
ROOT_URLCONF = "src.urls"

# App
# https://docs.djangoproject.com/en/3.2/ref/settings/#wsgi-application

WSGI_APPLICATION = "src.service.wsgi.application"
WSGI_APPLICATION = "main.app"

# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
Expand Down
9 changes: 4 additions & 5 deletions codeforlife/user/auth/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
© Ocado Group
Created on 01/02/2024 at 14:48:57(+00:00).
"""

# TODO: Create a custom auth backend for Django admin permissions
from .email_and_password import EmailAndPasswordBackend
from .first_name_and_password_and_class_id import (
FirstNameAndPasswordAndClassIdBackend,
)
from .email import EmailBackend
from .otp import OtpBackend
from .otp_bypass_token import OtpBypassTokenBackend
from .user_id_and_login_id import UserIdAndLoginIdBackend
from .student import StudentBackend
from .student_auto import StudentAutoBackend
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .base import BaseBackend


class EmailAndPasswordBackend(BaseBackend):
class EmailBackend(BaseBackend):
"""Authenticate a user by checking their email and password."""

def authenticate( # type: ignore[override]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .base import BaseBackend


class FirstNameAndPasswordAndClassIdBackend(BaseBackend):
class StudentBackend(BaseBackend):
"""Authenticate a student using their first name, password and class ID."""

user_class = StudentUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,34 @@
from .base import BaseBackend


class UserIdAndLoginIdBackend(BaseBackend):
class StudentAutoBackend(BaseBackend):
"""Authenticate a student using their ID and auto-generated password."""

user_class = StudentUser

def authenticate( # type: ignore[override]
self,
request: t.Optional[HttpRequest],
user_id: t.Optional[int] = None,
login_id: t.Optional[str] = None,
student_id: t.Optional[int] = None,
auto_gen_password: t.Optional[str] = None,
**kwargs
):
if user_id is None or login_id is None:
if student_id is None or auto_gen_password is None:
return None

user = self.get_user(user_id)
if user:
try:
student = Student.objects.get(id=student_id)
except Student.DoesNotExist:
student = None

if student:
# TODO: refactor this
# Check the url against the student's stored hash.
student = Student.objects.get(new_user=user)
if (
student.login_id
# TODO: refactor this
and get_hashed_login_id(login_id) == student.login_id
student.new_user
and student.login_id
and get_hashed_login_id(auto_gen_password) == student.login_id
):
return user
return student.new_user

return None
38 changes: 38 additions & 0 deletions codeforlife/user/management/commands/load_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
© Ocado Group
Created on 10/06/2024 at 10:44:45(+01:00).
"""

import os
import typing as t

from django.apps import apps
from django.core.management import call_command
from django.core.management.base import BaseCommand


# pylint: disable-next=missing-class-docstring
class Command(BaseCommand):
help = "Loads all the fixtures of the specified apps."

def add_arguments(self, parser):
parser.add_argument("app_labels", nargs="*", type=str)

def handle(self, *args, **options):
fixture_labels: t.List[str] = []
for app_label in {*options["app_labels"], "user"}:
app_config = apps.app_configs[app_label]
fixtures_path = os.path.join(app_config.path, "fixtures")

self.stdout.write(f"{app_label} fixtures ({fixtures_path}):")
for fixture_label in os.listdir(fixtures_path):
if fixture_label in fixture_labels:
self.stderr.write(f"Duplicate fixture: {fixture_label}")
return

self.stdout.write(f" - {fixture_label}")
fixture_labels.append(fixture_label)

self.stdout.write()

call_command("loaddata", *fixture_labels)
3 changes: 3 additions & 0 deletions codeforlife/views/csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from ..permissions import AllowAny


class CookieView(APIView):
"""A view to get a CSRF cookie."""

http_method_names = ["get"]
permission_classes = [AllowAny]

@method_decorator(ensure_csrf_cookie)
def get(self, request: Request):
Expand Down

0 comments on commit c0ce515

Please sign in to comment.