Add optional app-level basic auth via env vars

This commit is contained in:
2026-03-02 20:21:30 +01:00
parent 158ef648ee
commit 2c99a75cd8
2 changed files with 50 additions and 3 deletions

View File

@@ -42,12 +42,38 @@ Beispiel mit Uvicorn direkt:
.venv/bin/python -m uvicorn web.app:app --host 0.0.0.0 --port 8000 .venv/bin/python -m uvicorn web.app:app --host 0.0.0.0 --port 8000
``` ```
Optional mit App-Auth (zusätzliche Schutzschicht):
```bash
WEB_AUTH_USER=kalender WEB_AUTH_PASSWORD='StarkesPasswort' \
.venv/bin/python -m uvicorn web.app:app --host 0.0.0.0 --port 8000
```
Empfohlen für Internet-Betrieb: Empfohlen für Internet-Betrieb:
- Uvicorn hinter Nginx - Uvicorn hinter Nginx
- HTTPS aktivieren - HTTPS aktivieren
- Upload-Größenlimit setzen - Upload-Größenlimit setzen
- Zugriff absichern (z. B. Basic Auth oder Login) - Zugriff absichern (z. B. Basic Auth oder Login)
## App-Auth (optional, zusätzlich zu Nginx)
Wenn `WEB_AUTH_USER` und `WEB_AUTH_PASSWORD` gesetzt sind, schützt die App alle Endpunkte per HTTP Basic Auth.
Linux/macOS Beispiel:
```bash
export WEB_AUTH_USER=kalender
export WEB_AUTH_PASSWORD='StarkesPasswort'
./start_web.sh
```
Windows (PowerShell) Beispiel:
```powershell
$env:WEB_AUTH_USER='kalender'
$env:WEB_AUTH_PASSWORD='StarkesPasswort'
./start_web.cmd
```
Hinweis: Für öffentlich erreichbare Server weiterhin Nginx + HTTPS verwenden.
## Öffentliches Deployment (HTTPS) ## Öffentliches Deployment (HTTPS)
Beispiel für Ubuntu-Server mit Domain `ics.example.de`. Beispiel für Ubuntu-Server mit Domain `ics.example.de`.

View File

@@ -8,9 +8,11 @@ import sys
import tempfile import tempfile
from pathlib import Path from pathlib import Path
import os import os
import secrets
from fastapi import FastAPI, File, Form, Request, UploadFile from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, UploadFile, status
from fastapi.responses import FileResponse, HTMLResponse from fastapi.responses import FileResponse, HTMLResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.background import BackgroundTask from starlette.background import BackgroundTask
@@ -22,10 +24,28 @@ from pdf_to_ics import create_ics_from_dienstplan, extract_dienstplan_data
app = FastAPI(title="PDF zu ICS Web") app = FastAPI(title="PDF zu ICS Web")
templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates")) templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
security = HTTPBasic()
def require_auth(credentials: HTTPBasicCredentials = Depends(security)):
expected_user = os.getenv("WEB_AUTH_USER")
expected_password = os.getenv("WEB_AUTH_PASSWORD")
if not expected_user or not expected_password:
return
valid_user = secrets.compare_digest(credentials.username, expected_user)
valid_password = secrets.compare_digest(credentials.password, expected_password)
if not (valid_user and valid_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Unauthorized",
headers={"WWW-Authenticate": "Basic"},
)
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
def index(request: Request): def index(request: Request, _: None = Depends(require_auth)):
return templates.TemplateResponse( return templates.TemplateResponse(
"index.html", "index.html",
{ {
@@ -41,6 +61,7 @@ async def convert_pdf(
file: UploadFile = File(...), file: UploadFile = File(...),
exclude_rest: bool = Form(False), exclude_rest: bool = Form(False),
exclude_vacation: bool = Form(False), exclude_vacation: bool = Form(False),
_: None = Depends(require_auth),
): ):
filename = file.filename or "dienstplan.pdf" filename = file.filename or "dienstplan.pdf"
if not filename.lower().endswith(".pdf"): if not filename.lower().endswith(".pdf"):
@@ -107,5 +128,5 @@ async def convert_pdf(
@app.get("/health") @app.get("/health")
def health(): def health(_: None = Depends(require_auth)):
return {"status": "ok"} return {"status": "ok"}