Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
39 lines
1.1 KiB
Python
39 lines
1.1 KiB
Python
"""Symmetric encryption of device credentials at rest (Fernet)."""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import hashlib
|
|
|
|
from cryptography.fernet import Fernet, InvalidToken
|
|
|
|
from app.config import settings
|
|
|
|
|
|
def _fernet() -> Fernet:
|
|
key = settings.credential_encryption_key
|
|
if not key:
|
|
# Derive a stable (but weak) key from SECRET_KEY so dev works without extra config.
|
|
digest = hashlib.sha256(settings.secret_key.encode()).digest()
|
|
key = base64.urlsafe_b64encode(digest).decode()
|
|
try:
|
|
return Fernet(key)
|
|
except (ValueError, TypeError):
|
|
# Treat provided value as raw material and derive a valid Fernet key
|
|
digest = hashlib.sha256(key.encode()).digest()
|
|
return Fernet(base64.urlsafe_b64encode(digest))
|
|
|
|
|
|
def encrypt_secret(plaintext: str | None) -> str | None:
|
|
if not plaintext:
|
|
return None
|
|
return _fernet().encrypt(plaintext.encode()).decode()
|
|
|
|
|
|
def decrypt_secret(token: str | None) -> str | None:
|
|
if not token:
|
|
return None
|
|
try:
|
|
return _fernet().decrypt(token.encode()).decode()
|
|
except InvalidToken:
|
|
return None
|