feat(userbot): prereqs at startup, SPA path guard, provision lock
ci / lint-prettier (push) Failing after 8s
ci / lint-ruff (push) Successful in 4s
ci / lint-yaml (push) Failing after 7s
ci / lint-dockerfiles (push) Successful in 4s
ci / validate (push) Successful in 5s
deploy / redeploy (push) Failing after 0s
ci / build (push) Has been skipped
ci / deploy-userbot-panel (push) Has been skipped

- ensure_prerequisites runs on startup, not per-request; kube config
  errors surface as 503 PanelError
- serialize provisioning with a lock; drop per-endpoint prereq checks
- guard SPA fallback against path traversal (relative_to)
- add backend tests for auth flow, k8s service, spa routing; ci comment
  for legacy userbot deployments
This commit is contained in:
forust committed 2026-09-06 20:39:12 +02:00
1 parent 861d89d36a
commit d9f1c8325a
10 files changed
+191 -34

No files matched your search

+10 -3
View File
@@ -27,6 +27,10 @@ from .models import (
async def lifespan(app: FastAPI):
app.state.kubernetes = KubernetesService(settings)
app.state.telegram = TelegramAuthService(settings.auth_ttl_seconds)
try:
app.state.kubernetes.ensure_prerequisites()
except PanelError as exc:
print(f"WARNING: userbot prerequisites check failed at startup: {exc.detail}")
yield
await app.state.telegram.close()
@@ -136,7 +140,6 @@ async def auth_phone_start(
request: Request,
) -> AuthResult:
service = kube(request)
service.ensure_prerequisites()
service.assert_available(payload.instance_id)
flow_id = await telegram(request).start_phone(payload)
return AuthResult(status="code_required", flow_id=flow_id)
@@ -172,7 +175,6 @@ async def auth_string_session(
request: Request,
) -> AuthResult:
service = kube(request)
service.ensure_prerequisites()
service.assert_available(payload.instance_id)
authorized = await telegram(request).validate_string_session(payload)
instance = service.provision(authorized.account, authorized.session_string)
@@ -192,7 +194,12 @@ def index() -> FileResponse:
@app.get("/{path:path}", include_in_schema=False)
def spa_fallback(path: str) -> FileResponse:
root = static_dir.resolve()
candidate = (static_dir / path).resolve()
if candidate.is_file() and static_dir.resolve() in candidate.parents:
try:
candidate.relative_to(root)
except ValueError:
return FileResponse(static_dir / "index.html")
if candidate.is_file():
return FileResponse(candidate)
return FileResponse(static_dir / "index.html")