Files
All-in-one-Financial-Analysis/atlas-terminal/server/routers/credentials.py
T

60 lines
2.4 KiB
Python

"""Secure credential storage routes."""
from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field
from server.core.secrets import MissingMasterKey, SecretsError
from server.db.credentials_repo import delete_credential, get_credential_status, log_credential_access
from server.services.credentials_service import store_credential
router = APIRouter()
class CredentialUpsertRequest(BaseModel):
secret: str = Field(min_length=1, description="Plaintext API secret. It is envelope-encrypted before storage.")
user_id: str = Field(default="local", min_length=1)
def _request_meta(request: Request) -> tuple[str | None, str | None]:
ip = request.client.host if request.client else None
ua = request.headers.get("user-agent")
return ip, ua
@router.put("/{provider}")
async def upsert_provider_credential(provider: str, body: CredentialUpsertRequest, request: Request) -> dict[str, object]:
ip, ua = _request_meta(request)
try:
await store_credential(body.user_id, provider, body.secret, ip, ua)
except MissingMasterKey as exc:
raise HTTPException(status_code=503, detail="ATLAS_MASTER_KEY is not configured") from exc
except SecretsError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"user_id": body.user_id, "provider": provider.lower(), "stored": True}
@router.get("/{provider}/status")
async def provider_credential_status(provider: str, request: Request, user_id: str = "local") -> dict[str, object]:
ip, ua = _request_meta(request)
row = await get_credential_status(user_id, provider)
await log_credential_access(user_id, provider, "status", ip, ua)
if not row:
return {"user_id": user_id, "provider": provider.lower(), "configured": False}
return {
"user_id": user_id,
"provider": provider.lower(),
"configured": True,
"created_at": row.get("created_at"),
"last_used_at": row.get("last_used_at"),
}
@router.delete("/{provider}")
async def delete_provider_credential(provider: str, request: Request, user_id: str = "local") -> dict[str, object]:
ip, ua = _request_meta(request)
deleted = await delete_credential(user_id, provider)
await log_credential_access(user_id, provider, "delete", ip, ua)
return {"user_id": user_id, "provider": provider.lower(), "deleted": deleted}