HomePilot / backend /app /compute /secrets.py
HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
a42bc3b
Raw
History Blame Contribute Delete
3.79 kB
"""
Compute credential store (PR 2).
Sources that talk to a remote endpoint (an OpenAI-compatible API, a hosted
provider) need a credential. We never keep that in the frontend or in the
``ComputeSource`` row — the source stores only a ``credential_ref`` (an opaque
key), and the actual secret lives here, server-side.
Encryption is opt-in and mirrors ``app.cloud_tokens``: if ``cryptography`` is
installed and ``HOMEPILOT_COMPUTE_SECRET_KEY`` (a Fernet key) is set, values are
encrypted at rest ("f:"); otherwise they are stored server-side as-is ("p:").
Either way the secret is out of the browser — the primary goal.
"""
from __future__ import annotations
import os
import sqlite3
import time
import uuid
from typing import Optional
from ..storage import _get_db_path
_TABLE = "compute_secrets"
_fernet_cache: object = None
def _fernet():
global _fernet_cache
if _fernet_cache is not None:
return _fernet_cache or None
key = os.getenv("HOMEPILOT_COMPUTE_SECRET_KEY", "").strip()
if not key:
_fernet_cache = False
return None
try:
from cryptography.fernet import Fernet
_fernet_cache = Fernet(key.encode())
return _fernet_cache
except Exception:
_fernet_cache = False
return None
def _encode(secret: str) -> str:
f = _fernet()
if f:
try:
return "f:" + f.encrypt(secret.encode()).decode()
except Exception:
pass
return "p:" + secret
def _decode(stored: str) -> Optional[str]:
if stored.startswith("f:"):
f = _fernet()
if not f:
return None
try:
return f.decrypt(stored[2:].encode()).decode()
except Exception:
return None
if stored.startswith("p:"):
return stored[2:]
return stored # legacy plaintext
def ensure_table() -> None:
con = sqlite3.connect(_get_db_path())
try:
con.execute(
f"""
CREATE TABLE IF NOT EXISTS {_TABLE} (
ref TEXT PRIMARY KEY,
secret_enc TEXT NOT NULL,
updated_at REAL NOT NULL
)
"""
)
con.commit()
finally:
con.close()
def put_secret(secret: str, *, ref: Optional[str] = None) -> str:
"""Store (upsert) a secret and return its ref. Generates a ref if omitted."""
ref = ref or f"cred_{uuid.uuid4().hex[:16]}"
if not secret:
return ref
ensure_table()
con = sqlite3.connect(_get_db_path())
try:
con.execute(
f"INSERT INTO {_TABLE}(ref, secret_enc, updated_at) VALUES (?,?,?) "
f"ON CONFLICT(ref) DO UPDATE SET secret_enc=excluded.secret_enc, "
f"updated_at=excluded.updated_at",
(ref, _encode(secret), time.time()),
)
con.commit()
finally:
con.close()
return ref
def get_secret(ref: Optional[str]) -> Optional[str]:
"""Return the secret for ``ref`` (decrypted), or None. Never raises."""
if not ref:
return None
try:
ensure_table()
con = sqlite3.connect(_get_db_path())
try:
row = con.execute(
f"SELECT secret_enc FROM {_TABLE} WHERE ref = ?", (ref,)
).fetchone()
finally:
con.close()
except Exception:
return None
if not row or not row[0]:
return None
return _decode(row[0])
def delete_secret(ref: Optional[str]) -> None:
if not ref:
return
try:
ensure_table()
con = sqlite3.connect(_get_db_path())
try:
con.execute(f"DELETE FROM {_TABLE} WHERE ref = ?", (ref,))
con.commit()
finally:
con.close()
except Exception:
pass