- settings/src/integrations/schema.js: rename azure from 'Azure AD' to 'Azure Document Intelligence', swap fields to endpoint + api_key - Add use_global_azure column (migration + model) - global_settings_service: add azure to check_global_status and apply_global_overrides - api/settings.py: expose use_global_azure in response/update; apply overrides in test_azure_connection before credential check - Settings.tsx: add 'Use credentials from main stack settings' toggle for Azure OCR section (endpoint/key disabled when on, test button enabled when global is configured); remove Users section (managed centrally via auth service), clean up UserData interface, users query and mutations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4.7 KiB
Python
104 lines
4.7 KiB
Python
"""
|
|
Fetch integration credentials from the central stack settings service.
|
|
|
|
When a kitchen setting has use_global_<slug>=True, the backend reads credentials
|
|
from the settings service (SETTINGS_URL) instead of the local kitchen_settings row.
|
|
apply_global_overrides() modifies the KitchenSettings object IN MEMORY only — it does
|
|
not commit anything to the DB.
|
|
"""
|
|
import os
|
|
import logging
|
|
import httpx
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SETTINGS_URL = os.getenv("SETTINGS_URL", "").rstrip("/")
|
|
STACK_INTERNAL_SECRET = os.getenv("STACK_INTERNAL_SECRET", "")
|
|
|
|
|
|
async def get_global_integration(slug: str) -> dict | None:
|
|
"""Fetch unmasked credentials for an integration from the central settings service."""
|
|
if not SETTINGS_URL or not STACK_INTERNAL_SECRET:
|
|
return None
|
|
try:
|
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
resp = await client.get(
|
|
f"{SETTINGS_URL}/api/internal/integration/{slug}",
|
|
headers={"Authorization": f"Bearer {STACK_INTERNAL_SECRET}"},
|
|
)
|
|
if resp.status_code == 200:
|
|
return resp.json()
|
|
logger.warning("Global settings fetch for %s returned %s", slug, resp.status_code)
|
|
except Exception as exc:
|
|
logger.warning("Global settings fetch for %s failed: %s", slug, exc)
|
|
return None
|
|
|
|
|
|
async def check_global_status() -> dict[str, bool]:
|
|
"""Return whether each integration is configured in the central settings service."""
|
|
# slug → list of secret fields that must be non-empty to count as "configured"
|
|
required = {
|
|
"smtp": ["pass"],
|
|
"nextcloud": ["password"],
|
|
"newbook": ["api_key"],
|
|
"resos": ["api_key"],
|
|
"sambapos": ["password"],
|
|
"azure": ["api_key"],
|
|
}
|
|
results: dict[str, bool] = {}
|
|
for slug, fields in required.items():
|
|
creds = await get_global_integration(slug)
|
|
results[slug] = bool(creds and all(creds.get(f) for f in fields))
|
|
return results
|
|
|
|
|
|
async def apply_global_overrides(settings) -> None:
|
|
"""
|
|
In-memory only: override credential fields on a KitchenSettings instance
|
|
where the corresponding use_global_* flag is True.
|
|
|
|
Attribute assignments do NOT persist to the DB — the session is never committed
|
|
as a result of this call.
|
|
"""
|
|
if getattr(settings, "use_global_smtp", False):
|
|
creds = await get_global_integration("smtp")
|
|
if creds:
|
|
settings.smtp_host = creds.get("host") or settings.smtp_host
|
|
settings.smtp_port = int(creds.get("port") or settings.smtp_port or 587)
|
|
settings.smtp_username = creds.get("user") or settings.smtp_username
|
|
settings.smtp_password = creds.get("pass") or settings.smtp_password
|
|
|
|
if getattr(settings, "use_global_nextcloud", False):
|
|
creds = await get_global_integration("nextcloud")
|
|
if creds:
|
|
settings.nextcloud_host = creds.get("base_url") or settings.nextcloud_host
|
|
settings.nextcloud_username = creds.get("username") or settings.nextcloud_username
|
|
settings.nextcloud_password = creds.get("password") or settings.nextcloud_password
|
|
|
|
if getattr(settings, "use_global_newbook", False):
|
|
creds = await get_global_integration("newbook")
|
|
if creds:
|
|
settings.newbook_api_username = creds.get("username") or settings.newbook_api_username
|
|
settings.newbook_api_password = creds.get("password") or settings.newbook_api_password
|
|
settings.newbook_api_key = creds.get("api_key") or settings.newbook_api_key
|
|
settings.newbook_api_region = creds.get("region") or settings.newbook_api_region
|
|
|
|
if getattr(settings, "use_global_resos", False):
|
|
creds = await get_global_integration("resos")
|
|
if creds:
|
|
settings.resos_api_key = creds.get("api_key") or settings.resos_api_key
|
|
|
|
if getattr(settings, "use_global_sambapos", False):
|
|
creds = await get_global_integration("sambapos")
|
|
if creds:
|
|
settings.sambapos_db_host = creds.get("host") or settings.sambapos_db_host
|
|
settings.sambapos_db_port = creds.get("port") or settings.sambapos_db_port
|
|
settings.sambapos_db_name = creds.get("database") or settings.sambapos_db_name
|
|
settings.sambapos_db_username = creds.get("username") or settings.sambapos_db_username
|
|
settings.sambapos_db_password = creds.get("password") or settings.sambapos_db_password
|
|
|
|
if getattr(settings, "use_global_azure", False):
|
|
creds = await get_global_integration("azure")
|
|
if creds:
|
|
settings.azure_endpoint = creds.get("endpoint") or settings.azure_endpoint
|
|
settings.azure_key = creds.get("api_key") or settings.azure_key
|