Finish login and registration flow
- add template for login page - add logout URL - display user information
This commit is contained in:
parent
6b3fce5684
commit
564a00d46d
10 changed files with 328 additions and 56 deletions
|
@ -1,42 +1,110 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
from urllib.parse import unquote_to_bytes
|
from urllib.parse import unquote_to_bytes
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.x509 import load_pem_x509_certificate, SubjectAlternativeName
|
from cryptography.x509 import SubjectAlternativeName, load_pem_x509_certificate
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.backends import BaseBackend
|
from django.contrib.auth.backends import BaseBackend
|
||||||
|
|
||||||
|
from cats.models import UserCertificate
|
||||||
|
|
||||||
class ClientCertificateUser:
|
UserModel = get_user_model()
|
||||||
def __init__(self, serial_number, issuer_name, subject_name, emails):
|
|
||||||
self.serial_number = serial_number
|
|
||||||
self.issuer_name = issuer_name
|
|
||||||
self.subject_name = subject_name
|
|
||||||
self.emails = emails
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return (f"<ClientCertificateUser[serial_number={self.serial_number}, issuer_name={self.issuer_name},"
|
|
||||||
f" subject_name={self.subject_name}, emails={self.emails}]>")
|
|
||||||
|
|
||||||
|
|
||||||
class ClientCertificateBackend(BaseBackend):
|
@dataclass(frozen=True)
|
||||||
def authenticate(self, request, encoded_certificate=None):
|
class CertificateInformation:
|
||||||
"""
|
serial_number: str
|
||||||
encoded_certificate is expected to be a URL encoded PEM certificate as sent by nginx when using the
|
issuer_common_name: str
|
||||||
$ssl_client_escaped_cert
|
issuer_organization: str
|
||||||
"""
|
issuer_organizational_unit: str
|
||||||
|
subject_common_name: str
|
||||||
|
emails: list[str]
|
||||||
|
not_before: datetime
|
||||||
|
not_after: datetime
|
||||||
|
|
||||||
|
|
||||||
|
def get_certificate_information(encoded_certificate=None):
|
||||||
if encoded_certificate is None:
|
if encoded_certificate is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
pem_data = unquote_to_bytes(encoded_certificate)
|
pem_data = unquote_to_bytes(encoded_certificate)
|
||||||
certificate_data = load_pem_x509_certificate(pem_data)
|
certificate = load_pem_x509_certificate(pem_data)
|
||||||
|
|
||||||
subject_alternative_name = certificate_data.extensions.get_extension_for_class(SubjectAlternativeName)
|
serial_number = f"{certificate.serial_number:X}"
|
||||||
emails = subject_alternative_name.value.get_values_for_type(x509.RFC822Name)
|
|
||||||
|
|
||||||
serial_number = f"{certificate_data.serial_number:X}"
|
|
||||||
if len(serial_number) % 2 == 1:
|
if len(serial_number) % 2 == 1:
|
||||||
serial_number = "0" + serial_number
|
serial_number = "0" + serial_number
|
||||||
|
|
||||||
issuer_name = certificate_data.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value
|
return CertificateInformation(
|
||||||
subject_name = certificate_data.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value
|
serial_number=serial_number,
|
||||||
|
issuer_common_name=certificate.issuer.get_attributes_for_oid(
|
||||||
|
x509.OID_COMMON_NAME
|
||||||
|
)[0].value,
|
||||||
|
issuer_organization=certificate.issuer.get_attributes_for_oid(
|
||||||
|
x509.OID_ORGANIZATION_NAME
|
||||||
|
)[0].value,
|
||||||
|
issuer_organizational_unit=certificate.issuer.get_attributes_for_oid(
|
||||||
|
x509.OID_ORGANIZATIONAL_UNIT_NAME
|
||||||
|
)[0].value,
|
||||||
|
subject_common_name=certificate.subject.get_attributes_for_oid(
|
||||||
|
x509.OID_COMMON_NAME
|
||||||
|
)[0].value,
|
||||||
|
emails=certificate.extensions.get_extension_for_class(
|
||||||
|
SubjectAlternativeName
|
||||||
|
).value.get_values_for_type(x509.RFC822Name),
|
||||||
|
not_before=certificate.not_valid_before_utc,
|
||||||
|
not_after=certificate.not_valid_after_utc,
|
||||||
|
)
|
||||||
|
|
||||||
return ClientCertificateUser(serial_number, issuer_name, subject_name, emails)
|
|
||||||
|
def get_user_for_certificate(certificate):
|
||||||
|
user = None
|
||||||
|
for email in certificate.emails:
|
||||||
|
try:
|
||||||
|
user = UserModel.objects.get(email=UserModel.objects.normalize_email(email))
|
||||||
|
break
|
||||||
|
except UserModel.DoesNotExist:
|
||||||
|
continue
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class ClientCertificateBackend(BaseBackend):
|
||||||
|
def authenticate(self, request, certificate=None):
|
||||||
|
"""
|
||||||
|
encoded_certificate is expected to be a URL encoded PEM certificate as sent by nginx when using the
|
||||||
|
$ssl_client_escaped_cert
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = get_user_for_certificate(certificate)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
user = UserModel.objects.create_user(
|
||||||
|
certificate.emails[0],
|
||||||
|
email=certificate.emails[0],
|
||||||
|
is_staff=certificate.emails[0] in settings.CATS_ADMIN_EMAILS,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_certificate = UserCertificate.objects.get(
|
||||||
|
serial_number=certificate.serial_number,
|
||||||
|
issuer_name=certificate.issuer_common_name,
|
||||||
|
)
|
||||||
|
if not user_certificate.user:
|
||||||
|
user_certificate.user = user
|
||||||
|
user_certificate.save()
|
||||||
|
except UserCertificate.DoesNotExist:
|
||||||
|
UserCertificate.objects.create(
|
||||||
|
serial_number=certificate.serial_number,
|
||||||
|
issuer_name=certificate.issuer_common_name,
|
||||||
|
user=user,
|
||||||
|
common_name=certificate.subject_common_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
try:
|
||||||
|
return UserModel.objects.get(pk=user_id)
|
||||||
|
except UserModel.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
|
@ -15,7 +15,22 @@
|
||||||
</header>
|
</header>
|
||||||
<main>{% block content %}{% endblock content %}</main>
|
<main>{% block content %}{% endblock content %}</main>
|
||||||
<nav class="sidebar">
|
<nav class="sidebar">
|
||||||
{% block sidebar_content %}{% endblock sidebar_content %}
|
{% block sidebar_content %}
|
||||||
|
<form method="post" action="{% url "switch_language" %}?next={{ request.path }}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="choose_language">
|
||||||
|
{% translate "Choose language" %}
|
||||||
|
</label>
|
||||||
|
<select id="choose_language" name="choose_language">
|
||||||
|
{% get_available_languages as LANGUAGES %}
|
||||||
|
{% for lang in LANGUAGES %}
|
||||||
|
<option value="{{ lang.0 }}"{% if lang.0 == LANGUAGE_CODE %}
|
||||||
|
selected{% endif %}>{{ lang.1 }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button type="submit">{% translate "Change" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock sidebar_content %}
|
||||||
</nav>
|
</nav>
|
||||||
<footer></footer>
|
<footer></footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
{% extends "base.html" %}
|
{% extends "regular_view.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% blocktranslate trimmed %}
|
<h1>Welcome to the CAcert Assurer Training System - CATS.</h1>
|
||||||
Welcome to the CAcert Assurer Training System - CATS.
|
|
||||||
{% endblocktranslate %}
|
<p>{% blocktranslate trimmed %}
|
||||||
|
For background information about the purpose of this application read the
|
||||||
|
<a href="{{ challenge_wiki_url }}">CAcert Assurer Challenge Wiki page</a>.
|
||||||
|
{% endblocktranslate %}</p>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
34
cats/templates/login.html
Normal file
34
cats/templates/login.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
{% if certificate %}
|
||||||
|
{% if certificate_user %}
|
||||||
|
<h1>{% translate "Welcome back" %}</h1>
|
||||||
|
{% include "snippets/certificate_information.html" %}
|
||||||
|
<form method="post">
|
||||||
|
<p>
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit">{% translate "Continue" %}</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<h1>{% translate "Registration" %}</h1>
|
||||||
|
<p>{% blocktranslate trimmed %}
|
||||||
|
You are not registered. Please check your certificate information and confirm the registration.
|
||||||
|
{% endblocktranslate %}</p>
|
||||||
|
{% include "snippets/certificate_information.html" %}
|
||||||
|
<p>{% blocktranslate trimmed %}
|
||||||
|
Register with this certificate?
|
||||||
|
{% endblocktranslate %}</p>
|
||||||
|
<form method="post">
|
||||||
|
<p>
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-primary">{% translate "Register" %}</button>
|
||||||
|
<a href="{% url "home" %}" role="button" class="btn btn-primary">{% translate "Cancel" %}</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p>{% translate "You must present a client certificate to use CATS." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
11
cats/templates/regular_view.html
Normal file
11
cats/templates/regular_view.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block sidebar_content %}
|
||||||
|
{{ super }}
|
||||||
|
{% if request.user.is_anonymous %}
|
||||||
|
<a href="{% url 'login' %}?next={{ request.path }}">{% translate "Login" %}</a>
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans trimmed %}You are logged in as {{ user }}{% endblocktrans %}
|
||||||
|
<a href="{% url 'logout' %}?next={{ request.path }}">Logout</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
41
cats/templates/snippets/certificate_information.html
Normal file
41
cats/templates/snippets/certificate_information.html
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{% load i18n %}
|
||||||
|
<div class="certificate-information">
|
||||||
|
<h3>{% translate "Certificate information" %}</h3>
|
||||||
|
<h4>{% translate "Issued to:" %}</h4>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>{% translate "Common Name (CN):" %}</dt>
|
||||||
|
<dd>{{ certificate.subject_common_name }}</dd>
|
||||||
|
<dt>{% translate "Serial Number:" %}</dt>
|
||||||
|
<dd>{{ certificate.serial_number }}</dd>
|
||||||
|
<dt>
|
||||||
|
{% blocktranslate trimmed count counter=certificate.emails|length %}
|
||||||
|
Email address:
|
||||||
|
{% plural %}
|
||||||
|
Email addresses:
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</dt>
|
||||||
|
<dd>{{ certificate.emails|join:", " }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h4>{% translate "Issued By:" %}</h4>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>{% translate "Common Name (CN):" %}</dt>
|
||||||
|
<dd>{{ certificate.issuer_common_name }}</dd>
|
||||||
|
<dt>{% translate "Organization (O):" %}</dt>
|
||||||
|
<dd>{{ certificate.organization }}</dd>
|
||||||
|
<dt>{% translate "Organizational Unit (OU):" %}</dt>
|
||||||
|
<dd>{{ certificate.issuer_organizational_unit }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h4>{% translate "Validity:" %}</h4>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>{% translate "Not valid before:" %}</dt>
|
||||||
|
<dd>{{ certificate.not_before|date:"r" }}</dd>
|
||||||
|
<dt>{% translate "Not valid after:" %}</dt>
|
||||||
|
<dd>{{ certificate.not_after|date:"r" }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
</div>
|
|
@ -1,9 +1,12 @@
|
||||||
# URL configuration for cats
|
# URL configuration for cats
|
||||||
|
from django.contrib.auth.views import LogoutView
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from cats.views import certificate_login, home_page
|
from cats.views import CertificateLoginView, home_page, switch_language
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home_page, name="home"),
|
path("", home_page, name="home"),
|
||||||
path("auth/login", certificate_login, name="login"),
|
path("switch-language", switch_language, name="switch_language"),
|
||||||
|
path("auth/login", CertificateLoginView.as_view(), name="login"),
|
||||||
|
path("auth/logout", LogoutView.as_view(), name="logout"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,23 +1,98 @@
|
||||||
from django.contrib.auth import authenticate
|
from http import HTTPStatus
|
||||||
from django.http import HttpResponseForbidden, HttpResponseRedirect
|
|
||||||
from django.shortcuts import redirect, render
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.contrib.auth.views import RedirectURLMixin
|
||||||
|
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
|
||||||
|
from django.shortcuts import render, resolve_url
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import get_language_from_request
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from .authentication import get_certificate_information, get_user_for_certificate
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
def certificate_login(request):
|
class CertificateLoginView(RedirectURLMixin, TemplateView):
|
||||||
certificate = request.META.get('HTTP_X_SSL_CERT', None)
|
template_name = "login.html"
|
||||||
|
|
||||||
user = authenticate(request, encoded_certificate=certificate)
|
def get_default_redirect_url(self):
|
||||||
|
"""Return the default redirect URL."""
|
||||||
|
if self.next_page:
|
||||||
|
return resolve_url(self.next_page)
|
||||||
|
else:
|
||||||
|
return resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
certificate = self.request.META.get("HTTP_X_SSL_CERT", None)
|
||||||
|
|
||||||
|
certificate_information = get_certificate_information(
|
||||||
|
encoded_certificate=certificate
|
||||||
|
)
|
||||||
|
|
||||||
|
context.update(
|
||||||
|
{
|
||||||
|
"certificate": certificate_information,
|
||||||
|
"certificate_user": (
|
||||||
|
get_user_for_certificate(certificate_information)
|
||||||
|
if certificate
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
certificate = self.request.META.get("HTTP_X_SSL_CERT", None)
|
||||||
|
|
||||||
|
certificate_information = get_certificate_information(
|
||||||
|
encoded_certificate=certificate
|
||||||
|
)
|
||||||
|
|
||||||
|
user = authenticate(self.request, certificate=certificate_information)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
return HttpResponseForbidden(_("you have not sent a valid client certificate"))
|
return HttpResponseForbidden(
|
||||||
|
_("you have not sent a valid client certificate")
|
||||||
|
)
|
||||||
|
|
||||||
if "next" in request.GET:
|
login(self.request, user)
|
||||||
return HttpResponseRedirect(request.GET["next"])
|
|
||||||
|
|
||||||
return redirect("home")
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
|
def get_challenge_wiki_url(request):
|
||||||
|
language = get_language_from_request(request)
|
||||||
|
|
||||||
|
url_map = {
|
||||||
|
"cz": "https://wiki.cacert.org/AssurerChallenge/CZ",
|
||||||
|
"de": "https://wiki.cacert.org/AssurerChallenge/DE",
|
||||||
|
"fr": "https://wiki.cacert.org/AssurerChallenge/fr",
|
||||||
|
"nl": "https://wiki.cacert.org/AssurerChallenge/NL",
|
||||||
|
}
|
||||||
|
|
||||||
|
return url_map.get(language, "https://wiki.cacert.org/AssurerChallenge")
|
||||||
|
|
||||||
|
|
||||||
def home_page(request):
|
def home_page(request):
|
||||||
return render(request, "home.html")
|
return render(
|
||||||
|
request,
|
||||||
|
"home.html",
|
||||||
|
context={"challenge_wiki_url": get_challenge_wiki_url(request)},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def switch_language(request):
|
||||||
|
if request.method != "POST":
|
||||||
|
return HttpResponse(status=HTTPStatus.METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
|
language = request.POST.get("choose_language", get_language_from_request(request))
|
||||||
|
next_page = request.GET.get("next", reverse("home"))
|
||||||
|
|
||||||
|
response = HttpResponseRedirect(next_page)
|
||||||
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
|
@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from environs import Env
|
from environs import Env
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
@ -24,16 +26,19 @@ DEBUG = env.bool("DEBUG", default=False)
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
SECRET_KEY = env.str("SECRET_KEY",
|
SECRET_KEY = env.str(
|
||||||
default="django-insecure-m8s=a8rg)_cqo%q$g8qzzho&160+jei4uocg%q-ce3v_kfumr7")
|
"SECRET_KEY",
|
||||||
|
default="django-insecure-m8s=a8rg)_cqo%q$g8qzzho&160+jei4uocg%q-ce3v_kfumr7",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
SECRET_KEY = env.str("SECRET_KEY")
|
SECRET_KEY = env.str("SECRET_KEY")
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[])
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[])
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
@ -44,20 +49,26 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"debug_toolbar",
|
||||||
"cats",
|
"cats",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.auth.middleware.PersistentRemoteUserMiddleware",
|
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
|
||||||
|
|
||||||
|
INTERNAL_IPS = ["127.0.0.1", "::1"]
|
||||||
|
|
||||||
ROOT_URLCONF = "django_cats.urls"
|
ROOT_URLCONF = "django_cats.urls"
|
||||||
|
|
||||||
CATS_ADMIN_EMAILS = env.list("CATS_ADMIN_EMAILS", default=[])
|
CATS_ADMIN_EMAILS = env.list("CATS_ADMIN_EMAILS", default=[])
|
||||||
|
@ -80,30 +91,33 @@ TEMPLATES = [
|
||||||
|
|
||||||
WSGI_APPLICATION = "django_cats.wsgi.application"
|
WSGI_APPLICATION = "django_cats.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {"default": env.dj_db_url("DATABASE_URL")}
|
DATABASES = {"default": env.dj_db_url("DATABASE_URL")}
|
||||||
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"cats.authentication.ClientCertificateBackend",
|
"cats.authentication.ClientCertificateBackend",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
LANGUAGES = [
|
||||||
|
("cz", _("Czech")),
|
||||||
|
("de", _("German")),
|
||||||
|
("en", _("English")),
|
||||||
|
("fr", _("French")),
|
||||||
|
]
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
|
@ -113,3 +127,5 @@ STATIC_URL = "static/"
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
|
@ -15,10 +15,16 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("", include("cats.urls")),
|
path("", include("cats.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
|
urlpatterns += debug_toolbar_urls()
|
||||||
|
|
Loading…
Reference in a new issue