changes to the Authentik integration

This commit is contained in:
Christian Krakau-Louis
2025-03-25 14:45:45 +01:00
parent 8aa667fd48
commit 5005f65f17
2 changed files with 63 additions and 18 deletions
+19 -4
View File
@@ -1,9 +1,10 @@
# app/auth.py # app/auth.py
import os import os
from functools import wraps
from authlib.integrations.starlette_client import OAuth from authlib.integrations.starlette_client import OAuth
from starlette.config import Config from starlette.config import Config
from starlette.middleware.sessions import SessionMiddleware from fastapi import APIRouter, Request, status
from fastapi import APIRouter, Request
from starlette.responses import RedirectResponse from starlette.responses import RedirectResponse
config = Config(".env") config = Config(".env")
@@ -26,19 +27,31 @@ def get_current_user(request: Request):
def require_login(func): def require_login(func):
"""Decorator to require login for particular endpoints.""" """Decorator to require login for particular endpoints."""
@wraps(func)
async def wrapper(request: Request, *args, **kwargs): async def wrapper(request: Request, *args, **kwargs):
if not request.session.get("user"): if not request.session.get("user"):
return RedirectResponse(url="/login") # Not logged in => redirect to /login
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
# User is logged in => proceed
return await func(request, *args, **kwargs) return await func(request, *args, **kwargs)
return wrapper return wrapper
@router.get("/login") @router.get("/login")
async def login(request: Request): async def login(request: Request):
"""
Initiate Authentik login flow.
This calls oauth.authentik.authorize_redirect(...)
The resulting redirect_uri must match what's in Authentik's application config.
"""
redirect_uri = request.url_for("auth") redirect_uri = request.url_for("auth")
return await oauth.authentik.authorize_redirect(request, redirect_uri) return await oauth.authentik.authorize_redirect(request, redirect_uri)
@router.get("/auth") @router.get("/auth")
async def auth(request: Request): async def auth(request: Request):
"""
Authentik callback endpoint. Exchanges code for token,
puts user info in session, and redirects to /ui or wherever you want.
"""
token = await oauth.authentik.authorize_access_token(request) token = await oauth.authentik.authorize_access_token(request)
userinfo = token.get("userinfo") userinfo = token.get("userinfo")
request.session["user"] = dict(userinfo) request.session["user"] = dict(userinfo)
@@ -46,11 +59,13 @@ async def auth(request: Request):
@router.get("/logout") @router.get("/logout")
async def logout(request: Request): async def logout(request: Request):
"""Clears session and redirects home."""
request.session.pop("user", None) request.session.pop("user", None)
return RedirectResponse(url="/") return RedirectResponse(url="/")
@router.get("/private") @router.get("/private")
@require_login @require_login
async def private_page(request: Request): async def private_page(request: Request):
user = request.session.get("user") """A protected endpoint that requires login."""
user = request.session.get("user") # e.g. {"email": "...", ...}
return {"message": f"This is a protected page. Hello {user['email']}!"} return {"message": f"This is a protected page. Hello {user['email']}!"}
+44 -14
View File
@@ -1,8 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
from fastapi import FastAPI, HTTPException, UploadFile, File from fastapi import FastAPI, HTTPException, UploadFile, File
from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.sessions import SessionMiddleware
from starlette.config import Config from starlette.config import Config
from starlette.middleware.trustedhost import TrustedHostMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from app.config import settings from app.config import settings
from app.tasks.upload_to_s3 import upload_to_s3 from app.tasks.upload_to_s3 import upload_to_s3
@@ -15,14 +18,27 @@ from app.auth import router as auth_router
# Load configuration from .env for the session key # Load configuration from .env for the session key
config = Config(".env") config = Config(".env")
SESSION_SECRET = config("SESSION_SECRET", default="YOUR_DEFAULT_SESSION_SECRET_MUST_BE_32_CHARS_OR_MORE") SESSION_SECRET = config(
"SESSION_SECRET",
default="YOUR_DEFAULT_SESSION_SECRET_MUST_BE_32_CHARS_OR_MORE"
)
app = FastAPI(title="Document Processing API") app = FastAPI(title="Document Processing API")
# 1) Session Middleware (for request.session to work)
# Add session middleware (needed for storing user sessions)
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET) app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)
# 2) Respect the X-Forwarded-* headers from Traefik
# so your request.url_for(...) uses https://docparse.hosterra.net
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
# 3) (Optional but recommended) Restrict valid hosts:
app.add_middleware(TrustedHostMiddleware, allowed_hosts=[
"docparse.hosterra.net",
"localhost",
"127.0.0.1"
])
@app.get("/") @app.get("/")
def root(): def root():
return {"message": "Document Processing API"} return {"message": "Document Processing API"}
@@ -37,7 +53,9 @@ def process(file_path: str):
file_path = os.path.join(settings.workdir, file_path) file_path = os.path.join(settings.workdir, file_path)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=400, detail=f"File {file_path} not found.") raise HTTPException(
status_code=400, detail=f"File {file_path} not found."
)
task = upload_to_s3.delay(file_path) task = upload_to_s3.delay(file_path)
return {"task_id": task.id, "status": "queued"} return {"task_id": task.id, "status": "queued"}
@@ -47,7 +65,9 @@ def send_to_dropbox(file_path: str):
if not os.path.isabs(file_path): if not os.path.isabs(file_path):
file_path = os.path.join(settings.workdir, 'processed', file_path) file_path = os.path.join(settings.workdir, 'processed', file_path)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=400, detail=f"File {file_path} not found.") raise HTTPException(
status_code=400, detail=f"File {file_path} not found."
)
task = upload_to_dropbox.delay(file_path) task = upload_to_dropbox.delay(file_path)
return {"task_id": task.id, "status": "queued"} return {"task_id": task.id, "status": "queued"}
@@ -56,7 +76,9 @@ def send_to_paperless(file_path: str):
if not os.path.isabs(file_path): if not os.path.isabs(file_path):
file_path = os.path.join(settings.workdir, 'processed', file_path) file_path = os.path.join(settings.workdir, 'processed', file_path)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=400, detail=f"File {file_path} not found.") raise HTTPException(
status_code=400, detail=f"File {file_path} not found."
)
task = upload_to_paperless.delay(file_path) task = upload_to_paperless.delay(file_path)
return {"task_id": task.id, "status": "queued"} return {"task_id": task.id, "status": "queued"}
@@ -65,7 +87,9 @@ def send_to_nextcloud(file_path: str):
if not os.path.isabs(file_path): if not os.path.isabs(file_path):
file_path = os.path.join(settings.workdir, 'processed', file_path) file_path = os.path.join(settings.workdir, 'processed', file_path)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=400, detail=f"File {file_path} not found.") raise HTTPException(
status_code=400, detail=f"File {file_path} not found."
)
task = upload_to_nextcloud.delay(file_path) task = upload_to_nextcloud.delay(file_path)
return {"task_id": task.id, "status": "queued"} return {"task_id": task.id, "status": "queued"}
@@ -78,7 +102,9 @@ def send_to_all_destinations_endpoint(file_path: str):
file_path = os.path.join(settings.workdir, 'processed', file_path) file_path = os.path.join(settings.workdir, 'processed', file_path)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=400, detail=f"File {file_path} not found.") raise HTTPException(
status_code=400, detail=f"File {file_path} not found."
)
task = send_to_all_destinations.delay(file_path) task = send_to_all_destinations.delay(file_path)
return {"task_id": task.id, "status": "queued", "file_path": file_path} return {"task_id": task.id, "status": "queued", "file_path": file_path}
@@ -86,12 +112,13 @@ def send_to_all_destinations_endpoint(file_path: str):
@app.post("/processall") @app.post("/processall")
def process_all_pdfs_in_workdir(): def process_all_pdfs_in_workdir():
""" """
Finds all .pdf files in <workdir>/processed Finds all .pdf files in <workdir> and enqueues them for upload_to_s3.
and enqueues them for upload_to_s3.
""" """
target_dir = settings.workdir target_dir = settings.workdir
if not os.path.exists(target_dir): if not os.path.exists(target_dir):
raise HTTPException(status_code=400, detail=f"Directory {target_dir} does not exist.") raise HTTPException(
status_code=400, detail=f"Directory {target_dir} does not exist."
)
pdf_files = [] pdf_files = []
for filename in os.listdir(target_dir): for filename in os.listdir(target_dir):
@@ -99,12 +126,11 @@ def process_all_pdfs_in_workdir():
pdf_files.append(filename) pdf_files.append(filename)
if not pdf_files: if not pdf_files:
return {"message": "No PDF files found in processed directory."} return {"message": "No PDF files found in that directory."}
task_ids = [] task_ids = []
for pdf in pdf_files: for pdf in pdf_files:
file_path = os.path.join(target_dir, pdf) file_path = os.path.join(target_dir, pdf)
from app.tasks.upload_to_s3 import upload_to_s3
task = upload_to_s3.delay(file_path) task = upload_to_s3.delay(file_path)
task_ids.append(task.id) task_ids.append(task.id)
@@ -116,6 +142,7 @@ def process_all_pdfs_in_workdir():
@app.post("/ui-upload") @app.post("/ui-upload")
async def ui_upload(file: UploadFile = File(...)): async def ui_upload(file: UploadFile = File(...)):
"""Endpoint to accept a user-uploaded file and enqueue it to S3."""
workdir = "/workdir" workdir = "/workdir"
target_path = os.path.join(workdir, file.filename) target_path = os.path.join(workdir, file.filename)
try: try:
@@ -123,7 +150,10 @@ async def ui_upload(file: UploadFile = File(...)):
content = await file.read() content = await file.read()
f.write(content) f.write(content)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save file: {e}") raise HTTPException(
status_code=500,
detail=f"Failed to save file: {e}"
)
task = upload_to_s3.delay(target_path) task = upload_to_s3.delay(target_path)
return {"task_id": task.id, "status": "queued"} return {"task_id": task.id, "status": "queued"}