Skip to content

Commit f97fbae

Browse files
committed
fix
1 parent 922f2ef commit f97fbae

File tree

13 files changed

+184
-69
lines changed

13 files changed

+184
-69
lines changed

packages/django_cfg/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class MyConfig(DjangoConfig):
3232
default_app_config = "django_cfg.apps.DjangoCfgConfig"
3333

3434
# Version information
35-
__version__ = "1.5.110"
35+
__version__ = "1.5.115"
3636
__license__ = "MIT"
3737

3838
# Setup warnings debug early (checks env var only at this point)

packages/django_cfg/apps/api/endpoints/endpoints_status/drf_views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from rest_framework import status
8-
from rest_framework.permissions import AllowAny
8+
from rest_framework.permissions import IsAdminUser
99
from rest_framework.response import Response
1010
from rest_framework.views import APIView
1111

@@ -26,9 +26,12 @@ class DRFEndpointsStatusView(APIView):
2626
- auto_auth: Auto-retry with JWT on 401/403 (default: true)
2727
2828
This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
29+
30+
**IMPORTANT**: Admin-only for security (shows all endpoint statuses).
31+
For public health checks, use /cfg/health/ instead.
2932
"""
3033

31-
permission_classes = [AllowAny] # Public endpoint
34+
permission_classes = [IsAdminUser] # Admin-only for security
3235
serializer_class = EndpointsStatusSerializer # For schema generation
3336

3437
def get(self, request):

packages/django_cfg/apps/api/endpoints/urls_list/views.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.conf import settings
1111
from django.urls import URLPattern, URLResolver, get_resolver
1212
from rest_framework import status
13-
from rest_framework.permissions import AllowAny
13+
from rest_framework.permissions import IsAdminUser
1414
from rest_framework.response import Response
1515
from rest_framework.views import APIView
1616

@@ -31,13 +31,24 @@ class DRFURLsListView(APIView):
3131
- HTTP methods
3232
3333
This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
34+
35+
**IMPORTANT**: Admin-only endpoint for security reasons.
3436
"""
3537

36-
permission_classes = [AllowAny] # Public endpoint (can be restricted)
38+
permission_classes = [IsAdminUser] # Admin-only for security
3739
serializer_class = URLsListSerializer # For schema generation
3840

3941
def get(self, request):
40-
"""Return all registered URLs."""
42+
"""
43+
Return registered URLs (API endpoints by default).
44+
45+
Query Parameters:
46+
filter (str): Filter URLs by prefix
47+
- omit or 'api': Only API endpoints (/api/*) - DEFAULT
48+
- 'all': All URLs (admin, api, cfg, etc.)
49+
- 'admin': Only admin endpoints (/admin/*)
50+
- 'cfg': Only django-cfg endpoints (/cfg/*)
51+
"""
4152
try:
4253
config = getattr(settings, 'config', None)
4354

@@ -46,19 +57,33 @@ def get(self, request):
4657
if not base_url:
4758
base_url = request.build_absolute_uri('/').rstrip('/')
4859

60+
# Get filter parameter (default: 'api')
61+
url_filter = request.query_params.get('filter', 'api')
62+
4963
urls_data = {
5064
"status": "success",
5165
"service": config.project_name if config else "Django CFG",
5266
"version": get_current_version(),
5367
"base_url": base_url,
68+
"filter": url_filter,
5469
"total_urls": 0,
5570
"urls": []
5671
}
5772

5873
# Extract all URLs
59-
url_patterns = self._get_all_urls()
60-
urls_data["urls"] = url_patterns
61-
urls_data["total_urls"] = len(url_patterns)
74+
all_urls = self._get_all_urls()
75+
76+
# Apply filter (default: api)
77+
if url_filter == 'all':
78+
# Show all URLs
79+
urls_data["urls"] = all_urls
80+
urls_data["total_urls"] = len(all_urls)
81+
else:
82+
# Filter by prefix (api, admin, cfg)
83+
filtered_urls = self._filter_urls(all_urls, url_filter)
84+
urls_data["urls"] = filtered_urls
85+
urls_data["total_urls"] = len(filtered_urls)
86+
urls_data["total_urls_unfiltered"] = len(all_urls)
6287

6388
return Response(urls_data, status=status.HTTP_200_OK)
6489

@@ -68,6 +93,27 @@ def get(self, request):
6893
"error": str(e)
6994
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
7095

96+
def _filter_urls(self, urls: List[Dict[str, Any]], filter_type: str) -> List[Dict[str, Any]]:
97+
"""
98+
Filter URLs by prefix.
99+
100+
Args:
101+
urls: List of URL dictionaries
102+
filter_type: Filter type ('api', 'admin', 'cfg')
103+
104+
Returns:
105+
Filtered list of URLs
106+
"""
107+
if filter_type == 'api':
108+
return [url for url in urls if url['pattern'].startswith('api/')]
109+
elif filter_type == 'admin':
110+
return [url for url in urls if url['pattern'].startswith('admin/')]
111+
elif filter_type == 'cfg':
112+
return [url for url in urls if url['pattern'].startswith('cfg/')]
113+
else:
114+
# Unknown filter - return all
115+
return urls
116+
71117
def _get_all_urls(self, urlpatterns=None, prefix='', namespace=None) -> List[Dict[str, Any]]:
72118
"""
73119
Recursively extract all URL patterns from Django URLconf.
@@ -187,9 +233,11 @@ class DRFURLsListCompactView(APIView):
187233
Compact URLs list endpoint - just patterns and names.
188234
189235
This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
236+
237+
**IMPORTANT**: Admin-only endpoint for security reasons.
190238
"""
191239

192-
permission_classes = [AllowAny]
240+
permission_classes = [IsAdminUser] # Admin-only for security
193241

194242
def get(self, request):
195243
"""Return compact URL list."""

packages/django_cfg/apps/integrations/centrifugo/views/rpc_proxy.py

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,15 @@
3030

3131

3232
class RPCProxyRequest(BaseModel):
33-
"""
34-
Request from Centrifugo RPC proxy.
35-
36-
Supports both Centrifugo v5 and v6 formats.
37-
V6 sends: {method, data} - minimal format
38-
V5 sends: {client, transport, protocol, encoding, user, method, data, meta}
39-
"""
33+
"""Request from Centrifugo RPC proxy."""
4034

41-
# V6 minimal format - these are the main fields
42-
method: str = Field("", description="RPC method name")
43-
data: dict[str, Any] | str = Field(default_factory=dict, description="RPC params (dict or JSON string)")
44-
45-
# V5 extended format - optional in v6
4635
client: str = Field("", description="Client connection ID")
4736
transport: str = Field("websocket", description="Transport type")
4837
protocol: str = Field("json", description="Protocol format")
4938
encoding: str = Field("json", description="Encoding type")
5039
user: str = Field("", description="User ID from auth")
40+
method: str = Field("", description="RPC method name")
41+
data: dict[str, Any] = Field(default_factory=dict, description="RPC params")
5142
meta: dict[str, Any] | None = Field(None, description="Connection metadata")
5243

5344

@@ -98,20 +89,11 @@ async def post(self, request):
9889
body = json.loads(request.body)
9990
rpc_request = RPCProxyRequest(**body)
10091

101-
client_short = rpc_request.client[:8] if rpc_request.client else "unknown"
10292
logger.info(
10393
f"RPC proxy: method={rpc_request.method} "
104-
f"user={rpc_request.user} client={client_short}..."
94+
f"user={rpc_request.user} client={rpc_request.client[:8]}..."
10595
)
10696

107-
# Parse data if it's a JSON string (Centrifugo v6 sends it this way)
108-
rpc_data = rpc_request.data
109-
if isinstance(rpc_data, str):
110-
try:
111-
rpc_data = json.loads(rpc_data)
112-
except json.JSONDecodeError:
113-
rpc_data = {"raw": rpc_data}
114-
11597
# Get router and check if handler exists
11698
router = get_global_router()
11799

@@ -129,7 +111,7 @@ async def post(self, request):
129111
handler = router.get_handler(rpc_request.method)
130112

131113
# Try to validate params using handler's type hints
132-
params = rpc_data
114+
params = rpc_request.data
133115
try:
134116
# Get param type from handler signature
135117
import inspect

packages/django_cfg/apps/integrations/grpc/managers/grpc_request_log.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,17 @@ async def amark_success(
191191
self,
192192
log_instance,
193193
duration_ms: int | None = None,
194+
response_data: dict | None = None,
194195
):
195196
"""
196197
Mark request as successful (ASYNC - Django 5.2).
197198
198199
Args:
199200
log_instance: GRPCRequestLog instance
200201
duration_ms: Duration in milliseconds
202+
response_data: Response data (optional, for future use)
201203
"""
204+
# NOTE: response_data is accepted but not stored (no field in model yet)
202205
from ..models import GRPCRequestLog
203206

204207
log_instance.status = GRPCRequestLog.StatusChoices.SUCCESS
@@ -258,6 +261,7 @@ async def amark_error(
258261
log_instance,
259262
grpc_status_code: str,
260263
error_message: str,
264+
error_details: dict | None = None,
261265
duration_ms: int | None = None,
262266
):
263267
"""
@@ -267,13 +271,15 @@ async def amark_error(
267271
log_instance: GRPCRequestLog instance
268272
grpc_status_code: gRPC status code
269273
error_message: Error message
274+
error_details: Additional error details (JSON)
270275
duration_ms: Duration in milliseconds
271276
"""
272277
from ..models import GRPCRequestLog
273278

274279
log_instance.status = GRPCRequestLog.StatusChoices.ERROR
275280
log_instance.grpc_status_code = grpc_status_code
276281
log_instance.error_message = error_message
282+
log_instance.error_details = error_details
277283
log_instance.completed_at = timezone.now()
278284

279285
if duration_ms is not None:
@@ -284,6 +290,7 @@ async def amark_error(
284290
"status",
285291
"grpc_status_code",
286292
"error_message",
293+
"error_details",
287294
"completed_at",
288295
"duration_ms",
289296
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated manually for django_cfg 1.5.111 compatibility
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("grpc", "0007_simplify_grpc_models"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="grpcrequestlog",
15+
name="error_details",
16+
field=models.JSONField(blank=True, null=True),
17+
),
18+
]

packages/django_cfg/apps/integrations/grpc/models/grpc_request_log.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class StatusChoices(models.TextChoices):
4444
)
4545
grpc_status_code = models.CharField(max_length=50, null=True, blank=True, db_index=True)
4646
error_message = models.TextField(null=True, blank=True)
47+
error_details = models.JSONField(null=True, blank=True)
4748

4849
# Performance
4950
duration_ms = models.IntegerField(null=True, blank=True)

packages/django_cfg/apps/integrations/grpc/services/interceptors/observability.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,13 @@ async def wrapper(request, context):
546546
)
547547

548548
try:
549-
response = await behavior(request, context)
549+
# Handle both async and sync behaviors (Health Check is sync)
550+
import inspect
551+
if inspect.iscoroutinefunction(behavior):
552+
response = await behavior(request, context)
553+
else:
554+
response = behavior(request, context)
555+
550556
duration_ms = (time.time() - start_time) * 1000
551557

552558
if self.enable_metrics:
@@ -848,10 +854,6 @@ async def _create_log_entry(
848854
api_key=api_key,
849855
is_authenticated=user is not None,
850856
client_ip=client_ip,
851-
user_agent=user_agent,
852-
peer=peer,
853-
request_data=self._serialize_message(request) if request else None,
854-
status="pending",
855857
)
856858
return log_entry
857859

packages/django_cfg/core/builders/security_builder.py

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -313,27 +313,20 @@ def _get_dev_csrf_origins(self) -> List[str]:
313313
Smart list of dev CSRF origins.
314314
315315
Covers:
316-
- Popular dev ports
316+
- All dev ports from 3000 to 10000 (covers all common dev servers)
317317
- localhost and 127.0.0.1
318318
319319
Docker IPs NOT needed - CSRF checks Referer from browser!
320320
321321
Returns:
322322
List of dev CSRF origins
323323
"""
324-
popular_ports = [
325-
3000, # React/Next.js default
326-
3777, # Next.js Admin default
327-
5173, # Vite default
328-
5174, # Vite preview
329-
8080, # Vue/Spring Boot
330-
4200, # Angular
331-
8000, # Django default
332-
8001, # Django alternative
333-
]
324+
# Wide port range for development (3000-10000)
325+
# Covers: Next.js, React, Vite, Angular, Vue, Django, Flask, etc.
326+
dev_ports = range(3000, 10001)
334327

335328
origins = []
336-
for port in popular_ports:
329+
for port in dev_ports:
337330
origins.extend([
338331
f"http://localhost:{port}",
339332
f"http://127.0.0.1:{port}",
@@ -364,29 +357,20 @@ def _get_localhost_cors_regexes(self) -> List[str]:
364357

365358
def _get_localhost_csrf_origins(self) -> List[str]:
366359
"""
367-
Localhost CSRF origins with common development ports.
360+
Localhost CSRF origins with wide port range for development.
368361
369362
CSRF doesn't support regex, so we need to list specific ports.
370-
Covers standard development tools and frameworks.
363+
Covers all development ports from 3000 to 10000.
371364
372365
Note: CORS uses regex for ALL ports via CORS_ALLOWED_ORIGIN_REGEXES.
373-
CSRF is more limited - only these common ports are whitelisted.
366+
CSRF needs explicit port list - using same range as dev mode (3000-10000).
374367
375368
Returns:
376-
List of localhost origins with common ports for CSRF
369+
List of localhost origins with development ports for CSRF
377370
"""
378-
# Common development ports (framework defaults)
379-
dev_ports = [
380-
# Frontend frameworks
381-
3000, 3001, 3002, # React/Next.js
382-
5173, 5174, # Vite
383-
8080, 8081, # Vue/Spring Boot
384-
4200, # Angular
385-
# Backend servers
386-
8000, 8001, 8002, # Django
387-
5000, # Flask
388-
4000, # Express
389-
]
371+
# Wide port range for development (3000-10000)
372+
# Covers: Next.js, React, Vite, Angular, Vue, Django, Flask, etc.
373+
dev_ports = range(3000, 10001)
390374

391375
origins = []
392376
for port in dev_ports:

0 commit comments

Comments
 (0)