commit
fe1e7480f6
4 changed files with 66 additions and 5 deletions
13
README.md
13
README.md
|
|
@ -347,6 +347,11 @@ Same variables as compose; use `-e` instead of `.env` when running `docker run`
|
|||
<strong>Content-Type:</strong> <code>application/json</code>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Authentication (HTTP Basic):</strong><br>
|
||||
All <code>/api/v1/</code> routes except <code>/api/v1/auth/*</code> require authentication. Send your Presenton admin username and password (same as the web UI, or <strong>AUTH_USERNAME</strong> / <strong>AUTH_PASSWORD</strong> when preseeding Docker). With <code>curl</code>, put them right after <code>-u</code> as <code>-u USERNAME:PASSWORD</code> — that is HTTP Basic auth and sets <code>Authorization: Basic …</code> for you. Replace the sample <code>sudipnext:sudipnext</code> below with your real credentials.
|
||||
</p>
|
||||
|
||||
**Request Body**
|
||||
|
||||
<table>
|
||||
|
|
@ -475,18 +480,20 @@ Options: <code>pptx</code>, <code>pdf</code>
|
|||
"edit_path": "string"
|
||||
}</code></pre>
|
||||
|
||||
**Example Request**
|
||||
**Example (curl + HTTP Basic auth with <code>-u</code>)**
|
||||
|
||||
<pre><code class="language-bash">curl -X POST http://localhost:5000/api/v1/ppt/presentation/generate \
|
||||
<pre><code class="language-bash">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"
|
||||
}'</code></pre>
|
||||
|
||||
|
||||
**Example Response**
|
||||
|
||||
<pre><code class="language-json">{
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue