feat: replace inline IP resolution with centralized client_ip utility across routers and audit
DevPlace CI / test (push) Failing after 36m45s

Consolidate scattered `X-Real-IP` / `request.client.host` fallback logic into the single `client_ip()` helper from `utils`, reducing duplication in the rate limiter middleware, project file zip routes, tools shared module, and audit record builder. Also refactor three admin endpoints (`/ai-usage/data`, `/analytics`, `/users/{uid}/ai-usage`) to use the existing `require_admin()` guard instead of repeating manual auth checks, and remove now-unused `get_current_user`/`is_admin` imports from those modules.
This commit is contained in:
2026-07-04 18:07:33 +00:00
parent 244a524b91
commit 3fbac87723
14 changed files with 266 additions and 36 deletions
+2 -6
View File
@@ -3,7 +3,7 @@
import logging
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse
from devplacepy.utils import require_admin, get_current_user, is_admin
from devplacepy.utils import require_admin
from devplacepy.seo import base_seo_context, site_url, website_schema
from devplacepy.responses import respond
from devplacepy.schemas import GatewayUsageOut
@@ -46,11 +46,7 @@ async def admin_ai_usage(request: Request):
@router.get("/ai-usage/data")
async def admin_ai_usage_json(request: Request, hours: int = 48, top_n: int = 10):
user = get_current_user(request)
if not user:
return JSONResponse({"error": "Authentication required"}, status_code=401)
if not is_admin(user):
return JSONResponse({"error": "Admin access required"}, status_code=403)
require_admin(request)
service = service_manager.get_service("openai")
pricing = pricing_from_cfg(service.get_config()) if service is not None else None
return JSONResponse(build_analytics(hours, top_n=top_n, pricing=pricing))
+2 -6
View File
@@ -4,7 +4,7 @@ import logging
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse
from devplacepy.database import get_platform_analytics
from devplacepy.utils import require_admin, get_current_user, is_admin
from devplacepy.utils import require_admin
from devplacepy.responses import action_result
logger = logging.getLogger(__name__)
@@ -19,9 +19,5 @@ async def admin_index(request: Request):
@router.get("/analytics")
async def admin_analytics(request: Request, top_n: int = 10):
user = get_current_user(request)
if not user:
return JSONResponse({"error": "Authentication required"}, status_code=401)
if not is_admin(user):
return JSONResponse({"error": "Admin access required"}, status_code=403)
require_admin(request)
return JSONResponse(get_platform_analytics(top_n))
+1 -7
View File
@@ -15,8 +15,6 @@ from devplacepy.utils import (
require_admin,
hash_password_async,
clear_user_cache,
get_current_user,
is_admin,
)
from devplacepy.seo import base_seo_context, site_url, website_schema
from devplacepy.responses import respond, action_result
@@ -56,11 +54,7 @@ def _deny_senior(request: Request, admin: dict, uid: str, target: dict, event_ke
@router.get("/users/{uid}/ai-usage")
async def admin_user_ai_usage(request: Request, uid: str, hours: int = 24):
user = get_current_user(request)
if not user:
return JSONResponse({"error": "Authentication required"}, status_code=401)
if not is_admin(user):
return JSONResponse({"error": "Admin access required"}, status_code=403)
require_admin(request)
service = service_manager.get_service("openai")
pricing = pricing_from_cfg(service.get_config()) if service is not None else None
usage = build_user_usage(uid, hours=hours, pricing=pricing)
+2 -2
View File
@@ -22,7 +22,7 @@ from devplacepy.responses import respond, action_result, wants_json, json_error
from devplacepy.schemas import ProjectFilesOut
from devplacepy.services.jobs import queue
from devplacepy.seo import base_seo_context
from devplacepy.utils import get_current_user, require_user, not_found, is_admin, track_action
from devplacepy.utils import get_current_user, require_user, not_found, is_admin, track_action, client_ip
from devplacepy import project_files
from devplacepy.project_files import ProjectFileError
from devplacepy.services.audit import record as audit
@@ -125,7 +125,7 @@ async def project_files_zip(request: Request, project_slug: str, path: str = "")
return json_error(404, "Path not found")
name = subpath.rsplit("/", 1)[-1]
owner_kind, owner_id = (
("user", user["uid"]) if user else ("guest", request.client.host or "anon")
("user", user["uid"]) if user else ("guest", client_ip(request, default="anon"))
)
uid = queue.enqueue(
"zip",
+2 -1
View File
@@ -41,6 +41,7 @@ from devplacepy.utils import (
not_found,
is_admin,
track_action,
client_ip,
XP_PROJECT,
)
from devplacepy.seo import (
@@ -269,7 +270,7 @@ async def zip_project(request: Request, project_slug: str):
if not can_view_project(project, user):
raise not_found("Project not found")
owner_kind, owner_id = (
("user", user["uid"]) if user else ("guest", request.client.host or "anon")
("user", user["uid"]) if user else ("guest", client_ip(request, default="anon"))
)
busy = _active_job("zip", (owner_kind, owner_id))
if busy:
+2 -5
View File
@@ -2,14 +2,11 @@
from fastapi import Request
from devplacepy.utils import get_current_user
from devplacepy.utils import get_current_user, client_ip
def owner_for(request: Request) -> tuple[str, str]:
user = get_current_user(request)
if user:
return "user", user["uid"]
ip = request.headers.get("X-Real-IP") or (
request.client.host if request.client else "anon"
)
return "guest", ip
return "guest", client_ip(request, default="anon")