diff --git a/autogpt_platform/frontend/src/app/profile/page.tsx b/autogpt_platform/frontend/src/app/profile/page.tsx index 155b5e04de31..e368bb69846b 100644 --- a/autogpt_platform/frontend/src/app/profile/page.tsx +++ b/autogpt_platform/frontend/src/app/profile/page.tsx @@ -20,6 +20,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { APIKeysSection } from "@/components/profile/api-keys-section"; import { CredentialsProviderName } from "@/lib/autogpt-server-api"; export default function PrivatePage() { @@ -112,6 +113,8 @@ export default function PrivatePage() { + +

Connections & Credentials

diff --git a/autogpt_platform/frontend/src/components/profile/api-keys-section.tsx b/autogpt_platform/frontend/src/components/profile/api-keys-section.tsx new file mode 100644 index 000000000000..ad6389eadd6d --- /dev/null +++ b/autogpt_platform/frontend/src/components/profile/api-keys-section.tsx @@ -0,0 +1,289 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useToast } from "../ui/use-toast"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "../ui/card"; +import { Button } from "../ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { Checkbox } from "../ui/checkbox"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "../ui/table"; +import { APIKey, APIKeyPermission } from "@/lib/autogpt-server-api/types"; +import AutoGPTServerAPI from "@/lib/autogpt-server-api/client"; +import { Badge } from "../ui/badge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; +import { LuMoreVertical, LuCopy } from "react-icons/lu"; +import { Loader2 } from "lucide-react"; + +export function APIKeysSection() { + const [apiKeys, setApiKeys] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isCreateOpen, setIsCreateOpen] = useState(false); + const [isKeyDialogOpen, setIsKeyDialogOpen] = useState(false); + const [newKeyName, setNewKeyName] = useState(""); + const [newKeyDescription, setNewKeyDescription] = useState(""); + const [newApiKey, setNewApiKey] = useState(""); + const [selectedPermissions, setSelectedPermissions] = useState< + APIKeyPermission[] + >([]); + const { toast } = useToast(); + const api = new AutoGPTServerAPI(); + + useEffect(() => { + loadAPIKeys(); + }); + + const loadAPIKeys = async () => { + setIsLoading(true); + try { + const keys = await api.listAPIKeys(); + setApiKeys(keys.filter((key) => key.status === "ACTIVE")); + } finally { + setIsLoading(false); + } + }; + + const handleCreateKey = async () => { + try { + const response = await api.createAPIKey( + newKeyName, + selectedPermissions, + newKeyDescription, + ); + + setNewApiKey(response.plain_text_key); + setIsCreateOpen(false); + setIsKeyDialogOpen(true); + loadAPIKeys(); + } catch (error) { + toast({ + title: "Error", + description: "Failed to create AutoGPT Platform API key", + variant: "destructive", + }); + } + }; + + const handleCopyKey = () => { + navigator.clipboard.writeText(newApiKey); + toast({ + title: "Copied", + description: "AutoGPT Platform API key copied to clipboard", + }); + }; + + const handleRevokeKey = async (keyId: string) => { + try { + await api.revokeAPIKey(keyId); + toast({ + title: "Success", + description: "AutoGPT Platform API key revoked successfully", + }); + loadAPIKeys(); + } catch (error) { + toast({ + title: "Error", + description: "Failed to revoke AutoGPT Platform API key", + variant: "destructive", + }); + } + }; + + return ( + + + AutoGPT Platform API Keys + + Manage your AutoGPT Platform API keys for programmatic access + + + +
+ + + + + + + Create New API Key + + Create a new AutoGPT Platform API key + + +
+
+ + setNewKeyName(e.target.value)} + placeholder="My AutoGPT Platform API Key" + /> +
+
+ + setNewKeyDescription(e.target.value)} + placeholder="Used for..." + /> +
+
+ + {Object.values(APIKeyPermission).map((permission) => ( +
+ { + setSelectedPermissions( + checked + ? [...selectedPermissions, permission] + : selectedPermissions.filter( + (p) => p !== permission, + ), + ); + }} + /> + +
+ ))} +
+
+ + + + +
+
+ + + + + AutoGPT Platform API Key Created + + Please copy your AutoGPT API key now. You won't be able + to see it again! + + +
+ + {newApiKey} + + +
+ + + +
+
+
+ + {isLoading ? ( +
+ +
+ ) : ( + apiKeys.length > 0 && ( +
+ + + Name + API Key + Status + Created + Last Used + + + + + {apiKeys.map((key) => ( + + {key.name} + +
+ {`${key.prefix}******************${key.postfix}`} +
+
+ + + {key.status} + + + + {new Date(key.created_at).toLocaleDateString()} + + + {key.last_used_at + ? new Date(key.last_used_at).toLocaleDateString() + : "Never"} + + + + + + + + handleRevokeKey(key.id)} + > + Revoke + + + + +
+ ))} +
+
+ ) + )} + + + ); +} diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts index 12b45ebfca53..122398b4ef10 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts @@ -16,6 +16,9 @@ import { NodeExecutionResult, OAuth2Credentials, User, + APIKeyPermission, + CreateAPIKeyResponse, + APIKey, ScheduleCreatable, ScheduleUpdateable, } from "./types"; @@ -240,6 +243,35 @@ export default class BaseAutoGPTServerAPI { return this._request("GET", path, query); } + // API Key related requests + async createAPIKey( + name: string, + permissions: APIKeyPermission[], + description?: string, + ): Promise { + return this._request("POST", "/api-keys", { + name, + permissions, + description, + }); + } + + async listAPIKeys(): Promise { + return this._get("/api-keys"); + } + + async revokeAPIKey(keyId: string): Promise { + return this._request("DELETE", `/api-keys/${keyId}`); + } + + async updateAPIKeyPermissions( + keyId: string, + permissions: APIKeyPermission[], + ): Promise { + return this._request("PUT", `/api-keys/${keyId}/permissions`, { + permissions, + }); + // Scheduling request async createSchedule( graphId: string, @@ -263,6 +295,7 @@ export default class BaseAutoGPTServerAPI { async getSchedules(graphId: string): Promise<{ [key: string]: string }> { return this._get(`/graphs/${graphId}/schedules`); + } private async _request( diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index a416b244fa80..2412c59f0f6e 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -330,6 +330,40 @@ export type AnalyticsDetails = { index: string; }; + +// API Key Types + +export enum APIKeyPermission { + EXECUTE_GRAPH = "EXECUTE_GRAPH", + READ_GRAPH = "READ_GRAPH", + EXECUTE_BLOCK = "EXECUTE_BLOCK", + READ_BLOCK = "READ_BLOCK", +} + +export enum APIKeyStatus { + ACTIVE = "ACTIVE", + REVOKED = "REVOKED", + SUSPENDED = "SUSPENDED", +} + +export interface APIKey { + id: string; + name: string; + prefix: string; + postfix: string; + status: APIKeyStatus; + permissions: APIKeyPermission[]; + created_at: string; + last_used_at?: string; + revoked_at?: string; + description?: string; +} + +export interface CreateAPIKeyResponse { + api_key: APIKey; + plain_text_key: string; +} + // Schedule types export type Schedule = { id: string; @@ -345,3 +379,4 @@ export type ScheduleCreatable = { export type ScheduleUpdateable = { is_enabled: boolean; }; +