mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
The Windows GPU server has been the actual production home for these
services for some time, and the running code there has drifted ahead of
the repo. This sync pulls the live versions back into the repo so the
Windows box is no longer the only place those changes exist.
Pulled from C:\mana\services\* on mana-server-gpu (192.168.178.11):
mana-llm:
- src/main.py, src/config.py — small fixes (auth wiring, config tweaks)
- src/api_auth.py — NEW (cross-service GPU_API_KEY validator)
- service.pyw — Windows runner used by the ManaLLM scheduled task
(sets up logging redirect, loads .env, calls uvicorn)
mana-stt:
- app/main.py — substantial cleanup (684→392 lines), drops the
whisperx-as-separate-backend branching now that whisper_service.py
rolls whisperx in directly
- app/whisper_service.py — full CUDA + whisperx rewrite (158→358 lines)
- app/auth.py + external_auth.py — significantly expanded auth
- app/vram_manager.py — NEW (shared VRAM accounting helper)
- service.pyw — Windows runner with CUDA pre-init, FFmpeg PATH
injection, .env loading
- removed: app/whisper_service_cuda.py (folded into whisper_service.py)
- removed: app/whisperx_service.py (folded into whisper_service.py)
mana-tts:
- app/auth.py, external_auth.py — same auth expansion as stt
- app/f5_service.py, kokoro_service.py — Windows tweaks
- app/vram_manager.py — NEW (same shared helper as stt)
- service.pyw — Windows runner
mana-video-gen:
- service.pyw — Windows runner (no other changes; the .py code on the
GPU box is byte-identical to what's already in the repo)
The service.pyw files contain absolute Windows paths
(C:\mana\services\<svc>) and a hardcoded FFmpeg PATH for the tills user
profile. Kept as-is intentionally — they exist to be deployed to that
one machine and any abstraction layer would just hide what's actually
happening. Anyone redeploying to a different layout will need to edit
the path strings, which is a known and obvious change.
Mac-Mini infrastructure for these services (launchd plists, install
scripts, scripts/mac-mini/setup-{stt,tts}.sh, the Mac-flux2c image-gen
implementation) is still on disk and will be removed in a follow-up
commit, along with replacing mana-image-gen with the Windows
diffusers+CUDA implementation. This commit is just the live-code sync.
145 lines
5 KiB
Python
145 lines
5 KiB
Python
"""
|
|
External API Key Validation via mana-core-auth
|
|
|
|
When EXTERNAL_AUTH_ENABLED=true, API keys are validated against the
|
|
central mana-core-auth service. This allows users to create and manage
|
|
API keys from the mana.how web interface.
|
|
|
|
Results are cached for 5 minutes to reduce load on the auth service.
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
import logging
|
|
import httpx
|
|
from typing import Optional
|
|
from dataclasses import dataclass
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Configuration
|
|
EXTERNAL_AUTH_ENABLED = os.getenv("EXTERNAL_AUTH_ENABLED", "false").lower() == "true"
|
|
MANA_CORE_AUTH_URL = os.getenv("MANA_CORE_AUTH_URL", "http://localhost:3001")
|
|
API_KEY_CACHE_TTL = int(os.getenv("API_KEY_CACHE_TTL", "300")) # 5 minutes
|
|
EXTERNAL_AUTH_TIMEOUT = float(os.getenv("EXTERNAL_AUTH_TIMEOUT", "5.0")) # seconds
|
|
|
|
|
|
@dataclass
|
|
class ExternalValidationResult:
|
|
"""Result from external API key validation."""
|
|
valid: bool
|
|
user_id: Optional[str] = None
|
|
scopes: Optional[list] = None
|
|
rate_limit_requests: int = 60
|
|
rate_limit_window: int = 60
|
|
error: Optional[str] = None
|
|
cached_at: float = 0.0
|
|
|
|
|
|
# In-memory cache for validation results
|
|
# Key: API key, Value: ExternalValidationResult
|
|
_validation_cache: dict[str, ExternalValidationResult] = {}
|
|
|
|
|
|
def is_external_auth_enabled() -> bool:
|
|
"""Check if external authentication is enabled."""
|
|
return EXTERNAL_AUTH_ENABLED
|
|
|
|
|
|
def _get_cached_result(api_key: str) -> Optional[ExternalValidationResult]:
|
|
"""Get cached validation result if still valid."""
|
|
result = _validation_cache.get(api_key)
|
|
if result and (time.time() - result.cached_at) < API_KEY_CACHE_TTL:
|
|
return result
|
|
return None
|
|
|
|
|
|
def _cache_result(api_key: str, result: ExternalValidationResult):
|
|
"""Cache a validation result."""
|
|
result.cached_at = time.time()
|
|
_validation_cache[api_key] = result
|
|
|
|
# Clean up old entries periodically (keep cache size manageable)
|
|
if len(_validation_cache) > 1000:
|
|
now = time.time()
|
|
expired_keys = [
|
|
k for k, v in _validation_cache.items()
|
|
if (now - v.cached_at) >= API_KEY_CACHE_TTL
|
|
]
|
|
for k in expired_keys:
|
|
del _validation_cache[k]
|
|
|
|
|
|
async def validate_api_key_external(api_key: str, scope: str) -> Optional[ExternalValidationResult]:
|
|
"""
|
|
Validate an API key against mana-core-auth service.
|
|
|
|
Args:
|
|
api_key: The API key to validate (e.g., "sk_live_...")
|
|
scope: The required scope (e.g., "stt" or "tts")
|
|
|
|
Returns:
|
|
ExternalValidationResult if external auth is enabled and the key was validated.
|
|
None if external auth is disabled or the service is unavailable (fallback to local).
|
|
"""
|
|
if not EXTERNAL_AUTH_ENABLED:
|
|
return None
|
|
|
|
# Check cache first
|
|
cached = _get_cached_result(api_key)
|
|
if cached:
|
|
logger.debug(f"Using cached validation result for key prefix: {api_key[:12]}...")
|
|
# Check scope against cached result
|
|
if cached.valid and cached.scopes and scope not in cached.scopes:
|
|
return ExternalValidationResult(
|
|
valid=False,
|
|
error=f"API key does not have scope: {scope}",
|
|
)
|
|
return cached
|
|
|
|
# Call mana-core-auth validation endpoint
|
|
try:
|
|
async with httpx.AsyncClient(timeout=EXTERNAL_AUTH_TIMEOUT) as client:
|
|
response = await client.post(
|
|
f"{MANA_CORE_AUTH_URL}/api/v1/api-keys/validate",
|
|
json={"apiKey": api_key, "scope": scope},
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
result = ExternalValidationResult(
|
|
valid=data.get("valid", False),
|
|
user_id=data.get("userId"),
|
|
scopes=data.get("scopes", []),
|
|
rate_limit_requests=data.get("rateLimit", {}).get("requests", 60),
|
|
rate_limit_window=data.get("rateLimit", {}).get("window", 60),
|
|
error=data.get("error"),
|
|
)
|
|
_cache_result(api_key, result)
|
|
return result
|
|
else:
|
|
logger.warning(
|
|
f"External auth returned status {response.status_code}: {response.text}"
|
|
)
|
|
# Don't cache errors - allow retry
|
|
return ExternalValidationResult(
|
|
valid=False,
|
|
error=f"Auth service returned {response.status_code}",
|
|
)
|
|
|
|
except httpx.TimeoutException:
|
|
logger.warning("External auth service timeout - falling back to local auth")
|
|
return None
|
|
except httpx.ConnectError:
|
|
logger.warning("Cannot connect to external auth service - falling back to local auth")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"External auth error: {e}")
|
|
return None
|
|
|
|
|
|
def clear_cache():
|
|
"""Clear the validation cache (for testing or runtime updates)."""
|
|
global _validation_cache
|
|
_validation_cache.clear()
|
|
logger.info("External auth cache cleared")
|