- app_state gains configured + setup_token_hash (migration 0025); existing installs (any users) are marked configured so they never see the wizard, a fresh DB starts in setup mode. - On first boot the app mints a one-time setup token and logs the wizard URL (only the hash is stored); the token gates the setup steps (added in 6c). - A middleware locks all /api + /auth routes (except /api/setup, /api/version, /healthz, static) to 503 until configured, with a DB-free cached fast-path once setup completes. - GET /api/setup/status tells the SPA whether to show the wizard.
32 lines
1.3 KiB
Python
32 lines
1.3 KiB
Python
"""First-run install wizard.
|
|
|
|
`GET /api/setup/status` is public (the SPA uses it to decide whether to show the wizard). Every
|
|
mutating step goes through `require_setup`: it rejects once the instance is configured AND requires
|
|
the one-time setup token (printed to the container logs at first boot, carried by the wizard as the
|
|
X-Setup-Token header). The wizard steps themselves are added in epic 6c.
|
|
"""
|
|
from fastapi import APIRouter, Depends, Header, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app import state
|
|
from app.db import get_db
|
|
|
|
router = APIRouter(prefix="/api/setup", tags=["setup"])
|
|
|
|
|
|
def require_setup(
|
|
x_setup_token: str | None = Header(default=None),
|
|
db: Session = Depends(get_db),
|
|
) -> Session:
|
|
"""Gate for setup steps. Returns the db session for the handler to reuse."""
|
|
if state.is_configured(db):
|
|
raise HTTPException(status_code=409, detail="This instance is already set up.")
|
|
if not state.check_setup_token(db, x_setup_token):
|
|
raise HTTPException(status_code=403, detail="Invalid or missing setup token.")
|
|
return db
|
|
|
|
|
|
@router.get("/status")
|
|
def setup_status(db: Session = Depends(get_db)) -> dict:
|
|
"""Public: whether the instance still needs first-run setup."""
|
|
return {"configured": state.is_configured(db)}
|