feat(SEC-001): protect workspace bootstrap with turnstile
This commit is contained in:
25
app/main.py
25
app/main.py
@@ -10,14 +10,15 @@ from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi import FastAPI, Form, Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.responses import RedirectResponse, Response
|
||||
from nicegui import ui # type: ignore[attr-defined]
|
||||
|
||||
import app.pages # noqa: F401
|
||||
from app.api.routes import router as api_router
|
||||
from app.models.workspace import WORKSPACE_COOKIE, get_workspace_repository
|
||||
from app.services import turnstile as turnstile_service
|
||||
from app.services.cache import CacheService
|
||||
from app.services.data_service import DataService
|
||||
from app.services.runtime import set_data_service
|
||||
@@ -37,11 +38,14 @@ class Settings:
|
||||
websocket_interval_seconds: int = 5
|
||||
nicegui_mount_path: str = "/"
|
||||
nicegui_storage_secret: str = "vault-dash-dev-secret"
|
||||
turnstile_site_key: str = ""
|
||||
turnstile_secret_key: str = ""
|
||||
|
||||
@classmethod
|
||||
def load(cls) -> Settings:
|
||||
cls._load_dotenv()
|
||||
origins = os.getenv("CORS_ORIGINS", "*")
|
||||
turnstile = turnstile_service.load_turnstile_settings()
|
||||
return cls(
|
||||
app_name=os.getenv("APP_NAME", cls.app_name),
|
||||
environment=os.getenv("APP_ENV", os.getenv("ENVIRONMENT", cls.environment)),
|
||||
@@ -52,6 +56,8 @@ class Settings:
|
||||
websocket_interval_seconds=int(os.getenv("WEBSOCKET_INTERVAL_SECONDS", cls.websocket_interval_seconds)),
|
||||
nicegui_mount_path=os.getenv("NICEGUI_MOUNT_PATH", cls.nicegui_mount_path),
|
||||
nicegui_storage_secret=os.getenv("NICEGUI_STORAGE_SECRET", cls.nicegui_storage_secret),
|
||||
turnstile_site_key=turnstile.site_key,
|
||||
turnstile_secret_key=turnstile.secret_key,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -149,7 +155,20 @@ async def health(request: Request) -> dict[str, Any]:
|
||||
|
||||
|
||||
@app.get("/workspaces/bootstrap", tags=["workspace"])
|
||||
async def bootstrap_workspace() -> RedirectResponse:
|
||||
async def bootstrap_workspace_redirect() -> RedirectResponse:
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
|
||||
@app.post("/workspaces/bootstrap", tags=["workspace"])
|
||||
async def bootstrap_workspace(
|
||||
request: Request,
|
||||
turnstile_response: str = Form(alias="cf-turnstile-response", default=""),
|
||||
) -> Response:
|
||||
if not turnstile_service.verify_turnstile_token(
|
||||
turnstile_response, request.client.host if request.client else None
|
||||
):
|
||||
return RedirectResponse(url="/?captcha_error=1", status_code=303)
|
||||
|
||||
workspace_id = get_workspace_repository().create_workspace_id()
|
||||
response = RedirectResponse(url=f"/{workspace_id}", status_code=303)
|
||||
response.set_cookie(
|
||||
|
||||
Reference in New Issue
Block a user