Compare commits
1
Commits
docker
...
security-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22102dbb7a |
+8
-4
@@ -5,8 +5,12 @@
|
||||
# Сгенерировать: python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
|
||||
DJANGO_SECRET_KEY=
|
||||
|
||||
# Включить режим отладки (ТОЛЬКО для разработки!): 1 = да, пусто/0 = нет
|
||||
DJANGO_DEBUG=1
|
||||
# Режим отладки: для продакшена строго 0 (пусто). 1 — только локальная разработка!
|
||||
DJANGO_DEBUG=0
|
||||
|
||||
# Разрешённые хосты через запятую (для продакшена: ваш домен)
|
||||
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
|
||||
# Разрешённые хосты через запятую (для продакшена: ваш домен, например teacher.labnethub.ru)
|
||||
DJANGO_ALLOWED_HOSTS=teacher.labnethub.ru,127.0.0.1,localhost
|
||||
|
||||
# Secure-куки и HSTS (сайт за HTTPS-прокси): держать включёнными (=1)
|
||||
DJANGO_COOKIE_SECURE=1
|
||||
DJANGO_HSTS_SECONDS=31536000
|
||||
|
||||
@@ -50,10 +50,37 @@ MIDDLEWARE = [
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
# --- HTTPS / безопасность кук (сайт работает только через HTTPS-прокси) ---
|
||||
# nginx должен передавать заголовок X-Forwarded-Proto: https
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
SESSION_COOKIE_SECURE = os.environ.get("DJANGO_COOKIE_SECURE", "1") == "1"
|
||||
CSRF_COOKIE_SECURE = os.environ.get("DJANGO_COOKIE_SECURE", "1") == "1"
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = "Lax"
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
|
||||
X_FRAME_OPTIONS = "DENY"
|
||||
SECURE_HSTS_SECONDS = int(os.environ.get("DJANGO_HSTS_SECONDS", "31536000"))
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
# Редирект на HTTPS не включаем — его делает nginx (иначе цикл при неправильном X-Forwarded-Proto)
|
||||
# SECURE_SSL_REDIRECT = True
|
||||
|
||||
# --- django-axes: защита от брутфорса логина ---
|
||||
AXES_FAILURE_LIMIT = 5
|
||||
AXES_COOLOFF_TIME = 1 # часы
|
||||
AXES_RESET_ON_SUCCESS = True
|
||||
AXES_LOCKOUT_PARAMETERS = ["ip_address"]
|
||||
|
||||
# --- антиспам: лимит заявок с одного IP ---
|
||||
# Кэш in-memory (locmem) подходит для одного воркера; для нескольких воркеров
|
||||
# нужно memcached/redis, иначе лимит считается на каждый процесс отдельно.
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"LOCATION": "teacher-site-cache",
|
||||
}
|
||||
}
|
||||
|
||||
ROOT_URLCONF = "config.urls"
|
||||
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
from django.contrib import messages
|
||||
from django.core.cache import cache
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from .forms import ConsultationRequestForm
|
||||
from .models import ConsultationSlot
|
||||
|
||||
|
||||
# Антиспам: не более 3 заявок с одного IP за 10 минут
|
||||
SUBMIT_LIMIT = 3
|
||||
SUBMIT_WINDOW_SECONDS = 600
|
||||
|
||||
|
||||
def _client_ip(request):
|
||||
# X-Forwarded-For добавляет nginx; берём первый адрес (реальный клиент)
|
||||
xff = request.META.get("HTTP_X_FORWARDED_FOR")
|
||||
if xff:
|
||||
return xff.split(",")[0].strip()
|
||||
return request.META.get("REMOTE_ADDR", "unknown")
|
||||
|
||||
|
||||
def _submit_allowed(request):
|
||||
key = f"submit:{_client_ip(request)}"
|
||||
now = timezone.now().timestamp()
|
||||
stamps = cache.get(key, [])
|
||||
stamps = [s for s in stamps if now - s < SUBMIT_WINDOW_SECONDS]
|
||||
if len(stamps) >= SUBMIT_LIMIT:
|
||||
cache.set(key, stamps, SUBMIT_WINDOW_SECONDS)
|
||||
return False
|
||||
stamps.append(now)
|
||||
cache.set(key, stamps, SUBMIT_WINDOW_SECONDS)
|
||||
return True
|
||||
|
||||
|
||||
def home(request):
|
||||
if request.method == "POST":
|
||||
if not _submit_allowed(request):
|
||||
messages.error(
|
||||
request,
|
||||
"Слишком много заявок за короткое время. Попробуйте через 10 минут.",
|
||||
)
|
||||
return redirect(f"{reverse('consultations:home')}#booking")
|
||||
form = ConsultationRequestForm(request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# This is a sample Python script.
|
||||
|
||||
# Press Shift+F10 to execute it or replace it with your code.
|
||||
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
|
||||
|
||||
|
||||
def print_hi(name):
|
||||
# Use a breakpoint in the code line below to debug your script.
|
||||
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
|
||||
|
||||
|
||||
# Press the green button in the gutter to run the script.
|
||||
if __name__ == '__main__':
|
||||
print_hi('PyCharm')
|
||||
|
||||
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
|
||||
Reference in New Issue
Block a user