feat(web): Gitea OIDC auth, signed session, allowlist (W5)
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
"""FastAPI application factory for the read-only web dashboard (W4).
|
||||
"""FastAPI application factory for the web dashboard (W4 read-only + W5 auth).
|
||||
|
||||
**In-process only**: routes call ``operations/`` + ``backends.load_backend``
|
||||
directly and render Jinja2 templates. They never shell out to the CLI (see
|
||||
``CLAUDE.md`` / the web-UI plan's architecture rules).
|
||||
|
||||
**No auth in this step** (that is W5) -- the app binds ``127.0.0.1`` by
|
||||
default via :class:`~ci_orchestrator.config.WebConfig`; ``create_app`` itself
|
||||
never chooses the bind address, that is ``commands/web.py``'s job.
|
||||
**Auth (W5)**: the four data routes below are protected by
|
||||
:func:`~ci_orchestrator.web.auth.require_user`. Auth is ACTIVE iff
|
||||
``config.web.oidc is not None``; when inactive (the W4 default -- bare
|
||||
localhost dev), ``require_user`` is a no-op and these routes behave exactly
|
||||
as in W4. ``create_app`` itself never chooses the bind address, that is
|
||||
``commands/web.py``'s job.
|
||||
|
||||
**Backend may be unavailable**: :func:`get_backend` is a FastAPI dependency
|
||||
that swallows :class:`~ci_orchestrator.backends.errors.BackendError` (raised
|
||||
@@ -29,6 +32,7 @@ from ci_orchestrator.backends.errors import BackendError
|
||||
from ci_orchestrator.operations.cache import CacheStatus, cargo_cache_status
|
||||
from ci_orchestrator.operations.host import DiskStatus, RunnerStatus, disk_status, runner_status
|
||||
from ci_orchestrator.operations.vms import LiveVm, list_live_vms
|
||||
from ci_orchestrator.web.auth import configure_auth, require_user
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from ci_orchestrator.backends.protocol import VmBackend
|
||||
@@ -156,11 +160,16 @@ def host_partial(request: Request) -> HTMLResponse:
|
||||
|
||||
|
||||
def create_app(config: Config) -> FastAPI:
|
||||
"""Build the read-only dashboard app.
|
||||
"""Build the dashboard app.
|
||||
|
||||
W4 scope: no auth, read-only routes only. Binding to a concrete address
|
||||
(always ``127.0.0.1`` by default -- never ``0.0.0.0``) is
|
||||
Read-only routes only (mutating actions are W6). Binding to a concrete
|
||||
address (always ``127.0.0.1`` by default -- never ``0.0.0.0``) is
|
||||
``commands/web.py``'s job, not this factory's.
|
||||
|
||||
All data routes are guarded by ``require_user`` (W5): when
|
||||
``config.web.oidc is None`` that dependency is a no-op (open mode, W4
|
||||
parity); when set, unauthenticated requests redirect to ``/login``.
|
||||
``/healthz`` and ``/static`` stay unprotected either way.
|
||||
"""
|
||||
app = FastAPI(title="ci-orchestrator web")
|
||||
app.state.config = config
|
||||
@@ -168,10 +177,22 @@ def create_app(config: Config) -> FastAPI:
|
||||
app.mount("/static", StaticFiles(directory=str(_WEB_DIR / "static")), name="static")
|
||||
|
||||
app.get("/healthz")(healthz)
|
||||
app.get("/", response_class=HTMLResponse)(dashboard)
|
||||
app.get("/vms", response_class=HTMLResponse)(vms_partial)
|
||||
app.get("/cache", response_class=HTMLResponse)(cache_partial)
|
||||
app.get("/host", response_class=HTMLResponse)(host_partial)
|
||||
app.get("/", response_class=HTMLResponse, dependencies=[Depends(require_user)])(dashboard)
|
||||
app.get("/vms", response_class=HTMLResponse, dependencies=[Depends(require_user)])(
|
||||
vms_partial
|
||||
)
|
||||
app.get("/cache", response_class=HTMLResponse, dependencies=[Depends(require_user)])(
|
||||
cache_partial
|
||||
)
|
||||
app.get("/host", response_class=HTMLResponse, dependencies=[Depends(require_user)])(
|
||||
host_partial
|
||||
)
|
||||
|
||||
# SessionMiddleware (needed by the OAuth flow's state/nonce) is added
|
||||
# here, before the app is ever handed to a server -- Starlette builds
|
||||
# the middleware stack lazily on first request, so this is safe
|
||||
# regardless of where the route registrations above sit.
|
||||
configure_auth(app, config)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
Reference in New Issue
Block a user