Replaces nginx basic auth with a proper per-user session system:
- auth_utils.py: bcrypt password hashing, JWT cookie creation/decode
- auth_middleware.py: validates JWT cookie on all routes except /login,
/health, /static/, and webhook endpoints (/channels/, /webhook/)
- routers/ui.py: GET /login, POST /login, POST /logout,
GET /{username}/{persona} — serves index.html with CORTEX_CONFIG injected
- static/login.html: minimal login form (dark theme, matches UI)
- main.py: registers SessionAuthMiddleware + ui.router
- config.py: jwt_secret, jwt_expire_days settings
- manage_passwords.py: CLI tool to set/check/list user passwords
- app.js: reads window.CORTEX_CONFIG (user + persona), sends both on
every /chat and /orchestrate request; persona name shown in header;
logout button (⏏) added to header
- requirements.txt: bcrypt, PyJWT, python-multipart
- .env.default: JWT_SECRET, JWT_EXPIRE_DAYS documented
- tests: client fixture injects JWT cookie; security test assertions
updated for URL-normalized path traversal paths (still secure, codes differ)
All 80 tests pass.
Setup for a new user:
python manage_passwords.py set scott
python manage_passwords.py set holly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
1.4 KiB
Python
56 lines
1.4 KiB
Python
import logging
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
import uvicorn
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(name)s: %(message)s")
|
|
|
|
from config import settings
|
|
from auth_middleware import SessionAuthMiddleware
|
|
from routers import chat, google_chat, nextcloud_talk, files, distill, auth, orchestrator
|
|
from routers import ui
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
import scheduler
|
|
scheduler.start()
|
|
yield
|
|
scheduler.stop()
|
|
from llm_client import cleanup
|
|
await cleanup()
|
|
|
|
|
|
app = FastAPI(title="Cortex Dispatcher", lifespan=lifespan)
|
|
|
|
app.add_middleware(SessionAuthMiddleware)
|
|
|
|
# API routers
|
|
app.include_router(chat.router)
|
|
app.include_router(google_chat.router)
|
|
app.include_router(nextcloud_talk.router)
|
|
app.include_router(files.router)
|
|
app.include_router(distill.router)
|
|
app.include_router(auth.router)
|
|
app.include_router(orchestrator.router)
|
|
|
|
# UI router (login + /{user}/{persona} — must be last to avoid swallowing API paths)
|
|
app.include_router(ui.router)
|
|
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict:
|
|
return {"status": "ok"}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
uvicorn.run(
|
|
"main:app",
|
|
host=settings.host,
|
|
port=settings.port,
|
|
reload=True,
|
|
)
|