This commit is contained in:
Louis Mazin 2026-10-15 15:05:26 +02:00
parent 663268bbe9
commit 63496f1c2a
8 changed files with 17 additions and 62 deletions

View File

@ -1,7 +1,8 @@
"""Régressions des droits admin. Lancer avec : python manage.py test common."""
from django.conf import settings
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from django.urls import Resolver404, resolve, reverse
class AdminApiTests(TestCase):
@ -52,3 +53,13 @@ class AdminApiTests(TestCase):
response = self.client.post(reverse("auth-login"), data='{"username":"member", "password":"TestPassword2026!"}', content_type="application/json")
self.assertEqual(response.status_code, 403)
self.assertNotIn("_auth_user_id", self.client.session)
class NoDjangoAdminTests(TestCase):
def test_native_django_admin_is_absent(self):
self.assertNotIn("django.contrib.admin", settings.INSTALLED_APPS)
with self.assertRaises(Resolver404):
resolve("/django-admin/")
def test_custom_admin_api_still_exists(self):
self.assertEqual(reverse("admin-dashboard"), "/api/admin/dashboard/")

View File

@ -28,11 +28,10 @@ _csrf_backend = effective_origins(
auto_port=config("AUTO_PORT_CSRF", default=True, cast=bool),
)
# Ajoute également l'origine du frontend : nécessaire aux requêtes CSRF
# de l'interface (par exemple lorsque Django Admin est exposé via le proxy).
# du tableau de bord React utilisant les sessions Django.
CSRF_TRUSTED_ORIGINS = list(dict.fromkeys([*_csrf_backend, *CORS_ALLOWED_ORIGINS]))
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",

View File

@ -1,13 +1,7 @@
from django.contrib import admin
from django.urls import include, path
admin.site.site_header = "Administration avancée"
admin.site.site_title = "Administration Django"
admin.site.index_title = "Gestion technique"
# L'administration est une interface React (/admin/) servie par le frontend.
# Le backend n'expose que l'API, y compris les routes administrateur sécurisées.
urlpatterns = [
# /admin/ est le tableau de bord React basé sur shadcn/ui.
# Django Admin reste accessible pour les fonctions avancées.
path("django-admin/", admin.site.urls),
path("api/", include("common.urls")),
]

View File

@ -12,13 +12,6 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
location /django-admin/ {
proxy_pass http://backend:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
proxy_pass http://backend:8000;
proxy_set_header Host $http_host;

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from "react"
import { Activity, ArrowUpRight, Database, LayoutDashboard, LogOut, RefreshCw, Search, ShieldCheck, Users } from "lucide-react"
import { Activity, LayoutDashboard, LogOut, RefreshCw, Search, ShieldCheck, Users } from "lucide-react"
import { AppShell } from "@/components/layout/AppShell"
import { LoginForm } from "@/components/admin/LoginForm"
import { CreateUserDialog } from "@/components/admin/CreateUserDialog"
@ -118,7 +118,6 @@ export default function AdminPage() {
<div className="border-t border-border bg-muted/20 px-6 py-3 text-xs text-muted-foreground">{filtered.length} compte(s) affiché(s) · Les comptes sensibles sont protégés.</div>
</CardContent>
</Card>
<Card className="border-dashed shadow-none"><CardContent className="flex flex-wrap items-center justify-between gap-4 py-5"><div className="flex items-center gap-3"><Database className="size-5 text-primary"/><div><p className="text-sm font-medium">Administration Django avancée</p><p className="text-xs text-muted-foreground">Groupes, permissions et modèles Django</p></div></div><Button size="sm" variant="outline" asChild><a href="/django-admin/">Ouvrir <ArrowUpRight className="size-4"/></a></Button></CardContent></Card>
</main>
</div>}
</AppShell>

View File

@ -17,7 +17,6 @@ export default function HomePage() {
<p className="mt-5 max-w-xl text-base leading-relaxed text-muted-foreground">Docker Compose, Django REST Framework, React avec shadcn/ui, MySQL 8 et Oxlint. Une architecture modulaire, prête à être personnalisée.</p>
<div className="mt-8 flex flex-wrap gap-3">
<Button asChild><a href="/admin/">Ouvrir l’administration <ArrowRight className="size-4"/></a></Button>
<Button variant="outline" asChild><a href="/django-admin/">Django avancé <ArrowRight className="size-4"/></a></Button>
</div>
</div>
</section>

View File

@ -9,7 +9,7 @@ export default defineConfig(({ mode }) => {
const backendPort = process.env.BACKEND_PORT || env.BACKEND_PORT || "1029"
const proxyTarget = `http://127.0.0.1:${backendPort}`
const proxy = Object.fromEntries(
["/api", "/django-admin", "/static"].map((path) => [
["/api", "/static"].map((path) => [
path,
{ target: proxyTarget, changeOrigin: true },
]),

View File

@ -1,40 +0,0 @@
"""Complète les secrets manquants du .env, sans écraser les valeurs existantes."""
from pathlib import Path
import secrets
import stat
ROOT = Path(__file__).resolve().parents[1]
ENV = ROOT / ".env"
SOURCE = ROOT / ".env.example"
if not ENV.exists():
ENV.write_text(SOURCE.read_text(encoding="utf-8"), encoding="utf-8")
values = ENV.read_text(encoding="utf-8").splitlines(keepends=True)
secrets_to_generate = {
"SECRET_KEY": lambda: secrets.token_urlsafe(64),
"MYSQL_PASSWORD": lambda: secrets.token_urlsafe(24),
"MYSQL_ROOT_PASSWORD": lambda: secrets.token_urlsafe(28),
"ADMIN_PASSWORD": lambda: secrets.token_urlsafe(16),
}
updated = set()
result = []
for line in values:
if line.lstrip().startswith("#") or "=" not in line:
result.append(line)
continue
key, val = line.rstrip("\n").split("=", 1)
if key in secrets_to_generate and not val.strip():
line = f"{key}={secrets_to_generate[key]()}\n"
updated.add(key)
result.append(line)
ENV.write_text("".join(result), encoding="utf-8")
if hasattr(ENV, "chmod"):
ENV.chmod(stat.S_IRUSR | stat.S_IWUSR)
print("Fichier .env prêt.")
if updated:
print("Secrets générés :", ", ".join(sorted(updated)))
print("Le mot de passe admin est dans .env : conserve-le de manière sûre.")
else:
print("Aucune valeur existante modifiée.")