From f8da1802fd6035a261df1a182f96dcc8089d78fd Mon Sep 17 00:00:00 2001
From: sudipnext
Date: Thu, 23 Apr 2026 17:00:17 +0545
Subject: [PATCH 1/2] Enhance authentication handling in FastAPI by adding
basic authentication support and improving session token management.
Introduced new utility functions for retrieving configured usernames and
basic auth credentials, and updated middleware to utilize these enhancements
for better session management.
---
servers/fastapi/api/middlewares.py | 14 ++++++++-
.../fastapi/templates/get_layout_by_name.py | 13 +++++++-
servers/fastapi/utils/simple_auth.py | 31 +++++++++++++++++++
3 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/servers/fastapi/api/middlewares.py b/servers/fastapi/api/middlewares.py
index fb45e7f1..b8bcd11c 100644
--- a/servers/fastapi/api/middlewares.py
+++ b/servers/fastapi/api/middlewares.py
@@ -3,7 +3,12 @@ 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_session_token_from_request
+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
@@ -55,6 +60,13 @@ class SessionAuthMiddleware(BaseHTTPMiddleware):
)
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"},
diff --git a/servers/fastapi/templates/get_layout_by_name.py b/servers/fastapi/templates/get_layout_by_name.py
index f69251ff..d6baca16 100644
--- a/servers/fastapi/templates/get_layout_by_name.py
+++ b/servers/fastapi/templates/get_layout_by_name.py
@@ -2,12 +2,23 @@ import aiohttp
from fastapi import HTTPException
from templates.presentation_layout import PresentationLayoutModel
+from utils.simple_auth import (
+ SESSION_COOKIE_NAME,
+ create_session_token,
+ get_configured_auth_username,
+)
async def get_layout_by_name(layout_name: str) -> PresentationLayoutModel:
url = f"http://localhost/api/template?group={layout_name}"
+ headers = {}
+ auth_username = get_configured_auth_username()
+ if auth_username:
+ internal_token = create_session_token(auth_username)
+ headers["Cookie"] = f"{SESSION_COOKIE_NAME}={internal_token}"
+
async with aiohttp.ClientSession() as session:
- async with session.get(url) as response:
+ async with session.get(url, headers=headers) as response:
if response.status != 200:
error_text = await response.text()
raise HTTPException(
diff --git a/servers/fastapi/utils/simple_auth.py b/servers/fastapi/utils/simple_auth.py
index 5eb23f50..0acae5bc 100644
--- a/servers/fastapi/utils/simple_auth.py
+++ b/servers/fastapi/utils/simple_auth.py
@@ -98,6 +98,14 @@ def is_auth_configured() -> bool:
return bool(config.get("AUTH_USERNAME") and config.get("AUTH_PASSWORD_HASH"))
+def get_configured_auth_username() -> Optional[str]:
+ config = _load_user_config()
+ username = config.get("AUTH_USERNAME")
+ if isinstance(username, str) and username.strip():
+ return username.strip()
+ return None
+
+
def setup_initial_credentials(username: str, password: str) -> None:
cleaned_username = (username or "").strip()
if len(cleaned_username) < 3:
@@ -244,6 +252,29 @@ def get_session_token_from_request(request: Request) -> Optional[str]:
return None
+def get_basic_auth_credentials_from_request(
+ request: Request,
+) -> Optional[tuple[str, str]]:
+ auth_header = request.headers.get("Authorization", "")
+ if not auth_header.lower().startswith("basic "):
+ return None
+
+ encoded_value = auth_header[6:].strip()
+ if not encoded_value:
+ return None
+
+ try:
+ decoded_value = base64.b64decode(encoded_value).decode("utf-8")
+ except Exception:
+ return None
+
+ if ":" not in decoded_value:
+ return None
+
+ username, password = decoded_value.split(":", 1)
+ return username, password
+
+
def get_auth_status(session_token: Optional[str] = None) -> dict:
config = _load_user_config()
configured = bool(config.get("AUTH_USERNAME") and config.get("AUTH_PASSWORD_HASH"))
From 398124b6a606b5397a5bad76053b951d849d2951 Mon Sep 17 00:00:00 2001
From: sudipnext
Date: Thu, 23 Apr 2026 17:40:03 +0545
Subject: [PATCH 2/2] Update README.md to include HTTP Basic authentication
details for API routes and provide example usage with curl.
---
README.md | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 849708ba..9f9d141d 100644
--- a/README.md
+++ b/README.md
@@ -347,6 +347,11 @@ Same variables as compose; use `-e` instead of `.env` when running `docker run`
Content-Type: application/json
+
+Authentication (HTTP Basic):
+All /api/v1/ routes except /api/v1/auth/* require authentication. Send your Presenton admin username and password (same as the web UI, or AUTH_USERNAME / AUTH_PASSWORD when preseeding Docker). With curl, put them right after -u as -u USERNAME:PASSWORD — that is HTTP Basic auth and sets Authorization: Basic … for you. Replace the sample sudipnext:sudipnext below with your real credentials.
+
+
**Request Body**
@@ -475,18 +480,20 @@ Options: pptx, pdf
"edit_path": "string"
}
-**Example Request**
+**Example (curl + HTTP Basic auth with -u)**
-curl -X POST http://localhost:5000/api/v1/ppt/presentation/generate \
+curl -u username:password \
+ -X POST http://localhost:5000/api/v1/ppt/presentation/generate \
-H "Content-Type: application/json" \
-d '{
- "content": "Introduction to Machine Learning",
+ "content": "Introduction to Machine Learning",
"n_slides": 5,
"language": "English",
"template": "general",
"export_as": "pptx"
}'
+
**Example Response**
{