228 lines
9 KiB
Python
228 lines
9 KiB
Python
"""Configured public-avatar storage boundary used by API routes and health."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
from starlette.responses import Response
|
|
from starlette.staticfiles import StaticFiles
|
|
|
|
from .config import settings
|
|
from .upload_storage import (
|
|
CurrentAvatarReferenceProof,
|
|
PUBLIC_AVATAR_DIRECTORY,
|
|
UploadManifestProof,
|
|
UploadWriteFreezeGate,
|
|
assert_path_without_reparse,
|
|
assert_regular_file_without_reparse,
|
|
connected_database_target_sha256,
|
|
is_runtime_generated_public_avatar_relative_path,
|
|
public_avatar_url_to_relative_path,
|
|
public_avatar_relative_path_sha256,
|
|
read_decodable_public_avatar_object,
|
|
validate_current_avatar_references,
|
|
validate_upload_manifest,
|
|
)
|
|
|
|
|
|
def _configured_upload_root() -> Path:
|
|
configured = Path(settings.user_upload_dir)
|
|
if not configured.is_absolute():
|
|
if settings.user_upload_manifest_required:
|
|
raise RuntimeError("public USER_UPLOAD_DIR must be absolute")
|
|
configured = Path.cwd() / configured
|
|
if settings.user_upload_manifest_required:
|
|
try:
|
|
assert_path_without_reparse(configured, "public USER_UPLOAD_DIR")
|
|
except ValueError as exc:
|
|
raise RuntimeError(str(exc)) from exc
|
|
return configured.resolve(strict=False)
|
|
|
|
|
|
upload_root = _configured_upload_root()
|
|
upload_manifest_proof: UploadManifestProof | None = None
|
|
|
|
if settings.user_upload_manifest_required:
|
|
if not upload_root.is_dir():
|
|
raise RuntimeError("initialized public USER_UPLOAD_DIR does not exist")
|
|
if not settings.user_upload_manifest_path:
|
|
raise RuntimeError("USER_UPLOAD_MANIFEST_PATH is required")
|
|
if len(settings.user_upload_manifest_sha256) != 64:
|
|
raise RuntimeError("USER_UPLOAD_MANIFEST_SHA256 is required")
|
|
if not settings.user_upload_write_freeze_path:
|
|
raise RuntimeError("USER_UPLOAD_WRITE_FREEZE_PATH is required")
|
|
freeze_path = Path(settings.user_upload_write_freeze_path)
|
|
if not freeze_path.is_absolute():
|
|
raise RuntimeError("USER_UPLOAD_WRITE_FREEZE_PATH must be absolute")
|
|
upload_manifest_proof = validate_upload_manifest(
|
|
upload_root=upload_root,
|
|
manifest_path=Path(settings.user_upload_manifest_path),
|
|
expected_manifest_sha256=settings.user_upload_manifest_sha256,
|
|
expected_write_freeze_path=freeze_path,
|
|
expected_database_target_sha256=settings.public_runtime_db_target_sha256,
|
|
verify_preserved_objects=True,
|
|
reject_unbound_extras=False,
|
|
)
|
|
else:
|
|
upload_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
public_avatar_root = upload_root / PUBLIC_AVATAR_DIRECTORY
|
|
if settings.user_upload_manifest_required:
|
|
if not public_avatar_root.is_dir():
|
|
raise RuntimeError("initialized public avatar directory does not exist")
|
|
try:
|
|
assert_path_without_reparse(
|
|
public_avatar_root,
|
|
"initialized public avatar directory",
|
|
)
|
|
except ValueError as exc:
|
|
raise RuntimeError(str(exc)) from exc
|
|
else:
|
|
public_avatar_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
configured_freeze_path = (
|
|
Path(settings.user_upload_write_freeze_path)
|
|
if settings.user_upload_write_freeze_path
|
|
else None
|
|
)
|
|
upload_write_freeze_gate = UploadWriteFreezeGate(configured_freeze_path)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RuntimeUploadDatabaseProof:
|
|
database_target_sha256: str
|
|
current_references: CurrentAvatarReferenceProof
|
|
|
|
|
|
async def validate_runtime_upload_database_state(
|
|
pool: Any,
|
|
) -> RuntimeUploadDatabaseProof:
|
|
"""Bind readiness to one repeatable-read snapshot from this API's pool."""
|
|
|
|
if upload_manifest_proof is None:
|
|
raise RuntimeError("public upload manifest proof is unavailable")
|
|
async with pool.acquire() as connection:
|
|
async with connection.transaction(isolation="repeatable_read", readonly=True):
|
|
database_target = await connected_database_target_sha256(connection)
|
|
if database_target != settings.public_runtime_db_target_sha256:
|
|
raise RuntimeError("connected public runtime database target drift")
|
|
await connection.execute(
|
|
"SELECT set_config('app.ai_context', '', true), "
|
|
"set_config('app.current_role', 'admin', true)"
|
|
)
|
|
rows = await connection.fetch(
|
|
"""
|
|
SELECT avatar_url
|
|
FROM app.app_user
|
|
WHERE avatar_url IS NOT NULL
|
|
AND btrim(avatar_url) LIKE '/uploads/%'
|
|
ORDER BY avatar_url
|
|
"""
|
|
)
|
|
current_references = validate_current_avatar_references(
|
|
upload_root=upload_root,
|
|
avatar_urls=[str(row["avatar_url"]) for row in rows],
|
|
initial_manifest=upload_manifest_proof,
|
|
expected_database_target_sha256=database_target,
|
|
)
|
|
return RuntimeUploadDatabaseProof(
|
|
database_target_sha256=database_target,
|
|
current_references=current_references,
|
|
)
|
|
|
|
|
|
def assert_public_avatar_url_exists(value: str) -> None:
|
|
if not value.startswith("/uploads/"):
|
|
return
|
|
relative_path = public_avatar_url_to_relative_path(value)
|
|
path_sha256 = public_avatar_relative_path_sha256(relative_path)
|
|
preserved_decode_by_path = dict(
|
|
upload_manifest_proof.preserved_path_decode_records
|
|
if upload_manifest_proof is not None
|
|
else ()
|
|
)
|
|
decode_valid = preserved_decode_by_path.get(path_sha256)
|
|
if decode_valid is False:
|
|
raise ValueError("public avatar URL object is forensic-only")
|
|
candidate = upload_root / Path(*relative_path.split("/"))
|
|
if decode_valid is True:
|
|
assert_regular_file_without_reparse(
|
|
candidate,
|
|
"public avatar URL object",
|
|
)
|
|
return
|
|
if not is_runtime_generated_public_avatar_relative_path(relative_path):
|
|
raise ValueError("public avatar URL object is not authorized")
|
|
read_decodable_public_avatar_object(
|
|
relative_path=relative_path,
|
|
path=candidate,
|
|
label="public avatar URL object",
|
|
)
|
|
|
|
|
|
class FlatPublicAvatarStaticFiles(StaticFiles):
|
|
"""Serve only one validated public avatar image, never nested/hidden extras."""
|
|
|
|
async def get_response(self, path: str, scope):
|
|
method = str(scope.get("method", "GET")).upper()
|
|
if method not in {"GET", "HEAD"}:
|
|
raise StarletteHTTPException(status_code=405)
|
|
try:
|
|
normalized = public_avatar_url_to_relative_path(
|
|
f"/uploads/profile-avatars/{path}"
|
|
)
|
|
filename = normalized.split("/", 1)[1]
|
|
path_sha256 = public_avatar_relative_path_sha256(normalized)
|
|
preserved_decode_by_path = dict(
|
|
upload_manifest_proof.preserved_path_decode_records
|
|
if upload_manifest_proof is not None
|
|
else ()
|
|
)
|
|
preserved_public_cache = {
|
|
record[0]: (record[1], record[2], record[3], record[4])
|
|
for record in (
|
|
upload_manifest_proof.preserved_records
|
|
if upload_manifest_proof is not None
|
|
else ()
|
|
)
|
|
}
|
|
decode_valid = preserved_decode_by_path.get(path_sha256)
|
|
if decode_valid is False:
|
|
raise ValueError("public avatar object is forensic-only")
|
|
preserved = preserved_public_cache.get(path_sha256)
|
|
if preserved is not None:
|
|
content_sha256, size_bytes, media_type, content = preserved
|
|
body = content if method == "GET" else b""
|
|
return Response(
|
|
content=body,
|
|
media_type=media_type,
|
|
headers={
|
|
"Content-Length": str(size_bytes),
|
|
"ETag": f'"{content_sha256}"',
|
|
"Cache-Control": "public, max-age=3600",
|
|
},
|
|
)
|
|
if decode_valid is True:
|
|
raise ValueError("validated public avatar cache is unavailable")
|
|
if not is_runtime_generated_public_avatar_relative_path(normalized):
|
|
raise ValueError("public avatar object is not authorized")
|
|
candidate = Path(self.directory) / filename
|
|
runtime_object = read_decodable_public_avatar_object(
|
|
relative_path=normalized,
|
|
path=candidate,
|
|
label="public avatar static object",
|
|
)
|
|
except (OSError, ValueError) as exc:
|
|
raise StarletteHTTPException(status_code=404) from exc
|
|
return Response(
|
|
content=runtime_object.content if method == "GET" else b"",
|
|
media_type=runtime_object.media_type,
|
|
headers={
|
|
"Content-Length": str(runtime_object.size_bytes),
|
|
"ETag": f'"{runtime_object.content_sha256}"',
|
|
"Cache-Control": "public, max-age=3600",
|
|
},
|
|
)
|