feat(auth): enhance user authentication system

- Implemented custom login and logout views replacing the default Django
auth views to provide a more tailored user experience.
- Enforced login requirements for main user area and content views,
ensuring that only authenticated users can access specific pages.
- Updated the base template to dynamically display user authentication
status, enhancing the user interface by showing whether a user is logged
in and their email.
- Added settings for login, logout, and post-login redirect URLs,
utilizing reverse_lazy for URL resolution, improving maintainability and
readability of URL management.
- Created a new login template that includes form handling and error
display to support the custom login view.

These changes create a more secure and user-friendly authentication
flow, reflecting a commitment to both security and user experience
improvements.
This commit is contained in:
Kumi 2024-03-27 07:56:39 +01:00
parent 6ba838f770
commit c9e9e99728
Signed by: kumi
GPG key ID: ECBCC9082395383F
5 changed files with 70 additions and 10 deletions

View file

@ -3,6 +3,8 @@ from autosecretkey import AutoSecretKey
from pathlib import Path
from urllib.parse import urlparse
from django.urls import reverse_lazy
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -241,3 +243,9 @@ FFMPEG_OPTIONS = {
FFMPEG_DEFAULT_OPTION = ASK.config.get("ffmpeg", "DefaultOption", fallback="default")
# TODO: CSP settings
# Authentication
LOGIN_URL = reverse_lazy("quackscape.users:login")
LOGIN_REDIRECT_URL = reverse_lazy("quackscape.users:categories")
LOGOUT_REDIRECT_URL = reverse_lazy("quackscape.users:login")

View file

@ -10,7 +10,11 @@
<div class="admin-header">
<h2>Quackscape</h2>
<div class="user-info">
{% if user.is_authenticated %}
<p>Logged in as <strong>{{ user.email }}</strong></p>
{% else %}
<p>Not logged in</p>
{% endif %}
</div>
</div>
<div class="container-fluid">

View file

@ -0,0 +1,38 @@
{% extends "users/base.html" %} <!-- Extend from your base template if you have one -->
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Login</h4>
</div>
<div class="card-body">
<form method="post" action="{% url 'quackscape.users:login' %}">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<div class="mb-3">
<label for="id_username" class="form-label">Username</label>
<input type="text" class="form-control" name="username" id="id_username" required>
</div>
<div class="mb-3">
<label for="id_password" class="form-label">Password</label>
<input type="password" class="form-control" name="password" id="id_password" required>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,13 +1,14 @@
from .views import UserAreaMainView, CategoriesView, CategoryView, FileUploadView
from .views import UserAreaMainView, CategoriesView, CategoryView, FileUploadView, Login, Logout
from django.urls import path
from django.contrib.auth.views import LogoutView, LoginView
app_name = 'quackscape.users'
urlpatterns = [
path('', UserAreaMainView.as_view(), name='user-area-main'),
path('categories/', CategoriesView.as_view(), name='categories'),
path('category/<uuid:category>/', CategoryView.as_view(), name='category'),
path('category/<uuid:category>/upload/', FileUploadView.as_view(), name='media-upload'),
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('login/', Login.as_view(), name='login'),
path('logout/', Logout.as_view(), name='logout'),
]

View file

@ -1,5 +1,7 @@
from django.views.generic import TemplateView, ListView, DetailView
from django.http import Http404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView, LogoutView
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
@ -21,12 +23,12 @@ class TitleMixin:
return context
class UserAreaMainView(TitleMixin, TemplateView):
class UserAreaMainView(LoginRequiredMixin, TitleMixin, TemplateView):
template_name = "users/base.html"
title = "User Area"
class CategoriesView(TitleMixin, ListView):
class CategoriesView(LoginRequiredMixin, TitleMixin, ListView):
model = Category
template_name = "users/categories.html"
title = "Categories"
@ -42,7 +44,7 @@ class CategoriesView(TitleMixin, ListView):
return categories
class CategoryView(TitleMixin, DetailView):
class CategoryView(LoginRequiredMixin, TitleMixin, DetailView):
template_name = "users/category.html"
title = "Category"
context_object_name = "category"
@ -56,17 +58,17 @@ class CategoryView(TitleMixin, DetailView):
raise Http404()
class MediaUploadView(TitleMixin, TemplateView):
class MediaUploadView(LoginRequiredMixin, TitleMixin, TemplateView):
template_name = "users/media_upload.html"
title = "Upload Media"
class CategoryCreateView(TitleMixin, TemplateView):
class CategoryCreateView(LoginRequiredMixin, TitleMixin, TemplateView):
template_name = "users/category_create.html"
title = "Create Category"
class FileUploadView(GenericAPIView):
class FileUploadView(LoginRequiredMixin, GenericAPIView):
parser_classes = (MultiPartParser, FormParser)
def get(self, request, *args, **kwargs):
@ -93,3 +95,10 @@ class FileUploadView(GenericAPIView):
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class Login(TitleMixin, LoginView):
title = "Login"
template_name = "users/login.html"
class Logout(LogoutView):
pass