- Added new middleware to handle session authentication for presentation retrieval. - Introduced new layout and page components for PDF export, ensuring no loading state is shown during headless rendering. - Enhanced ConfigurationInitializer to manage loading state based on route. - Updated DashboardApi to include credentials in requests for presentation data. - Refactored hasValidLLMConfig function for cleaner validation logic.
90 lines
3 KiB
Python
90 lines
3 KiB
Python
import re
|
|
|
|
from fastapi import Request
|
|
from starlette.responses import JSONResponse
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
from utils.get_env import get_can_change_keys_env
|
|
from utils.simple_auth import (
|
|
get_auth_status,
|
|
get_basic_auth_credentials_from_request,
|
|
get_session_token_from_request,
|
|
verify_credentials,
|
|
)
|
|
from utils.user_config import update_env_with_user_config
|
|
|
|
|
|
class UserConfigEnvUpdateMiddleware(BaseHTTPMiddleware):
|
|
async def dispatch(self, request: Request, call_next):
|
|
if get_can_change_keys_env() != "false":
|
|
update_env_with_user_config()
|
|
return await call_next(request)
|
|
|
|
|
|
class SessionAuthMiddleware(BaseHTTPMiddleware):
|
|
_EXEMPT_PREFIXES = (
|
|
"/api/v1/auth/",
|
|
)
|
|
# PPTX/PDF export loads /pdf-maker in a headless browser with no session cookie; it
|
|
# only needs a single-deck read by id. (UUID is not a secret; this matches prior behavior
|
|
# when auth middleware did not protect these routes during export.)
|
|
_PRESENTATION_GET_BY_ID = re.compile(
|
|
r"^/api/v1/ppt/presentation/[0-9a-fA-F-]{36}/?$"
|
|
)
|
|
_PROTECTED_NON_API_PATHS = {
|
|
"/docs",
|
|
"/openapi.json",
|
|
"/redoc",
|
|
}
|
|
|
|
def _is_exempt(self, path: str) -> bool:
|
|
return any(path.startswith(prefix) for prefix in self._EXEMPT_PREFIXES)
|
|
|
|
def _is_presentation_get_by_id(self, request: Request, path: str) -> bool:
|
|
if request.method != "GET":
|
|
return False
|
|
return bool(self._PRESENTATION_GET_BY_ID.match(path))
|
|
|
|
def _requires_auth(self, path: str) -> bool:
|
|
if path.startswith("/api/"):
|
|
return True
|
|
if path.startswith("/app_data/"):
|
|
return True
|
|
return path in self._PROTECTED_NON_API_PATHS
|
|
|
|
async def dispatch(self, request: Request, call_next):
|
|
path = request.url.path
|
|
|
|
if (
|
|
request.method == "OPTIONS"
|
|
or not self._requires_auth(path)
|
|
or self._is_exempt(path)
|
|
or self._is_presentation_get_by_id(request, path)
|
|
):
|
|
return await call_next(request)
|
|
|
|
auth_status = get_auth_status(get_session_token_from_request(request))
|
|
if not auth_status["configured"]:
|
|
return JSONResponse(
|
|
status_code=428,
|
|
content={
|
|
"detail": "Login setup is required",
|
|
"setup_required": True,
|
|
},
|
|
)
|
|
|
|
if not auth_status["authenticated"]:
|
|
basic_credentials = get_basic_auth_credentials_from_request(request)
|
|
if basic_credentials and verify_credentials(
|
|
basic_credentials[0], basic_credentials[1]
|
|
):
|
|
request.state.auth_username = basic_credentials[0].strip()
|
|
return await call_next(request)
|
|
|
|
return JSONResponse(
|
|
status_code=401,
|
|
content={"detail": "Unauthorized"},
|
|
)
|
|
|
|
request.state.auth_username = auth_status.get("username")
|
|
return await call_next(request)
|