Files
stroblmeandClaude Opus 5 d01a8dad37 Rename Installation to Instance
Follows the portal: the noun is "instance" everywhere the app says it —
UI strings, CLI output, error details, docs and comments. The wire keys
(`instance_id`, `instance_token`) and the hub route this calls move with it.

An existing cloud.json is adopted rather than refused: without the key
alias the dataclass fails to parse, which the caller swallows and reads as
"never enrolled" instead of "reconnect".

`instance_key` on a node type becomes `target_key`. It means the outside
thing a node points at, which is a different sense of the word, and keeping
both would put two meanings of "instance" in one codebase.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
2026-08-31 10:12:01 +02:00

58 lines
1.8 KiB
Python

"""Credentials for node integrations.
Values go in and are never handed back out — the API only ever lists names.
"""
from typing import Any
from fastapi import APIRouter, Depends, HTTPException
from fastapi.concurrency import run_in_threadpool
from pydantic import BaseModel
from fluksio.api.deps import get_current_active_superuser
from fluksio.flow.secrets import SecretNotFound, get_secrets
from fluksio.models import Message
# Superuser, not merely signed in. The names alone say what this instance
# talks to, and `PUT /{name}` takes any name — so an ordinary account could
# overwrite the credential a flow authenticates with. `/search` already gates
# secret names this way and said so; this router was the half that did not.
router = APIRouter(
prefix="/secrets",
tags=["secrets"],
dependencies=[Depends(get_current_active_superuser)],
)
class SecretNames(BaseModel):
data: list[str]
count: int
class SecretValue(BaseModel):
value: str
@router.get("/", response_model=SecretNames)
def read_secrets() -> Any:
"""List the names of stored secrets."""
names = get_secrets().list()
return SecretNames(data=names, count=len(names))
@router.put("/{name}", response_model=Message)
async def save_secret(name: str, body: SecretValue) -> Any:
"""Store a secret under a name that nodes can reference."""
await run_in_threadpool(get_secrets().set, name, body.value)
return Message(message=f"Saved secret '{name}'")
@router.delete("/{name}", response_model=Message)
async def delete_secret(name: str) -> Any:
"""Delete a secret."""
try:
await run_in_threadpool(get_secrets().delete, name)
except SecretNotFound:
raise HTTPException(status_code=404, detail=f"No secret named '{name}'")
return Message(message=f"Deleted secret '{name}'")