fix: build out the credentials provider on the front and backend a bit more

This commit is contained in:
Nicholas Tindle 2024-12-20 15:11:58 -06:00
parent 5720225a75
commit e933502cbd
No known key found for this signature in database
GPG Key ID: C4A2154D91363A47
8 changed files with 90 additions and 24 deletions

View File

@ -197,7 +197,10 @@ class OAuth2Credentials(_BaseCredentials):
class APIKeyCredentials(_BaseCredentials):
type: Literal["api_key"] = "api_key"
api_key: SecretStr
expires_at: Optional[int]
expires_at: Optional[int] = Field(
default=None,
description="Unix timestamp (seconds) indicating when the API key expires (if at all)",
)
"""Unix timestamp (seconds) indicating when the API key expires (if at all)"""
def bearer(self) -> str:

View File

@ -17,6 +17,7 @@ from backend.data.model import (
Credentials,
CredentialsType,
OAuth2Credentials,
UserPasswordCredentials,
)
from backend.executor.manager import ExecutionManager
from backend.integrations.creds_manager import IntegrationCredentialsManager
@ -196,22 +197,15 @@ def get_credential(
@router.post("/{provider}/credentials", status_code=201)
def create_api_key_credentials(
def create_credentials(
user_id: Annotated[str, Depends(get_user_id)],
provider: Annotated[
ProviderName, Path(title="The provider to create credentials for")
],
api_key: Annotated[str, Body(title="The API key to store")],
title: Annotated[str, Body(title="Optional title for the credentials")],
expires_at: Annotated[
int | None, Body(title="Unix timestamp when the key expires")
] = None,
) -> APIKeyCredentials:
new_credentials = APIKeyCredentials(
provider=provider,
api_key=SecretStr(api_key),
title=title,
expires_at=expires_at,
credential: Credentials,
) -> Credentials:
new_credentials = credential.__class__(
provider=provider, **credential.model_dump(exclude={"provider"})
)
try:

View File

@ -123,7 +123,11 @@ export default function PrivatePage() {
const allCredentials = providers
? Object.values(providers).flatMap((provider) =>
[...provider.savedOAuthCredentials, ...provider.savedApiKeys]
[
...provider.savedOAuthCredentials,
...provider.savedApiKeys,
...provider.savedUserPasswordCredentials,
]
.filter((cred) => !hiddenCredentials.includes(cred.id))
.map((credentials) => ({
...credentials,

View File

@ -123,14 +123,22 @@ export default function PrivatePage() {
const allCredentials = providers
? Object.values(providers).flatMap((provider) =>
[...provider.savedOAuthCredentials, ...provider.savedApiKeys]
[
...provider.savedOAuthCredentials,
...provider.savedApiKeys,
...provider.savedUserPasswordCredentials,
]
.filter((cred) => !hiddenCredentials.includes(cred.id))
.map((credentials) => ({
...credentials,
provider: provider.provider,
providerName: provider.providerName,
ProviderIcon: providerIcons[provider.provider],
TypeIcon: { oauth2: IconUser, api_key: IconKey }[credentials.type],
TypeIcon: {
oauth2: IconUser,
api_key: IconKey,
user_password: IconKey,
}[credentials.type],
})),
)
: [];

View File

@ -246,13 +246,18 @@ export const CredentialsInput: FC<{
selectedCredentials &&
!savedApiKeys
.concat(savedOAuthCredentials)
.concat(savedUserPasswordCredentials)
.some((c) => c.id === selectedCredentials.id)
) {
onSelectCredentials(undefined);
}
// No saved credentials yet
if (savedApiKeys.length === 0 && savedOAuthCredentials.length === 0) {
if (
savedApiKeys.length === 0 &&
savedOAuthCredentials.length === 0 &&
savedUserPasswordCredentials.length === 0
) {
return (
<>
<div className="mb-2 flex gap-1">
@ -287,6 +292,7 @@ export const CredentialsInput: FC<{
);
}
//TODO: This is a mess that won't scale. We need to refactor this single credential logic
const singleCredential =
savedApiKeys.length === 1 && savedOAuthCredentials.length === 0
? savedApiKeys[0]
@ -316,6 +322,7 @@ export const CredentialsInput: FC<{
} else {
const selectedCreds = savedApiKeys
.concat(savedOAuthCredentials)
.concat(savedUserPasswordCredentials)
.find((c) => c.id == newValue)!;
onSelectCredentials({
@ -576,6 +583,45 @@ export const UserPasswordCredentialsModal: FC<{
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input type="text" placeholder="Enter username..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" placeholder="Enter password..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="text" placeholder="Enter a name for this user password..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Save & use this user password
</Button>

View File

@ -116,6 +116,9 @@ export default function CredentialsProvider({
credentials,
];
} else if (credentials.type === "user_password") {
console.log("Adding user password credentials", credentials);
console.log("Updated provider", updatedProvider);
console.log("Updated provider savedUserPasswordCredentials", updatedProvider.savedUserPasswordCredentials);
updatedProvider.savedUserPasswordCredentials = [
...updatedProvider.savedUserPasswordCredentials,
credentials,
@ -201,7 +204,10 @@ export default function CredentialsProvider({
updatedProvider.savedOAuthCredentials.filter(
(cred) => cred.id !== id,
);
updatedProvider.savedUserPasswordCredentials =
updatedProvider.savedUserPasswordCredentials.filter(
(cred) => cred.id !== id,
);
return {
...prev,
[provider]: updatedProvider,
@ -257,6 +263,8 @@ export default function CredentialsProvider({
savedApiKeys: credentialsByProvider[provider]?.apiKeys ?? [],
savedOAuthCredentials:
credentialsByProvider[provider]?.oauthCreds ?? [],
savedUserPasswordCredentials:
credentialsByProvider[provider]?.userPasswordCreds ?? [],
oAuthCallback: (code: string, state_token: string) =>
oAuthCallback(
provider as CredentialsProviderName,

View File

@ -30,6 +30,7 @@ import {
ScheduleCreatable,
Schedule,
UserPasswordCredentials,
Credentials,
} from "./types";
import { createBrowserClient } from "@supabase/ssr";
import getServerSupabase from "../supabase/getServerSupabase";
@ -189,7 +190,7 @@ export default class BackendAPI {
return this._request(
"POST",
`/integrations/${credentials.provider}/credentials`,
credentials,
{ ...credentials, type: "api_key" },
);
}
@ -199,7 +200,7 @@ export default class BackendAPI {
return this._request(
"POST",
`/integrations/${credentials.provider}/credentials`,
credentials,
{ ...credentials, type: "user_password" },
);
}
@ -211,10 +212,7 @@ export default class BackendAPI {
);
}
getCredentials(
provider: string,
id: string,
): Promise<APIKeyCredentials | OAuth2Credentials> {
getCredentials(provider: string, id: string): Promise<Credentials> {
return this._get(`/integrations/${provider}/credentials/${id}`);
}

View File

@ -99,6 +99,11 @@ export type BlockIOBooleanSubSchema = BlockIOSubSchemaMeta & {
export type CredentialsType = "api_key" | "oauth2" | "user_password";
export type Credentials =
| APIKeyCredentials
| OAuth2Credentials
| UserPasswordCredentials;
// --8<-- [start:BlockIOCredentialsSubSchema]
export const PROVIDER_NAMES = {
ANTHROPIC: "anthropic",