refactor(backend): extract shared helper modules per architecture rules
- Add backend/ai/utils.py — parse_classification, parse_suggestions, strip_code_fences shared by all AI providers; removes duplicated private functions from anthropic_provider.py and openai_provider.py - Add backend/deps/utils.py — get_client_ip, parse_uuid request-parsing helpers; removes local _ip() variants from admin.py, auth.py, shares.py, folders.py - Add backend/storage/exceptions.py — canonical CloudConnectionError definition; all routers and backends import from here instead of redefining - Move validate_password_strength to backend/services/auth.py; removes duplicated _validate_password_strength from admin.py and auth.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
-18
@@ -27,6 +27,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from db.models import Document, Share, User
|
||||
from deps.auth import get_regular_user
|
||||
from deps.db import get_db
|
||||
from deps.utils import get_client_ip
|
||||
from services.audit import write_audit_log
|
||||
|
||||
router = APIRouter(prefix="/api/shares", tags=["shares"])
|
||||
@@ -62,21 +63,6 @@ class SharePermissionPatch(BaseModel):
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _ip(request: Request) -> Optional[str]:
|
||||
"""Extract best-effort client IP from request (behind proxy or direct).
|
||||
|
||||
TRUST BOUNDARY: X-Forwarded-For is a client-controlled header and can be
|
||||
forged by any caller. This value is used for forensic audit logging only —
|
||||
not for authentication or access control decisions. In production, deploy
|
||||
behind a trusted reverse proxy (e.g. nginx with
|
||||
`proxy_set_header X-Forwarded-For $remote_addr;`) which overwrites this
|
||||
header with the real remote IP before it reaches FastAPI, or use a
|
||||
trusted-proxy middleware that validates the source CIDR.
|
||||
"""
|
||||
return request.headers.get("X-Forwarded-For") or (
|
||||
request.client.host if request.client else None
|
||||
)
|
||||
|
||||
|
||||
# ── POST /api/shares ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -141,7 +127,7 @@ async def grant_share(
|
||||
user_id=current_user.id,
|
||||
actor_id=current_user.id,
|
||||
resource_id=uid,
|
||||
ip_address=_ip(request),
|
||||
ip_address=get_client_ip(request),
|
||||
metadata_={"recipient_id": str(recipient.id)},
|
||||
)
|
||||
|
||||
@@ -283,7 +269,7 @@ async def update_share_permission(
|
||||
user_id=current_user.id,
|
||||
actor_id=current_user.id,
|
||||
resource_id=share.document_id,
|
||||
ip_address=_ip(request),
|
||||
ip_address=get_client_ip(request),
|
||||
metadata_={"share_id": str(share.id), "new_permission": body.permission},
|
||||
)
|
||||
await session.commit()
|
||||
@@ -328,7 +314,7 @@ async def revoke_share(
|
||||
user_id=current_user.id,
|
||||
actor_id=current_user.id,
|
||||
resource_id=document_id,
|
||||
ip_address=_ip(request),
|
||||
ip_address=get_client_ip(request),
|
||||
metadata_={"recipient_id": str(recipient_id)},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user