diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index aca22e5c5..9e8bf50d6 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -541,7 +541,7 @@ def get_execution_schedules( @v1_router.post( "/api-keys", - response_model=list[CreateAPIKeyResponse] | dict[str, str], + response_model=CreateAPIKeyResponse, tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) @@ -583,7 +583,7 @@ async def get_api_keys( @v1_router.get( "/api-keys/{key_id}", - response_model=list[APIKeyWithoutHash] | dict[str, str], + response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) @@ -604,7 +604,7 @@ async def get_api_key( @v1_router.delete( "/api-keys/{key_id}", - response_model=list[APIKeyWithoutHash] | dict[str, str], + response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) @@ -626,7 +626,7 @@ async def delete_api_key( @v1_router.post( "/api-keys/{key_id}/suspend", - response_model=list[APIKeyWithoutHash] | dict[str, str], + response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) @@ -648,7 +648,7 @@ async def suspend_key( @v1_router.put( "/api-keys/{key_id}/permissions", - response_model=list[APIKeyWithoutHash] | dict[str, str], + response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) diff --git a/autogpt_platform/frontend/src/app/store/(user)/api_keys/page.tsx b/autogpt_platform/frontend/src/app/store/(user)/api_keys/page.tsx new file mode 100644 index 000000000..87f3d58b4 --- /dev/null +++ b/autogpt_platform/frontend/src/app/store/(user)/api_keys/page.tsx @@ -0,0 +1,11 @@ +import { APIKeysSection } from "@/components/agptui/composite/APIKeySection"; + +const ApiKeysPage = () => { + return ( +
+ +
+ ); +}; + +export default ApiKeysPage; diff --git a/autogpt_platform/frontend/src/app/store/(user)/layout.tsx b/autogpt_platform/frontend/src/app/store/(user)/layout.tsx index 0f90e5bd3..64900562a 100644 --- a/autogpt_platform/frontend/src/app/store/(user)/layout.tsx +++ b/autogpt_platform/frontend/src/app/store/(user)/layout.tsx @@ -8,6 +8,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { { text: "Creator Dashboard", href: "/store/dashboard" }, { text: "Agent dashboard", href: "/store/agent-dashboard" }, { text: "Integrations", href: "/store/integrations" }, + { text: "API Keys", href: "/store/api_keys" }, { text: "Profile", href: "/store/profile" }, { text: "Settings", href: "/store/settings" }, ], @@ -17,7 +18,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { return (
-
{children}
+
{children}
); } diff --git a/autogpt_platform/frontend/src/components/agptui/Sidebar.tsx b/autogpt_platform/frontend/src/components/agptui/Sidebar.tsx index 5cad3fddc..545d82b76 100644 --- a/autogpt_platform/frontend/src/components/agptui/Sidebar.tsx +++ b/autogpt_platform/frontend/src/components/agptui/Sidebar.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import Link from "next/link"; import { Button } from "./Button"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; -import { Menu } from "lucide-react"; +import { KeyIcon, Menu } from "lucide-react"; import { IconDashboardLayout, IconIntegrations, @@ -58,6 +58,15 @@ export const Sidebar: React.FC = ({ linkGroups }) => { Integrations + + +
+ API Keys +
+ = ({ linkGroups }) => { Integrations + + +
+ API Keys +
+ ([]); + 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 = useBackendAPI(); + + 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/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 1f74e67f6..169c17ac8 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -29,6 +29,9 @@ import { StoreReview, ScheduleCreatable, Schedule, + APIKeyPermission, + CreateAPIKeyResponse, + APIKey, } from "./types"; import { createBrowserClient } from "@supabase/ssr"; import getServerSupabase from "../supabase/getServerSupabase"; @@ -221,6 +224,36 @@ export default class BackendAPI { ); } + // 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, + }); + } + /** * @returns `true` if a ping event was received, `false` if provider doesn't support pinging but the webhook exists. * @throws `Error` if the webhook does not exist. 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 2f2b8fb96..d38320666 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -513,3 +513,36 @@ export type StoreReviewCreate = { score: number; comments?: 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; +}