Harden security controls from REPORT findings
This commit is contained in:
@@ -59,13 +59,21 @@ def get_request_role(
|
||||
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer_auth)],
|
||||
settings: Annotated[Settings, Depends(get_settings)],
|
||||
) -> str:
|
||||
"""Authenticates request token and returns its authorization role."""
|
||||
"""Authenticates request token and returns its authorization role.
|
||||
|
||||
Development environments can optionally allow tokenless user access for non-admin routes to
|
||||
preserve local workflow compatibility while production remains token-enforced.
|
||||
"""
|
||||
|
||||
if credentials is None:
|
||||
if settings.allow_development_anonymous_user_access and settings.app_env.strip().lower() in {"development", "dev"}:
|
||||
return AuthRole.USER
|
||||
_raise_unauthorized()
|
||||
|
||||
token = credentials.credentials.strip()
|
||||
if not token:
|
||||
if settings.allow_development_anonymous_user_access and settings.app_env.strip().lower() in {"development", "dev"}:
|
||||
return AuthRole.USER
|
||||
_raise_unauthorized()
|
||||
return _resolve_token_role(token=token, settings=settings)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from fastapi.responses import FileResponse, Response, StreamingResponse
|
||||
from sqlalchemy import or_, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.config import get_settings, is_inline_preview_mime_type_safe
|
||||
from app.db.base import get_session
|
||||
from app.models.document import Document, DocumentStatus
|
||||
from app.schemas.documents import (
|
||||
@@ -448,14 +448,22 @@ def download_document(document_id: UUID, session: Session = Depends(get_session)
|
||||
|
||||
@router.get("/{document_id}/preview")
|
||||
def preview_document(document_id: UUID, session: Session = Depends(get_session)) -> FileResponse:
|
||||
"""Streams the original document inline when browser rendering is supported."""
|
||||
"""Streams trusted-safe MIME types inline and forces attachment for active script-capable types."""
|
||||
|
||||
document = session.execute(select(Document).where(Document.id == document_id)).scalar_one_or_none()
|
||||
if document is None:
|
||||
raise HTTPException(status_code=404, detail="Document not found")
|
||||
|
||||
original_path = absolute_path(document.stored_relative_path)
|
||||
return FileResponse(path=original_path, media_type=document.mime_type)
|
||||
common_headers = {"X-Content-Type-Options": "nosniff"}
|
||||
if not is_inline_preview_mime_type_safe(document.mime_type):
|
||||
return FileResponse(
|
||||
path=original_path,
|
||||
filename=document.original_filename,
|
||||
media_type="application/octet-stream",
|
||||
headers=common_headers,
|
||||
)
|
||||
return FileResponse(path=original_path, media_type=document.mime_type, headers=common_headers)
|
||||
|
||||
|
||||
@router.get("/{document_id}/thumbnail")
|
||||
|
||||
Reference in New Issue
Block a user