fix: Address high-severity bandit issues

This commit is contained in:
DSS
2025-12-11 07:13:06 -03:00
parent bcb4475744
commit 5b2a328dd1
167 changed files with 7051 additions and 7168 deletions

View File

@@ -1,5 +1,5 @@
"""
Design System Server (DSS) - FastAPI Server
Design System Server (DSS) - FastAPI Server.
Portable API server providing:
- Project management (CRUD)
@@ -16,37 +16,43 @@ Modes:
Uses SQLite for persistence, integrates with Figma tools.
"""
import asyncio
import subprocess
import json
import os
from pathlib import Path
from typing import Optional, List, Dict, Any
from datetime import datetime
from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import subprocess
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional
from config import config
from storage.json_store import (
Projects, Components, SyncHistory, ActivityLog, Teams, Cache, get_stats
)
from fastapi import BackgroundTasks, FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from figma.figma_tools import FigmaToolSuite
from pydantic import BaseModel
from storage.json_store import (
ActivityLog,
Cache,
Components,
Projects,
SyncHistory,
Teams,
get_stats,
)
sys.path.insert(0, str(Path(__file__).parent.parent))
# === Runtime Configuration ===
class RuntimeConfig:
"""
Runtime configuration that can be modified from the dashboard.
Persists to .dss/runtime-config.json for portability.
"""
def __init__(self):
self.config_path = Path(__file__).parent.parent.parent / ".dss" / "runtime-config.json"
self.config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -71,7 +77,7 @@ class RuntimeConfig:
"token_sync": True,
"code_gen": True,
"ai_advisor": False,
}
},
}
def _save(self):
@@ -114,6 +120,7 @@ runtime_config = RuntimeConfig()
# === Service Discovery ===
class ServiceDiscovery:
"""Discovers and manages companion services."""
@@ -136,13 +143,13 @@ class ServiceDiscovery:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5)
result = sock.connect_ex(('127.0.0.1', port))
result = sock.connect_ex(("127.0.0.1", port))
sock.close()
if result == 0:
discovered[service] = {
"running": True,
"port": port,
"url": f"http://localhost:{port}"
"url": f"http://localhost:{port}",
}
break
except:
@@ -164,11 +171,7 @@ class ServiceDiscovery:
try:
async with httpx.AsyncClient(timeout=2.0) as client:
resp = await client.get(url)
return {
"running": resp.status_code == 200,
"url": url,
"port": port
}
return {"running": resp.status_code == 200, "url": url, "port": port}
except:
return {"running": False, "url": url, "port": port}
@@ -178,7 +181,7 @@ class ServiceDiscovery:
app = FastAPI(
title="Design System Server (DSS)",
description="API for design system management and Figma integration",
version="1.0.0"
version="1.0.0",
)
app.add_middleware(
@@ -195,31 +198,38 @@ if UI_DIR.exists():
app.mount("/admin-ui", StaticFiles(directory=str(UI_DIR), html=True), name="admin-ui")
# Initialize Figma tools
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
figma_suite = FigmaToolSuite(
output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output")
)
# === Request/Response Models ===
class ProjectCreate(BaseModel):
name: str
description: str = ""
figma_file_key: str = ""
class ProjectUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
figma_file_key: Optional[str] = None
status: Optional[str] = None
class FigmaExtractRequest(BaseModel):
file_key: str
format: str = "css"
class FigmaSyncRequest(BaseModel):
file_key: str
target_path: str
format: str = "css"
class TeamCreate(BaseModel):
name: str
description: str = ""
@@ -227,10 +237,12 @@ class TeamCreate(BaseModel):
# === Root & Health ===
@app.get("/")
async def root():
"""Redirect to Admin UI dashboard."""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/admin-ui/index.html")
@@ -243,30 +255,30 @@ async def health():
"version": "1.0.0",
"timestamp": datetime.utcnow().isoformat() + "Z",
"figma_mode": figma_suite.mode,
"config": config.summary()
"config": config.summary(),
}
@app.get("/api/stats")
async def get_statistics():
"""Get database and system statistics."""
db_stats = get_stats()
return {
"database": db_stats,
"figma": {
"mode": figma_suite.mode,
"configured": config.figma.is_configured
}
"figma": {"mode": figma_suite.mode, "configured": config.figma.is_configured},
}
# === Projects ===
@app.get("/api/projects")
async def list_projects(status: Optional[str] = None):
"""List all projects."""
projects = Projects.list(status=status)
return projects
@app.get("/api/projects/{project_id}")
async def get_project(project_id: str):
"""Get a specific project."""
@@ -275,6 +287,7 @@ async def get_project(project_id: str):
raise HTTPException(status_code=404, detail="Project not found")
return project
@app.post("/api/projects")
async def create_project(project: ProjectCreate):
"""Create a new project."""
@@ -283,17 +296,18 @@ async def create_project(project: ProjectCreate):
id=project_id,
name=project.name,
description=project.description,
figma_file_key=project.figma_file_key
figma_file_key=project.figma_file_key,
)
ActivityLog.log(
action="project_created",
entity_type="project",
entity_id=project_id,
project_id=project_id,
details={"name": project.name}
details={"name": project.name},
)
return created
@app.put("/api/projects/{project_id}")
async def update_project(project_id: str, update: ProjectUpdate):
"""Update a project."""
@@ -311,25 +325,23 @@ async def update_project(project_id: str, update: ProjectUpdate):
entity_type="project",
entity_id=project_id,
project_id=project_id,
details=update_data
details=update_data,
)
return updated
@app.delete("/api/projects/{project_id}")
async def delete_project(project_id: str):
"""Delete a project."""
if not Projects.delete(project_id):
raise HTTPException(status_code=404, detail="Project not found")
ActivityLog.log(
action="project_deleted",
entity_type="project",
entity_id=project_id
)
ActivityLog.log(action="project_deleted", entity_type="project", entity_id=project_id)
return {"success": True}
# === Components ===
@app.get("/api/projects/{project_id}/components")
async def list_components(project_id: str):
"""List components for a project."""
@@ -340,6 +352,7 @@ async def list_components(project_id: str):
# === Figma Integration ===
@app.post("/api/figma/extract-variables")
async def extract_variables(request: FigmaExtractRequest, background_tasks: BackgroundTasks):
"""Extract design tokens from Figma file."""
@@ -348,12 +361,17 @@ async def extract_variables(request: FigmaExtractRequest, background_tasks: Back
ActivityLog.log(
action="figma_extract_variables",
entity_type="figma",
details={"file_key": request.file_key, "format": request.format, "count": result.get("tokens_count")}
details={
"file_key": request.file_key,
"format": request.format,
"count": result.get("tokens_count"),
},
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/extract-components")
async def extract_components(request: FigmaExtractRequest):
"""Extract components from Figma file."""
@@ -362,12 +380,13 @@ async def extract_components(request: FigmaExtractRequest):
ActivityLog.log(
action="figma_extract_components",
entity_type="figma",
details={"file_key": request.file_key, "count": result.get("components_count")}
details={"file_key": request.file_key, "count": result.get("components_count")},
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/extract-styles")
async def extract_styles(request: FigmaExtractRequest):
"""Extract styles from Figma file."""
@@ -377,20 +396,28 @@ async def extract_styles(request: FigmaExtractRequest):
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/sync-tokens")
async def sync_tokens(request: FigmaSyncRequest):
"""Sync tokens from Figma to target path."""
try:
result = await figma_suite.sync_tokens(request.file_key, request.target_path, request.format)
result = await figma_suite.sync_tokens(
request.file_key, request.target_path, request.format
)
ActivityLog.log(
action="figma_sync_tokens",
entity_type="figma",
details={"file_key": request.file_key, "target": request.target_path, "synced": result.get("tokens_synced")}
details={
"file_key": request.file_key,
"target": request.target_path,
"synced": result.get("tokens_synced"),
},
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/validate")
async def validate_components(request: FigmaExtractRequest):
"""Validate components against design system rules."""
@@ -400,6 +427,7 @@ async def validate_components(request: FigmaExtractRequest):
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/generate-code")
async def generate_code(file_key: str, component_name: str, framework: str = "webcomponent"):
"""Generate component code from Figma."""
@@ -412,6 +440,7 @@ async def generate_code(file_key: str, component_name: str, framework: str = "we
# === Discovery ===
@app.get("/api/discovery")
async def run_discovery(path: str = "."):
"""Run project discovery."""
@@ -419,10 +448,7 @@ async def run_discovery(path: str = "."):
try:
result = subprocess.run(
[str(script_path), path],
capture_output=True,
text=True,
timeout=30
[str(script_path), path], capture_output=True, text=True, timeout=30
)
if result.returncode == 0:
return json.loads(result.stdout)
@@ -433,22 +459,19 @@ async def run_discovery(path: str = "."):
except json.JSONDecodeError:
return {"raw_output": result.stdout}
@app.get("/api/discovery/ports")
async def discover_ports():
"""Discover listening ports and services."""
script_path = Path(__file__).parent.parent / "discovery" / "discover-ports.sh"
try:
result = subprocess.run(
[str(script_path)],
capture_output=True,
text=True,
timeout=10
)
result = subprocess.run([str(script_path)], capture_output=True, text=True, timeout=10)
return json.loads(result.stdout)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/discovery/env")
async def discover_env(path: str = "."):
"""Analyze environment configuration."""
@@ -456,10 +479,7 @@ async def discover_env(path: str = "."):
try:
result = subprocess.run(
[str(script_path), path],
capture_output=True,
text=True,
timeout=10
[str(script_path), path], capture_output=True, text=True, timeout=10
)
return json.loads(result.stdout)
except Exception as e:
@@ -468,24 +488,30 @@ async def discover_env(path: str = "."):
# === Activity & Sync History ===
@app.get("/api/activity")
async def get_activity(limit: int = Query(default=50, le=100)):
"""Get recent activity log."""
return ActivityLog.recent(limit=limit)
@app.get("/api/sync-history")
async def get_sync_history(project_id: Optional[str] = None, limit: int = Query(default=20, le=100)):
async def get_sync_history(
project_id: Optional[str] = None, limit: int = Query(default=20, le=100)
):
"""Get sync history."""
return SyncHistory.recent(project_id=project_id, limit=limit)
# === Teams ===
@app.get("/api/teams")
async def list_teams():
"""List all teams."""
return Teams.list()
@app.post("/api/teams")
async def create_team(team: TeamCreate):
"""Create a new team."""
@@ -493,6 +519,7 @@ async def create_team(team: TeamCreate):
created = Teams.create(team_id, team.name, team.description)
return created
@app.get("/api/teams/{team_id}")
async def get_team(team_id: str):
"""Get a specific team."""
@@ -504,12 +531,14 @@ async def get_team(team_id: str):
# === Cache Management ===
@app.post("/api/cache/clear")
async def clear_cache():
"""Clear expired cache entries."""
count = Cache.clear_expired()
return {"cleared": count}
@app.delete("/api/cache")
async def purge_cache():
"""Purge all cache entries."""
@@ -519,6 +548,7 @@ async def purge_cache():
# === Configuration Management ===
class ConfigUpdate(BaseModel):
mode: Optional[str] = None
figma_token: Optional[str] = None
@@ -532,7 +562,7 @@ async def get_config():
return {
"config": runtime_config.get(),
"env": config.summary(),
"mode": runtime_config.get("mode")
"mode": runtime_config.get("mode"),
}
@@ -548,11 +578,13 @@ async def update_config(update: ConfigUpdate):
runtime_config.set_figma_token(update.figma_token)
# Reinitialize Figma tools with new token
global figma_suite
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
figma_suite = FigmaToolSuite(
output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output")
)
ActivityLog.log(
action="figma_token_updated",
entity_type="config",
details={"configured": bool(update.figma_token)}
details={"configured": bool(update.figma_token)},
)
if update.services:
@@ -564,9 +596,7 @@ async def update_config(update: ConfigUpdate):
if updates:
runtime_config.update(updates)
ActivityLog.log(
action="config_updated",
entity_type="config",
details={"keys": list(updates.keys())}
action="config_updated", entity_type="config", details={"keys": list(updates.keys())}
)
return runtime_config.get()
@@ -586,7 +616,7 @@ async def get_figma_config():
"sync_tokens": True,
"validate": True,
"generate_code": True,
}
},
}
@@ -600,18 +630,16 @@ async def test_figma_connection():
# Test with a minimal API call
import httpx
token = runtime_config._data["figma"]["token"]
async with httpx.AsyncClient() as client:
resp = await client.get(
"https://api.figma.com/v1/me",
headers={"X-Figma-Token": token}
)
resp = await client.get("https://api.figma.com/v1/me", headers={"X-Figma-Token": token})
if resp.status_code == 200:
user = resp.json()
return {
"success": True,
"user": user.get("email", "connected"),
"handle": user.get("handle")
"handle": user.get("handle"),
}
else:
return {"success": False, "error": f"API returned {resp.status_code}"}
@@ -621,6 +649,7 @@ async def test_figma_connection():
# === Service Discovery ===
@app.get("/api/services")
async def list_services():
"""List configured and discovered services."""
@@ -630,7 +659,7 @@ async def list_services():
return {
"configured": configured,
"discovered": discovered,
"storybook": await ServiceDiscovery.check_storybook()
"storybook": await ServiceDiscovery.check_storybook(),
}
@@ -645,7 +674,7 @@ async def configure_service(service_name: str, config_data: Dict[str, Any]):
action="service_configured",
entity_type="service",
entity_id=service_name,
details={"keys": list(config_data.keys())}
details={"keys": list(config_data.keys())},
)
return services[service_name]
@@ -659,6 +688,7 @@ async def get_storybook_status():
# === DSS Mode ===
@app.get("/api/mode")
async def get_mode():
"""Get current DSS mode."""
@@ -666,7 +696,7 @@ async def get_mode():
return {
"mode": mode,
"description": "Local dev companion" if mode == "local" else "Remote design system server",
"features": runtime_config.get("features")
"features": runtime_config.get("features"),
}
@@ -677,11 +707,7 @@ async def set_mode(mode: str):
raise HTTPException(status_code=400, detail="Mode must be 'local' or 'server'")
runtime_config.set("mode", mode)
ActivityLog.log(
action="mode_changed",
entity_type="config",
details={"mode": mode}
)
ActivityLog.log(action="mode_changed", entity_type="config", details={"mode": mode})
return {"mode": mode, "success": True}
@@ -704,7 +730,8 @@ if __name__ == "__main__":
host = os.getenv("HOST", "0.0.0.0")
url = f"http://{host}:{port}"
print(f"""
print(
f"""
╔═══════════════════════════════════════════════════════════════╗
║ Design System Server (DSS) - Portable Server ║
╠═══════════════════════════════════════════════════════════════╣
@@ -714,11 +741,7 @@ if __name__ == "__main__":
║ Environment: {config.server.env:^47}
║ Figma Mode: {figma_suite.mode:^47}
╚═══════════════════════════════════════════════════════════════╝
""")
uvicorn.run(
"server:app",
host=host,
port=port,
reload=config.server.env == "development"
"""
)
uvicorn.run("server:app", host=host, port=port, reload=config.server.env == "development")