Fix LAN API base and development CORS regression
This commit is contained in:
@@ -116,7 +116,9 @@ cd frontend && npm run preview
|
|||||||
Main runtime variables are defined in `docker-compose.yml`:
|
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`
|
- 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://<current-hostname>:8000/api/v1`.
|
||||||
|
|
||||||
Application settings saved from the UI persist at:
|
Application settings saved from the UI persist at:
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ from app.services.typesense_index import ensure_typesense_collection
|
|||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
UPLOAD_ENDPOINT_PATH = "/api/v1/documents/upload"
|
UPLOAD_ENDPOINT_PATH = "/api/v1/documents/upload"
|
||||||
UPLOAD_ENDPOINT_METHOD = "POST"
|
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:
|
def _is_upload_size_guard_target(request: Request) -> bool:
|
||||||
@@ -34,9 +44,14 @@ def create_app() -> FastAPI:
|
|||||||
"""Builds and configures the FastAPI application instance."""
|
"""Builds and configures the FastAPI application instance."""
|
||||||
|
|
||||||
app = FastAPI(title="DCM DMS API", version="0.1.0")
|
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(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=settings.cors_origins,
|
allow_origins=settings.cors_origins,
|
||||||
|
allow_origin_regex=allow_origin_regex,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ def _install_main_import_stubs() -> dict[str, ModuleType | None]:
|
|||||||
"""Returns minimal settings consumed by app.main during test import."""
|
"""Returns minimal settings consumed by app.main during test import."""
|
||||||
|
|
||||||
return SimpleNamespace(
|
return SimpleNamespace(
|
||||||
|
app_env="development",
|
||||||
cors_origins=["http://localhost:5173"],
|
cors_origins=["http://localhost:5173"],
|
||||||
max_upload_request_size_bytes=1024,
|
max_upload_request_size_bytes=1024,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -113,9 +113,14 @@ Selected defaults from `Settings` (`backend/app/core/config.py`):
|
|||||||
## Frontend Configuration
|
## Frontend Configuration
|
||||||
|
|
||||||
Frontend runtime API target:
|
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)
|
- `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://<current-frontend-hostname>: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 API authentication behavior:
|
||||||
- `frontend/src/lib/api.ts` resolves bearer tokens at request time in this order:
|
- `frontend/src/lib/api.ts` resolves bearer tokens at request time in this order:
|
||||||
- custom runtime resolver (`setApiTokenResolver`)
|
- 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
|
- `documents` endpoints: user token or admin token
|
||||||
- `settings` and `processing/logs` endpoints: admin token only
|
- `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 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.
|
- 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.
|
- 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:
|
- Provider base URLs are validated on settings updates and before outbound model calls:
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
environment:
|
environment:
|
||||||
VITE_API_BASE: ${VITE_API_BASE:-http://localhost:8000/api/v1}
|
VITE_API_BASE: ${VITE_API_BASE:-}
|
||||||
VITE_API_TOKEN: ${VITE_API_TOKEN:-}
|
VITE_API_TOKEN: ${VITE_API_TOKEN:-}
|
||||||
ports:
|
ports:
|
||||||
- "${HOST_BIND_IP:-127.0.0.1}:5173:5173"
|
- "${HOST_BIND_IP:-127.0.0.1}:5173:5173"
|
||||||
|
|||||||
@@ -14,9 +14,25 @@ import type {
|
|||||||
} from '../types';
|
} 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.
|
* Legacy environment token fallback used only when no runtime token source is available.
|
||||||
|
|||||||
Reference in New Issue
Block a user