diff --git a/README.md b/README.md index d16df97..2611b66 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,9 @@ cd frontend && npm run preview Main runtime variables are defined in `docker-compose.yml`: - API and worker: `DATABASE_URL`, `REDIS_URL`, `REDIS_SECURITY_MODE`, `REDIS_TLS_MODE`, `STORAGE_ROOT`, `PUBLIC_BASE_URL`, `CORS_ORIGINS`, `ALLOW_DEVELOPMENT_ANONYMOUS_USER_ACCESS`, `TYPESENSE_*`, `APP_SETTINGS_ENCRYPTION_KEY` -- Frontend: `VITE_API_BASE`, optional `VITE_API_TOKEN` compatibility fallback +- Frontend: optional `VITE_API_BASE`, optional `VITE_API_TOKEN` compatibility fallback + +When `VITE_API_BASE` is unset, the frontend defaults to `http://:8000/api/v1`. Application settings saved from the UI persist at: diff --git a/backend/app/main.py b/backend/app/main.py index f8c18ce..1539c0b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -18,6 +18,16 @@ from app.services.typesense_index import ensure_typesense_collection settings = get_settings() UPLOAD_ENDPOINT_PATH = "/api/v1/documents/upload" UPLOAD_ENDPOINT_METHOD = "POST" +DEVELOPMENT_CORS_PRIVATE_ORIGIN_REGEX = ( + r"^https?://(" + r"localhost" + r"|127(?:\.\d{1,3}){3}" + r"|0\.0\.0\.0" + r"|10(?:\.\d{1,3}){3}" + r"|192\.168(?:\.\d{1,3}){2}" + r"|172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2}" + r")(?::\d+)?$" +) def _is_upload_size_guard_target(request: Request) -> bool: @@ -34,9 +44,14 @@ def create_app() -> FastAPI: """Builds and configures the FastAPI application instance.""" app = FastAPI(title="DCM DMS API", version="0.1.0") + app_env = settings.app_env.strip().lower() + allow_origin_regex = ( + DEVELOPMENT_CORS_PRIVATE_ORIGIN_REGEX if app_env in {"development", "dev"} else None + ) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, + allow_origin_regex=allow_origin_regex, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/backend/tests/test_upload_request_size_middleware.py b/backend/tests/test_upload_request_size_middleware.py index 43efa21..33deec7 100644 --- a/backend/tests/test_upload_request_size_middleware.py +++ b/backend/tests/test_upload_request_size_middleware.py @@ -115,6 +115,7 @@ def _install_main_import_stubs() -> dict[str, ModuleType | None]: """Returns minimal settings consumed by app.main during test import.""" return SimpleNamespace( + app_env="development", cors_origins=["http://localhost:5173"], max_upload_request_size_bytes=1024, ) diff --git a/doc/operations-and-configuration.md b/doc/operations-and-configuration.md index 44feb77..a6bcd36 100644 --- a/doc/operations-and-configuration.md +++ b/doc/operations-and-configuration.md @@ -113,9 +113,14 @@ Selected defaults from `Settings` (`backend/app/core/config.py`): ## Frontend Configuration Frontend runtime API target: -- `VITE_API_BASE` in `docker-compose.yml` frontend service +- `VITE_API_BASE` in `docker-compose.yml` frontend service (optional override) - `VITE_API_TOKEN` in `docker-compose.yml` frontend service (optional compatibility fallback only) +When `VITE_API_BASE` is unset, frontend API helpers resolve the backend URL dynamically as: +- `http://:8000/api/v1` + +This keeps development access working when the UI is opened through a LAN IP instead of `localhost`. + Frontend API authentication behavior: - `frontend/src/lib/api.ts` resolves bearer tokens at request time in this order: - custom runtime resolver (`setApiTokenResolver`) @@ -163,6 +168,7 @@ Retention settings are used by worker cleanup and by `POST /api/v1/processing/lo - `documents` endpoints: user token or admin token - `settings` and `processing/logs` endpoints: admin token only - Development environments can allow tokenless user-role access for document/search routes via `ALLOW_DEVELOPMENT_ANONYMOUS_USER_ACCESS=true`; production remains token-enforced. +- Development CORS allows localhost and RFC1918 private-network origins via regex in addition to explicit `CORS_ORIGINS`, so LAN-hosted frontend access remains functional. - Authentication fails closed when `ADMIN_API_TOKEN` is not configured and admin access is requested. - Document preview endpoint blocks inline rendering for script-capable MIME types and forces attachment responses for active content. - Provider base URLs are validated on settings updates and before outbound model calls: diff --git a/docker-compose.yml b/docker-compose.yml index a3b8d40..ef2424a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -119,7 +119,7 @@ services: build: context: ./frontend environment: - VITE_API_BASE: ${VITE_API_BASE:-http://localhost:8000/api/v1} + VITE_API_BASE: ${VITE_API_BASE:-} VITE_API_TOKEN: ${VITE_API_TOKEN:-} ports: - "${HOST_BIND_IP:-127.0.0.1}:5173:5173" diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 14b3070..7fa3ddb 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -14,9 +14,25 @@ import type { } from '../types'; /** - * Resolves backend base URL from environment with localhost fallback. + * Resolves backend base URL from environment with current-host HTTP fallback. */ -const API_BASE = import.meta.env?.VITE_API_BASE ?? 'http://localhost:8000/api/v1'; +function resolveApiBase(): string { + const envValue = import.meta.env?.VITE_API_BASE; + if (typeof envValue === 'string') { + const trimmed = envValue.trim().replace(/\/+$/, ''); + if (trimmed) { + return trimmed; + } + } + + if (typeof window !== 'undefined' && window.location?.hostname) { + return `http://${window.location.hostname}:8000/api/v1`; + } + + return 'http://localhost:8000/api/v1'; +} + +const API_BASE = resolveApiBase(); /** * Legacy environment token fallback used only when no runtime token source is available.