credential updated
This commit is contained in:
@@ -100,7 +100,11 @@ async def handle_check_agent(request: web.Request) -> web.Response:
|
||||
import os
|
||||
|
||||
from framework.credentials.setup import CredentialSetupSession
|
||||
from framework.credentials.storage import CompositeStorage, EncryptedFileStorage, EnvVarStorage
|
||||
from framework.credentials.storage import (
|
||||
CompositeStorage,
|
||||
EncryptedFileStorage,
|
||||
EnvVarStorage,
|
||||
)
|
||||
from framework.credentials.validation import _presync_aden_tokens, ensure_credential_key_env
|
||||
|
||||
# Load env vars from shell config (same as runtime startup)
|
||||
@@ -116,8 +120,7 @@ async def handle_check_agent(request: web.Request) -> web.Response:
|
||||
_presync_aden_tokens(CREDENTIAL_SPECS)
|
||||
|
||||
env_mapping = {
|
||||
(spec.credential_id or name): spec.env_var
|
||||
for name, spec in CREDENTIAL_SPECS.items()
|
||||
(spec.credential_id or name): spec.env_var for name, spec in CREDENTIAL_SPECS.items()
|
||||
}
|
||||
env_storage = EnvVarStorage(env_mapping=env_mapping)
|
||||
if os.environ.get("HIVE_CREDENTIAL_KEY"):
|
||||
@@ -134,6 +137,7 @@ async def handle_check_agent(request: web.Request) -> web.Response:
|
||||
if verify:
|
||||
try:
|
||||
from aden_tools.credentials import check_credential_health
|
||||
|
||||
check_health = check_credential_health
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -179,16 +183,62 @@ async def handle_check_agent(request: web.Request) -> web.Response:
|
||||
entry["validation_message"] = f"Health check error: {exc}"
|
||||
|
||||
required.append(entry)
|
||||
return web.json_response({"required": required})
|
||||
return web.json_response({
|
||||
"required": required,
|
||||
"has_aden_key": bool(os.environ.get("ADEN_API_KEY")),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception(f"Error checking agent credentials: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
async def handle_save_aden_key(request: web.Request) -> web.Response:
|
||||
"""POST /api/credentials/aden-key — save the user's ADEN_API_KEY.
|
||||
|
||||
Sets the key in the current process environment and persists it to shell
|
||||
config so future terminals pick it up. Then triggers an Aden token sync
|
||||
so OAuth credentials resolve immediately.
|
||||
|
||||
Body: {"key": "..."}
|
||||
"""
|
||||
import os
|
||||
|
||||
body = await request.json()
|
||||
key = body.get("key", "").strip()
|
||||
if not key:
|
||||
return web.json_response({"error": "key is required"}, status=400)
|
||||
|
||||
os.environ["ADEN_API_KEY"] = key
|
||||
|
||||
# Persist to shell config (best-effort, same pattern as TUI setup)
|
||||
try:
|
||||
from aden_tools.credentials.shell_config import add_env_var_to_shell_config
|
||||
|
||||
add_env_var_to_shell_config(
|
||||
"ADEN_API_KEY",
|
||||
key,
|
||||
comment="Aden Platform API key",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("Could not persist ADEN_API_KEY to shell config: %s", exc)
|
||||
|
||||
# Immediately sync OAuth tokens from Aden
|
||||
try:
|
||||
from aden_tools.credentials import CREDENTIAL_SPECS
|
||||
from framework.credentials.validation import _presync_aden_tokens
|
||||
|
||||
_presync_aden_tokens(CREDENTIAL_SPECS)
|
||||
except Exception as exc:
|
||||
logger.warning("Aden token sync after key save failed: %s", exc)
|
||||
|
||||
return web.json_response({"saved": True}, status=201)
|
||||
|
||||
|
||||
def register_routes(app: web.Application) -> None:
|
||||
"""Register credential routes on the application."""
|
||||
# check-agent must be registered BEFORE the {credential_id} wildcard
|
||||
# check-agent and aden-key must be registered BEFORE the {credential_id} wildcard
|
||||
app.router.add_post("/api/credentials/check-agent", handle_check_agent)
|
||||
app.router.add_post("/api/credentials/aden-key", handle_save_aden_key)
|
||||
app.router.add_get("/api/credentials", handle_list_credentials)
|
||||
app.router.add_post("/api/credentials", handle_save_credential)
|
||||
app.router.add_get("/api/credentials/{credential_id}", handle_get_credential)
|
||||
|
||||
@@ -39,8 +39,11 @@ export const credentialsApi = {
|
||||
api.delete<{ deleted: boolean }>(`/credentials/${credentialId}`),
|
||||
|
||||
checkAgent: (agentPath: string) =>
|
||||
api.post<{ required: AgentCredentialRequirement[] }>(
|
||||
api.post<{ required: AgentCredentialRequirement[]; has_aden_key: boolean }>(
|
||||
"/credentials/check-agent",
|
||||
{ agent_path: agentPath },
|
||||
),
|
||||
|
||||
saveAdenKey: (key: string) =>
|
||||
api.post<{ saved: boolean }>("/credentials/aden-key", { key }),
|
||||
};
|
||||
|
||||
@@ -72,6 +72,9 @@ export default function CredentialsModal({
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [hasAdenKey, setHasAdenKey] = useState(true); // assume true until backend says otherwise
|
||||
const [adenKeyInput, setAdenKeyInput] = useState("");
|
||||
const [savingAdenKey, setSavingAdenKey] = useState(false);
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
setError(null);
|
||||
@@ -96,7 +99,8 @@ export default function CredentialsModal({
|
||||
|
||||
// Real agent — ask backend what credentials it actually needs
|
||||
setLoading(true);
|
||||
const { required } = await credentialsApi.checkAgent(agentPath);
|
||||
const { required, has_aden_key } = await credentialsApi.checkAgent(agentPath);
|
||||
setHasAdenKey(has_aden_key);
|
||||
credentialCache.set(agentPath, required);
|
||||
const newRows: CredentialRow[] = required.map((r: AgentCredentialRequirement) => ({
|
||||
id: r.credential_id,
|
||||
@@ -135,9 +139,26 @@ export default function CredentialsModal({
|
||||
fetchStatus();
|
||||
setEditingId(null);
|
||||
setInputValue("");
|
||||
setAdenKeyInput("");
|
||||
}
|
||||
}, [open, fetchStatus]);
|
||||
|
||||
const handleSaveAdenKey = async () => {
|
||||
if (!adenKeyInput.trim()) return;
|
||||
setSavingAdenKey(true);
|
||||
try {
|
||||
await credentialsApi.saveAdenKey(adenKeyInput.trim());
|
||||
setAdenKeyInput("");
|
||||
if (agentPath) credentialCache.delete(agentPath);
|
||||
onCredentialChange?.();
|
||||
await fetchStatus();
|
||||
} catch {
|
||||
setError("Failed to save Aden API Key");
|
||||
} finally {
|
||||
setSavingAdenKey(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConnect = async (row: CredentialRow) => {
|
||||
if (row.adenSupported) {
|
||||
// OAuth credential — redirect to Aden platform
|
||||
@@ -189,6 +210,7 @@ export default function CredentialsModal({
|
||||
const requiredCount = rows.filter(c => c.required).length;
|
||||
const requiredConnected = rows.filter(c => c.required && c.connected).length;
|
||||
const allRequiredMet = requiredConnected === requiredCount;
|
||||
const needsAdenKeyInput = !hasAdenKey && rows.some(r => r.adenSupported);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -249,6 +271,50 @@ export default function CredentialsModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Aden API Key section */}
|
||||
{!loading && needsAdenKeyInput && (
|
||||
<div className="mx-5 mt-4 px-3 py-3 rounded-lg border border-amber-500/30 bg-amber-500/5">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<KeyRound className="w-3.5 h-3.5 text-amber-600" />
|
||||
<span className="text-sm font-medium text-foreground">Aden API Key</span>
|
||||
<span className="text-[9px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded text-destructive/70 bg-destructive/10">
|
||||
Required
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mb-2">
|
||||
Required to connect OAuth integrations below.{" "}
|
||||
<a
|
||||
href="https://hive.adenhq.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline inline-flex items-center gap-0.5"
|
||||
>
|
||||
Get your key at hive.adenhq.com
|
||||
<ExternalLink className="w-2.5 h-2.5" />
|
||||
</a>
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="password"
|
||||
value={adenKeyInput}
|
||||
onChange={(e) => setAdenKeyInput(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleSaveAdenKey();
|
||||
}}
|
||||
placeholder="Paste your ADEN_API_KEY..."
|
||||
className="flex-1 px-3 py-1.5 rounded-md border border-border bg-background text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSaveAdenKey}
|
||||
disabled={savingAdenKey || !adenKeyInput.trim()}
|
||||
className="px-3 py-1.5 rounded-md text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
{savingAdenKey ? <Loader2 className="w-3 h-3 animate-spin" /> : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Credential list */}
|
||||
{!loading && (
|
||||
<div className="p-5 space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user