Activa CSRF global en el portal (CsrfViewMiddleware)
- añade django.middleware.csrf.CsrfViewMiddleware a MIDDLEWARE (estaba ausente:
el CSRF estaba desactivado en todo el sitio)
- head.html: $.ajaxSetup envía X-CSRFToken en todo POST AJAX de jQuery + expone
window.CSRF_TOKEN (fuerza la cookie csrftoken en cada página)
- store_novawow.html: el fetch() nativo ahora manda X-CSRFToken (era el único que
se rompía al activar el middleware)
- register.html: añade {% csrf_token %} al formulario
- retira @csrf_exempt de login/register/restore_character/restore_items
(solo los webhooks stripe/sumup siguen exentos)
- auditoría previa: el resto de POST (AJAX jQuery + forms nativos con token) ya cubierto
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+5
-6
@@ -59,12 +59,11 @@ en `forum/permissions.py`:
|
|||||||
- Acceso a temas de foros ocultos/borrados bloqueado también por URL directa
|
- Acceso a temas de foros ocultos/borrados bloqueado también por URL directa
|
||||||
(no solo en los listados).
|
(no solo en los listados).
|
||||||
|
|
||||||
> ⚠️ **Recomendación para todo el sitio** (fuera del foro): `novawow/settings.py`
|
> ✅ **CSRF global activado** (todo el portal): `novawow/settings.py` ya incluye
|
||||||
> no incluye `django.middleware.csrf.CsrfViewMiddleware`, por lo que el resto de
|
> `django.middleware.csrf.CsrfViewMiddleware`. Las peticiones AJAX (jQuery) envían
|
||||||
> formularios POST del portal (app `home`) **no** validan CSRF. El foro está
|
> la cabecera `X-CSRFToken` automáticamente (`$.ajaxSetup` en `partials/head.html`),
|
||||||
> protegido con `@csrf_protect`, pero conviene activar el middleware global y
|
> los formularios nativos llevan `{% csrf_token %}`, y solo los webhooks externos
|
||||||
> revisar que las vistas POST de `home` lleven `{% csrf_token %}` (algunas ya lo
|
> (`stripe_webhook`, `sumup_webhook`) mantienen `@csrf_exempt`.
|
||||||
> llevan; las AJAX usan `@csrf_exempt`).
|
|
||||||
|
|
||||||
## Editor
|
## Editor
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,19 @@
|
|||||||
<link rel="mask-icon" href="{{ URL_PRINCIPAL }}/static/safari-pinned-tab.svg?v=4" color="#47210a" />
|
<link rel="mask-icon" href="{{ URL_PRINCIPAL }}/static/safari-pinned-tab.svg?v=4" color="#47210a" />
|
||||||
<link rel="shortcut icon" href="{{ URL_PRINCIPAL }}/static/favicon.ico" />
|
<link rel="shortcut icon" href="{{ URL_PRINCIPAL }}/static/favicon.ico" />
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
|
<!-- CSRF: expone el token y lo envía en cabecera en todas las peticiones AJAX (jQuery) no seguras -->
|
||||||
|
<script>window.CSRF_TOKEN = "{{ csrf_token }}";</script>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function (xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", window.CSRF_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js/power.js?lang=es"></script>
|
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js/power.js?lang=es"></script>
|
||||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js/nw-scripts.js"></script>
|
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js/nw-scripts.js"></script>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="body-box-content centered">
|
<div class="body-box-content centered">
|
||||||
<form action="" method="POST" id="nw-create-form" accept-charset="utf-8">
|
<form action="" method="POST" id="nw-create-form" accept-charset="utf-8">
|
||||||
|
{% csrf_token %}
|
||||||
<table class="middle-center-table">
|
<table class="middle-center-table">
|
||||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/register.js"></script>
|
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/register.js"></script>
|
||||||
<tbody><tr>
|
<tbody><tr>
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
// Crear la sesión de pago con Stripe
|
// Crear la sesión de pago con Stripe
|
||||||
fetch('/es/store-novawow/', {
|
fetch('/es/store-novawow/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': window.CSRF_TOKEN },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
character: personajeSeleccionado,
|
character: personajeSeleccionado,
|
||||||
carrito: carrito,
|
carrito: carrito,
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ def _set_game_account_session(request, game_account):
|
|||||||
request.session['account_id'] = game_account['id']
|
request.session['account_id'] = game_account['id']
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def login_view(request):
|
def login_view(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = LoginForm(request.POST)
|
form = LoginForm(request.POST)
|
||||||
@@ -334,7 +333,6 @@ def logout_view(request):
|
|||||||
return redirect('index')
|
return redirect('index')
|
||||||
|
|
||||||
# Vistas adicionales
|
# Vistas adicionales
|
||||||
@csrf_exempt
|
|
||||||
def register_view(request):
|
def register_view(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
password = request.POST.get('password', '').strip()
|
password = request.POST.get('password', '').strip()
|
||||||
@@ -1345,7 +1343,6 @@ def rename_cancel_view(request):
|
|||||||
return redirect('login')
|
return redirect('login')
|
||||||
return render(request, 'account/rename_cancel.html')
|
return render(request, 'account/rename_cancel.html')
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def restore_character_view(request):
|
def restore_character_view(request):
|
||||||
# Verifica si el usuario está autenticado y obtiene su nombre de usuario
|
# Verifica si el usuario está autenticado y obtiene su nombre de usuario
|
||||||
username = request.session.get('username')
|
username = request.session.get('username')
|
||||||
@@ -1428,7 +1425,6 @@ def restore_character_view(request):
|
|||||||
'character_list': character_list
|
'character_list': character_list
|
||||||
})
|
})
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def restore_items_view(request):
|
def restore_items_view(request):
|
||||||
username = request.session.get('username')
|
username = request.session.get('username')
|
||||||
if not username:
|
if not username:
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'home.middleware.MaintenanceMiddleware',
|
'home.middleware.MaintenanceMiddleware',
|
||||||
|
|||||||
Reference in New Issue
Block a user