feat: centralized IP restriction for registration via middleware
- RegistrationIPRestrictionMiddleware added - Removed view decorators - All logic now in middleware - Updated docs and settings - IP utils in common/ip_restrictions.py
This commit is contained in:
parent
c364d0635a
commit
98dabd6c53
290
IP_REGISTRATION_RESTRICTIONS.md
Normal file
290
IP_REGISTRATION_RESTRICTIONS.md
Normal file
@ -0,0 +1,290 @@
|
||||
# Ограничение доступа к регистрации по IP
|
||||
|
||||
## Описание
|
||||
|
||||
Реализована система ограничения доступа к регистрации новых пользователей по IP-адресу через middleware. Это позволяет разрешить регистрацию только из локальной сети или конкретных IP-адресов.
|
||||
|
||||
## Файлы
|
||||
|
||||
### 1. `backend/common/ip_restrictions.py`
|
||||
|
||||
Утилиты для проверки IP-адресов:
|
||||
|
||||
- **`get_client_ip(request)`** - получает реальный IP клиента (учитывает прокси)
|
||||
- **`is_local_ip(ip_address)`** - проверяет, является ли IP локальным
|
||||
- **`is_ip_allowed(ip_address, allowed_networks)`** - проверяет доступ для IP
|
||||
|
||||
### 2. `backend/eusrr_backend/middleware.py`
|
||||
|
||||
Добавлен класс `RegistrationIPRestrictionMiddleware` для проверки IP при регистрации.
|
||||
|
||||
### 3. Настройка в `backend/eusrr_backend/settings.py`
|
||||
|
||||
В `MIDDLEWARE` добавлена строка:
|
||||
```python
|
||||
"eusrr_backend.middleware.RegistrationIPRestrictionMiddleware",
|
||||
```
|
||||
|
||||
Добавлена настройка `REGISTRATION_ALLOWED_IPS`:
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = None # По умолчанию только локальные IP
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
### По умолчанию (только локальные IP)
|
||||
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = None
|
||||
```
|
||||
|
||||
Разрешены только:
|
||||
- `127.0.0.0/8` (localhost)
|
||||
- `10.0.0.0/8` (частная сеть класса A)
|
||||
- `172.16.0.0/12` (частная сеть класса B)
|
||||
- `192.168.0.0/16` (частная сеть класса C)
|
||||
- `::1` (IPv6 localhost)
|
||||
- `fe80::/10` (IPv6 link-local)
|
||||
|
||||
### Разрешить все IP
|
||||
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = ['*']
|
||||
```
|
||||
|
||||
### Разрешить конкретные сети (CIDR)
|
||||
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = [
|
||||
'192.168.1.0/24', # Локальная сеть офиса
|
||||
'10.0.0.0/8', # Корпоративная сеть
|
||||
'172.16.100.0/24', # VPN сеть
|
||||
]
|
||||
```
|
||||
|
||||
### Разрешить конкретные IP-адреса
|
||||
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = [
|
||||
'192.168.1.100',
|
||||
'192.168.1.101',
|
||||
'203.0.113.42',
|
||||
]
|
||||
```
|
||||
|
||||
### Комбинированный подход
|
||||
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = [
|
||||
'192.168.1.0/24', # Вся локальная сеть
|
||||
'203.0.113.42', # Конкретный внешний IP
|
||||
'198.51.100.0/28', # Диапазон внешних IP
|
||||
]
|
||||
```
|
||||
|
||||
## Поведение при блокировке
|
||||
|
||||
### Для обычных view (HTML)
|
||||
|
||||
При попытке доступа с неразрешенного IP пользователь увидит:
|
||||
|
||||
```html
|
||||
<h1>403 Forbidden</h1>
|
||||
<p>Регистрация доступна только из локальной сети.</p>
|
||||
<p>Ваш IP: 203.0.113.50</p>
|
||||
```
|
||||
|
||||
### Для API endpoints (JSON)
|
||||
|
||||
При попытке доступа с неразрешенного IP API вернет:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Регистрация доступна только из локальной сети.",
|
||||
"client_ip": "203.0.113.50"
|
||||
}
|
||||
```
|
||||
|
||||
HTTP статус: `403 Forbidden`
|
||||
|
||||
## Работа с прокси
|
||||
|
||||
Функция `get_client_ip()` корректно определяет реальный IP клиента даже при использовании прокси-серверов:
|
||||
|
||||
1. Проверяет заголовок `X-Forwarded-For`
|
||||
2. Берет первый IP из списка (клиентский IP)
|
||||
3. Если заголовка нет, использует `REMOTE_ADDR`
|
||||
|
||||
### Настройка для работы за Nginx
|
||||
|
||||
Добавьте в конфигурацию Nginx:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
```
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Проверка локального доступа
|
||||
|
||||
```bash
|
||||
curl http://localhost:9000/api/v1/auth/register/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com","password":"test123",...}'
|
||||
```
|
||||
|
||||
Должно работать с `127.0.0.1`.
|
||||
|
||||
### Проверка блокировки (эмуляция внешнего IP)
|
||||
|
||||
```bash
|
||||
curl http://localhost:9000/api/v1/auth/register/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Forwarded-For: 203.0.113.50" \
|
||||
-d '{"email":"test@example.com","password":"test123",...}'
|
||||
```
|
||||
|
||||
Должно вернуть `403 Forbidden` если `REGISTRATION_ALLOWED_IPS` не содержит этот IP.
|
||||
|
||||
### Тестирование в коде
|
||||
|
||||
```python
|
||||
from common.ip_restrictions import is_local_ip, is_ip_allowed
|
||||
|
||||
# Проверка локальных IP
|
||||
assert is_local_ip('127.0.0.1') == True
|
||||
assert is_local_ip('192.168.1.100') == True
|
||||
assert is_local_ip('10.0.0.5') == True
|
||||
assert is_local_ip('203.0.113.50') == False
|
||||
|
||||
# Проверка с настройками
|
||||
from django.conf import settings
|
||||
settings.REGISTRATION_ALLOWED_IPS = ['192.168.1.0/24']
|
||||
|
||||
assert is_ip_allowed('192.168.1.50') == True
|
||||
assert is_ip_allowed('192.168.2.50') == False
|
||||
```
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Рекомендации
|
||||
|
||||
1. **Для production**: используйте конкретные сети, не `['*']`
|
||||
2. **За прокси**: убедитесь, что прокси правильно передает заголовки
|
||||
3. **IPv6**: модуль поддерживает IPv6, учтите это в конфигурации
|
||||
4. **Логирование**: при необходимости добавьте логирование заблокированных попыток
|
||||
|
||||
### Обход защиты
|
||||
|
||||
Потенциальные проблемы:
|
||||
|
||||
1. **Подмена заголовков**: если прокси настроен неправильно, злоумышленник может подменить `X-Forwarded-For`
|
||||
2. **VPN/Proxy клиента**: пользователь может использовать VPN с локальным IP
|
||||
|
||||
### Дополнительная защита
|
||||
|
||||
Рекомендуется комбинировать с:
|
||||
- Rate limiting (throttling в DRF)
|
||||
- CAPTCHA для регистрации
|
||||
- Email верификация (уже реализована)
|
||||
- Логирование попыток регистрации
|
||||
|
||||
## Примеры использования в других view
|
||||
|
||||
Если нужно ограничить доступ к другим URL по IP, можно создать дополнительные middleware или расширить существующий:
|
||||
|
||||
```python
|
||||
class CustomIPRestrictionMiddleware:
|
||||
"""Ограничение по IP для произвольных URL"""
|
||||
|
||||
RESTRICTED_PATHS = (
|
||||
"/admin/", # Например, админка только из локальной сети
|
||||
"/api/v1/sensitive/", # Чувствительные API
|
||||
)
|
||||
|
||||
def __call__(self, request):
|
||||
from common.ip_restrictions import get_client_ip, is_ip_allowed
|
||||
|
||||
path = request.path_info
|
||||
|
||||
if any(path.startswith(p) for p in self.RESTRICTED_PATHS):
|
||||
client_ip = get_client_ip(request)
|
||||
if not is_ip_allowed(client_ip):
|
||||
# Блокируем доступ
|
||||
...
|
||||
|
||||
return self.get_response(request)
|
||||
```
|
||||
|
||||
## Отключение ограничений
|
||||
|
||||
### Временное отключение (для разработки)
|
||||
|
||||
В `settings.py`:
|
||||
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = ['*'] # Разрешить все IP
|
||||
```
|
||||
|
||||
### Полное отключение
|
||||
|
||||
В `settings.py` закомментируйте middleware:
|
||||
|
||||
```python
|
||||
MIDDLEWARE = [
|
||||
# ... другие middleware ...
|
||||
# "eusrr_backend.middleware.RegistrationIPRestrictionMiddleware", # Отключено
|
||||
"eusrr_backend.middleware.CacheControlMiddleware",
|
||||
]
|
||||
```
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Добавление логирования
|
||||
|
||||
Можно расширить `backend/eusrr_backend/middleware.py`:
|
||||
|
||||
```python
|
||||
def __call__(self, request):
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
path = request.path_info
|
||||
|
||||
if not any(path.startswith(p) for p in self.RESTRICTED_PATHS):
|
||||
return self.get_response(request)
|
||||
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
if not is_ip_allowed(client_ip):
|
||||
logger.warning(
|
||||
f'Registration blocked for IP {client_ip} '
|
||||
f'(path: {path}, user-agent: {request.META.get("HTTP_USER_AGENT")})'
|
||||
)
|
||||
# ... блокировка ...
|
||||
|
||||
logger.info(f'Registration allowed for IP {client_ip}')
|
||||
return self.get_response(request)
|
||||
```
|
||||
|
||||
### Метрики
|
||||
|
||||
Можно отслеживать:
|
||||
- Количество заблокированных попыток регистрации
|
||||
- IP-адреса, которые часто блокируются
|
||||
- Географию заблокированных запросов
|
||||
|
||||
## Вопросы и поддержка
|
||||
|
||||
При возникновении проблем проверьте:
|
||||
|
||||
1. Правильность настройки `REGISTRATION_ALLOWED_IPS` в settings.py
|
||||
2. Корректность заголовков прокси (если используется)
|
||||
3. Middleware включен в `MIDDLEWARE` в settings.py
|
||||
4. Формат IP-адресов в конфигурации (должны быть валидными CIDR или IP)
|
||||
5. Логи Django на наличие ошибок в модуле `eusrr_backend.middleware`
|
||||
96
IP_RESTRICTIONS_README.md
Normal file
96
IP_RESTRICTIONS_README.md
Normal file
@ -0,0 +1,96 @@
|
||||
# 🔒 IP-ограничения для регистрации - Краткая инструкция
|
||||
|
||||
## ✅ Что сделано
|
||||
|
||||
Добавлен **middleware** для ограничения доступа к регистрации по IP-адресу.
|
||||
|
||||
## 📁 Изменённые файлы
|
||||
|
||||
1. **`backend/eusrr_backend/middleware.py`** - добавлен `RegistrationIPRestrictionMiddleware`
|
||||
2. **`backend/eusrr_backend/settings.py`** - middleware включен в `MIDDLEWARE`
|
||||
3. **`backend/common/ip_restrictions.py`** - утилиты для проверки IP
|
||||
|
||||
## ⚙️ Настройка
|
||||
|
||||
В `backend/eusrr_backend/settings.py` найдите:
|
||||
|
||||
```python
|
||||
# IP ОГРАНИЧЕНИЯ ДЛЯ РЕГИСТРАЦИИ
|
||||
REGISTRATION_ALLOWED_IPS = None # По умолчанию только локальные IP
|
||||
```
|
||||
|
||||
### Варианты настройки:
|
||||
|
||||
```python
|
||||
# 1. Только локальные IP (по умолчанию)
|
||||
REGISTRATION_ALLOWED_IPS = None
|
||||
|
||||
# 2. Разрешить все IP (для разработки)
|
||||
REGISTRATION_ALLOWED_IPS = ['*']
|
||||
|
||||
# 3. Разрешить конкретные сети
|
||||
REGISTRATION_ALLOWED_IPS = [
|
||||
'192.168.1.0/24', # Локальная сеть офиса
|
||||
'10.0.0.0/8', # Корпоративная сеть
|
||||
]
|
||||
|
||||
# 4. Разрешить конкретные IP
|
||||
REGISTRATION_ALLOWED_IPS = ['192.168.1.100', '203.0.113.42']
|
||||
```
|
||||
|
||||
## 🎯 Как работает
|
||||
|
||||
**По умолчанию (None):**
|
||||
- ✅ Разрешены: `127.0.0.1`, `192.168.x.x`, `10.x.x.x`, `172.16-31.x.x`
|
||||
- ❌ Заблокированы: все внешние IP
|
||||
|
||||
**Блокируемые URL:**
|
||||
- `/auth/register/` (веб-форма)
|
||||
- `/api/v1/auth/register/` (API)
|
||||
|
||||
**Ответ при блокировке:**
|
||||
- API: `{"detail": "Регистрация доступна только из локальной сети.", "client_ip": "..."}`
|
||||
- Веб: Красивая страница 403 с информацией об IP
|
||||
|
||||
## 🔧 Отключение
|
||||
|
||||
### Временно (для разработки):
|
||||
```python
|
||||
REGISTRATION_ALLOWED_IPS = ['*']
|
||||
```
|
||||
|
||||
### Полностью:
|
||||
В `settings.py` закомментируйте строку в `MIDDLEWARE`:
|
||||
```python
|
||||
# "eusrr_backend.middleware.RegistrationIPRestrictionMiddleware",
|
||||
```
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
```bash
|
||||
# Из локальной сети - работает
|
||||
curl http://localhost:9000/api/v1/auth/register/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"test@example.com",...}'
|
||||
|
||||
# С внешнего IP - блокируется
|
||||
curl http://localhost:9000/api/v1/auth/register/ \
|
||||
-H "X-Forwarded-For: 203.0.113.50" \
|
||||
-d '{"email":"test@example.com",...}'
|
||||
```
|
||||
|
||||
## 🌐 Работа за прокси
|
||||
|
||||
Middleware автоматически определяет реальный IP через заголовки `X-Forwarded-For` и `X-Real-IP`.
|
||||
|
||||
Настройте Nginx:
|
||||
```nginx
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Полная документация
|
||||
|
||||
См. `IP_REGISTRATION_RESTRICTIONS.md`
|
||||
152
backend/common/ip_restrictions.py
Normal file
152
backend/common/ip_restrictions.py
Normal file
@ -0,0 +1,152 @@
|
||||
"""
|
||||
Ограничение доступа к определенным функциям по IP-адресу.
|
||||
"""
|
||||
import ipaddress
|
||||
from functools import wraps
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
def get_client_ip(request) -> str:
|
||||
"""
|
||||
Получает реальный IP-адрес клиента из request.
|
||||
Учитывает заголовки прокси (X-Forwarded-For, X-Real-IP).
|
||||
"""
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if x_forwarded_for:
|
||||
# Берем первый IP из списка (клиентский IP)
|
||||
ip = x_forwarded_for.split(',')[0].strip()
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR', '')
|
||||
return ip
|
||||
|
||||
|
||||
def is_local_ip(ip_address: str) -> bool:
|
||||
"""
|
||||
Проверяет, является ли IP-адрес локальным.
|
||||
|
||||
Локальными считаются:
|
||||
- 127.0.0.0/8 (localhost)
|
||||
- 10.0.0.0/8 (частная сеть класса A)
|
||||
- 172.16.0.0/12 (частная сеть класса B)
|
||||
- 192.168.0.0/16 (частная сеть класса C)
|
||||
- ::1 (IPv6 localhost)
|
||||
- fe80::/10 (IPv6 link-local)
|
||||
"""
|
||||
try:
|
||||
ip = ipaddress.ip_address(ip_address)
|
||||
return ip.is_private or ip.is_loopback or ip.is_link_local
|
||||
except ValueError:
|
||||
# Невалидный IP - считаем небезопасным
|
||||
return False
|
||||
|
||||
|
||||
def is_ip_allowed(ip_address: str, allowed_networks: List[str] = None) -> bool:
|
||||
"""
|
||||
Проверяет, разрешен ли доступ с данного IP-адреса.
|
||||
|
||||
Args:
|
||||
ip_address: IP-адрес для проверки
|
||||
allowed_networks: Список разрешенных сетей в формате CIDR
|
||||
или отдельных IP. Если None, используется
|
||||
REGISTRATION_ALLOWED_IPS из settings.
|
||||
|
||||
Returns:
|
||||
True если IP разрешен, False иначе
|
||||
"""
|
||||
if allowed_networks is None:
|
||||
# Проверяем настройки Django
|
||||
allowed_networks = getattr(settings, 'REGISTRATION_ALLOWED_IPS', None)
|
||||
|
||||
# Если настройка не задана, разрешаем только локальные IP
|
||||
if allowed_networks is None:
|
||||
return is_local_ip(ip_address)
|
||||
|
||||
# Пустой список = запретить всем
|
||||
if not allowed_networks:
|
||||
return False
|
||||
|
||||
# Специальное значение для разрешения всех IP
|
||||
if allowed_networks == ['*'] or '*' in allowed_networks:
|
||||
return True
|
||||
|
||||
try:
|
||||
ip = ipaddress.ip_address(ip_address)
|
||||
|
||||
for network_str in allowed_networks:
|
||||
# Проверяем, является ли это сетью (содержит /) или отдельным IP
|
||||
if '/' in network_str:
|
||||
network = ipaddress.ip_network(network_str, strict=False)
|
||||
if ip in network:
|
||||
return True
|
||||
else:
|
||||
# Отдельный IP-адрес
|
||||
allowed_ip = ipaddress.ip_address(network_str)
|
||||
if ip == allowed_ip:
|
||||
return True
|
||||
|
||||
return False
|
||||
except ValueError:
|
||||
# Невалидный IP - запрещаем
|
||||
return False
|
||||
|
||||
|
||||
def local_ip_required(view_func):
|
||||
"""
|
||||
Декоратор для ограничения доступа к view только с локальных IP.
|
||||
|
||||
Использование:
|
||||
@local_ip_required
|
||||
def my_view(request):
|
||||
...
|
||||
"""
|
||||
@wraps(view_func)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
if not is_ip_allowed(client_ip):
|
||||
return HttpResponse(
|
||||
'<h1>403 Forbidden</h1>'
|
||||
'<p>Регистрация доступна только из локальной сети.</p>'
|
||||
f'<p>Ваш IP: {client_ip}</p>',
|
||||
status=403,
|
||||
content_type='text/html; charset=utf-8'
|
||||
)
|
||||
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def local_ip_required_api(view_class):
|
||||
"""
|
||||
Декоратор для ограничения доступа к API View только с локальных IP.
|
||||
|
||||
Использование:
|
||||
@local_ip_required_api
|
||||
class MyAPIView(APIView):
|
||||
...
|
||||
"""
|
||||
original_dispatch = view_class.dispatch
|
||||
|
||||
def dispatch_wrapper(self, request, *args, **kwargs):
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status as drf_status
|
||||
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
if not is_ip_allowed(client_ip):
|
||||
return Response(
|
||||
{
|
||||
'detail': 'Регистрация доступна только из локальной сети.',
|
||||
'client_ip': client_ip
|
||||
},
|
||||
status=drf_status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
return original_dispatch(self, request, *args, **kwargs)
|
||||
|
||||
view_class.dispatch = dispatch_wrapper
|
||||
return view_class
|
||||
@ -140,6 +140,143 @@ class EmailVerificationMiddleware:
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
class RegistrationIPRestrictionMiddleware:
|
||||
"""
|
||||
Проверяет IP-адрес для запросов к регистрации.
|
||||
|
||||
Применяется только к URL-ам регистрации:
|
||||
- /auth/register/
|
||||
- /api/v1/auth/register/
|
||||
|
||||
Все остальные URL пропускаются без проверки.
|
||||
"""
|
||||
|
||||
RESTRICTED_PATHS = (
|
||||
"/auth/register/",
|
||||
"/api/v1/auth/register/",
|
||||
)
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
from common.ip_restrictions import get_client_ip, is_ip_allowed
|
||||
|
||||
path = request.path_info
|
||||
|
||||
# Проверяем только запросы к регистрации
|
||||
if not any(path.startswith(p) for p in self.RESTRICTED_PATHS):
|
||||
return self.get_response(request)
|
||||
|
||||
# Проверяем IP-адрес
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
if not is_ip_allowed(client_ip):
|
||||
# IP не разрешен - блокируем
|
||||
|
||||
# Для API возвращаем JSON
|
||||
if path.startswith("/api/"):
|
||||
from django.http import JsonResponse
|
||||
return JsonResponse(
|
||||
{
|
||||
"detail": "Регистрация доступна только из "
|
||||
"локальной сети.",
|
||||
"client_ip": client_ip
|
||||
},
|
||||
status=403
|
||||
)
|
||||
|
||||
# Для веб-страниц возвращаем HTML
|
||||
from django.http import HttpResponse
|
||||
return HttpResponse(
|
||||
f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,
|
||||
initial-scale=1.0">
|
||||
<title>403 Forbidden</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg,
|
||||
#667eea 0%, #764ba2 100%);
|
||||
}}
|
||||
.container {{
|
||||
background: white;
|
||||
padding: 3rem;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
}}
|
||||
h1 {{
|
||||
color: #e53e3e;
|
||||
font-size: 3rem;
|
||||
margin: 0 0 1rem 0;
|
||||
}}
|
||||
h2 {{
|
||||
color: #2d3748;
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 1.5rem 0;
|
||||
}}
|
||||
p {{
|
||||
color: #4a5568;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 1rem 0;
|
||||
}}
|
||||
.ip-info {{
|
||||
background: #f7fafc;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 0.5rem;
|
||||
font-family: monospace;
|
||||
}}
|
||||
.back-link {{
|
||||
display: inline-block;
|
||||
margin-top: 1.5rem;
|
||||
padding: 0.75rem 2rem;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 0.5rem;
|
||||
}}
|
||||
.back-link:hover {{
|
||||
background: #5a67d8;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔒 403</h1>
|
||||
<h2>Доступ запрещен</h2>
|
||||
<p>Регистрация доступна только из локальной сети.</p>
|
||||
<p>Если вы в офисе или подключены через VPN,
|
||||
обратитесь к администратору.</p>
|
||||
<div class="ip-info">
|
||||
Ваш IP: <strong>{client_ip}</strong>
|
||||
</div>
|
||||
<a href="/" class="back-link">← На главную</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
status=403,
|
||||
content_type="text/html; charset=utf-8"
|
||||
)
|
||||
|
||||
# IP разрешен - продолжаем обработку
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
class CacheControlMiddleware:
|
||||
"""
|
||||
Добавляет правильные заголовки Cache-Control для страниц.
|
||||
|
||||
@ -62,6 +62,7 @@ MIDDLEWARE = [
|
||||
"api.middleware.JWTRefreshMiddleware", # Автообновление JWT токенов
|
||||
"eusrr_backend.middleware.AuthRequiredMiddleware",
|
||||
"eusrr_backend.middleware.EmailVerificationMiddleware",
|
||||
"eusrr_backend.middleware.RegistrationIPRestrictionMiddleware", # IP ограничение для регистрации
|
||||
"eusrr_backend.middleware.CacheControlMiddleware", # Cache-Control headers
|
||||
]
|
||||
|
||||
@ -157,6 +158,17 @@ PASSWORD_RESET_TIMEOUT = 60 * 60 * 24
|
||||
PHONE_DEFAULT_REGION = os.getenv("PHONE_DEFAULT_REGION", "RU")
|
||||
PHONENUMBER_DEFAULT_REGION = "RU"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# IP ОГРАНИЧЕНИЯ ДЛЯ РЕГИСТРАЦИИ
|
||||
# -----------------------------------------------------------------------------
|
||||
# Список разрешенных IP-адресов или сетей для регистрации (формат CIDR)
|
||||
# Примеры:
|
||||
# None - разрешены только локальные IP (по умолчанию)
|
||||
# ['*'] - разрешены все IP
|
||||
# ['192.168.1.0/24', '10.0.0.0/8'] - конкретные сети
|
||||
# ['192.168.1.100', '192.168.1.101'] - конкретные IP
|
||||
REGISTRATION_ALLOWED_IPS = None # По умолчанию только локальные IP
|
||||
|
||||
# Медиа файлы
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user