mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-27 01:11:40 +00:00
Add OAuth provider management to the Hermes dashboard with full
lifecycle support for Anthropic (PKCE), Nous and OpenAI Codex
(device-code) flows.
## Backend (hermes_cli/web_server.py)
- 6 new API endpoints:
GET /api/providers/oauth — list providers with connection status
POST /api/providers/oauth/{id}/start — initiate PKCE or device-code
POST /api/providers/oauth/{id}/submit — exchange PKCE auth code
GET /api/providers/oauth/{id}/poll/{session} — poll device-code
DELETE /api/providers/oauth/{id} — disconnect provider
DELETE /api/providers/oauth/sessions/{id} — cancel pending session
- OAuth constants imported from anthropic_adapter (no duplication)
- Blocking I/O wrapped in run_in_executor for async safety
- In-memory session store with 15-minute TTL and automatic GC
- Auth token required on all mutating endpoints
## Frontend
- OAuthLoginModal — PKCE (paste auth code) and device-code (poll) flows
- OAuthProvidersCard — status, token preview, connect/disconnect actions
- Toast fix: createPortal to document.body for correct z-index
- App.tsx: skip animation key bump on initial mount (prevent double-mount)
- Integrated into the Env/Keys page
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { createPortal } from "react-dom";
|
|
|
|
export function Toast({ toast }: { toast: { message: string; type: "success" | "error" } | null }) {
|
|
const [visible, setVisible] = useState(false);
|
|
const [current, setCurrent] = useState(toast);
|
|
|
|
useEffect(() => {
|
|
if (toast) {
|
|
setCurrent(toast);
|
|
setVisible(true);
|
|
} else {
|
|
setVisible(false);
|
|
const timer = setTimeout(() => setCurrent(null), 200);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [toast]);
|
|
|
|
if (!current) return null;
|
|
|
|
// Portal to document.body so the toast escapes any ancestor stacking context
|
|
// (e.g. <main> has `relative z-2`, which would trap z-50 below the header's z-40).
|
|
return createPortal(
|
|
<div
|
|
role="status"
|
|
aria-live="polite"
|
|
className={`fixed top-16 right-4 z-50 border px-4 py-2.5 font-courier text-xs tracking-wider uppercase backdrop-blur-sm ${
|
|
current.type === "success"
|
|
? "bg-success/15 text-success border-success/30"
|
|
: "bg-destructive/15 text-destructive border-destructive/30"
|
|
}`}
|
|
style={{
|
|
animation: visible ? "toast-in 200ms ease-out forwards" : "toast-out 200ms ease-in forwards",
|
|
}}
|
|
>
|
|
{current.message}
|
|
</div>,
|
|
document.body,
|
|
);
|
|
}
|