refactor(platform): Combine per-provider credentials API calls (#8772)

- Add `/integrations/credentials` endpoint which lists all credentials for the authenticated user
- Amend credential fetching logic in front end to fetch all at once instead of per provider

- Resolves #8770
- Resolves (hopefully) #8613
This commit is contained in:
Reinier van der Leer
2024-11-26 18:03:06 +01:00
committed by GitHub
parent c6e838da37
commit f1414550f9
4 changed files with 76 additions and 29 deletions

View File

@@ -65,6 +65,7 @@ def login(
class CredentialsMetaResponse(BaseModel):
id: str
provider: str
type: CredentialsType
title: str | None
scopes: list[str] | None
@@ -119,6 +120,7 @@ def callback(
)
return CredentialsMetaResponse(
id=credentials.id,
provider=credentials.provider,
type=credentials.type,
title=credentials.title,
scopes=credentials.scopes,
@@ -126,8 +128,26 @@ def callback(
)
@router.get("/{provider}/credentials")
@router.get("/credentials")
def list_credentials(
user_id: Annotated[str, Depends(get_user_id)],
) -> list[CredentialsMetaResponse]:
credentials = creds_manager.store.get_all_creds(user_id)
return [
CredentialsMetaResponse(
id=cred.id,
provider=cred.provider,
type=cred.type,
title=cred.title,
scopes=cred.scopes if isinstance(cred, OAuth2Credentials) else None,
username=cred.username if isinstance(cred, OAuth2Credentials) else None,
)
for cred in credentials
]
@router.get("/{provider}/credentials")
def list_credentials_by_provider(
provider: Annotated[str, Path(title="The provider to list credentials for")],
user_id: Annotated[str, Depends(get_user_id)],
) -> list[CredentialsMetaResponse]:
@@ -135,6 +155,7 @@ def list_credentials(
return [
CredentialsMetaResponse(
id=cred.id,
provider=cred.provider,
type=cred.type,
title=cred.title,
scopes=cred.scopes if isinstance(cred, OAuth2Credentials) else None,

View File

@@ -184,43 +184,64 @@ export default function CredentialsProvider({
api.isAuthenticated().then((isAuthenticated) => {
if (!isAuthenticated) return;
CREDENTIALS_PROVIDER_NAMES.forEach(
(provider: CredentialsProviderName) => {
api.listCredentials(provider).then((response) => {
const { oauthCreds, apiKeys } = response.reduce<{
api.listCredentials().then((response) => {
const credentialsByProvider = response.reduce(
(acc, cred) => {
if (!acc[cred.provider]) {
acc[cred.provider] = { oauthCreds: [], apiKeys: [] };
}
if (cred.type === "oauth2") {
acc[cred.provider].oauthCreds.push(cred);
} else if (cred.type === "api_key") {
acc[cred.provider].apiKeys.push(cred);
}
return acc;
},
{} as Record<
CredentialsProviderName,
{
oauthCreds: CredentialsMetaResponse[];
apiKeys: CredentialsMetaResponse[];
}>(
(acc, cred) => {
if (cred.type === "oauth2") {
acc.oauthCreds.push(cred);
} else if (cred.type === "api_key") {
acc.apiKeys.push(cred);
}
return acc;
},
{ oauthCreds: [], apiKeys: [] },
);
}
>,
);
setProviders((prev) => ({
...prev,
setProviders((prev) => ({
...prev,
...Object.entries(credentialsByProvider).reduce(
(acc, [provider, { apiKeys, oauthCreds }]) => ({
...acc,
[provider]: {
provider,
providerName: providerDisplayNames[provider],
providerName:
providerDisplayNames[provider as CredentialsProviderName],
savedApiKeys: apiKeys,
savedOAuthCredentials: oauthCreds,
oAuthCallback: (code: string, state_token: string) =>
oAuthCallback(provider, code, state_token),
oAuthCallback(
provider as CredentialsProviderName,
code,
state_token,
),
createAPIKeyCredentials: (
credentials: APIKeyCredentialsCreatable,
) => createAPIKeyCredentials(provider, credentials),
) =>
createAPIKeyCredentials(
provider as CredentialsProviderName,
credentials,
),
deleteCredentials: (id: string, force: boolean = false) =>
deleteCredentials(provider, id, force),
deleteCredentials(
provider as CredentialsProviderName,
id,
force,
),
},
}));
});
},
);
}),
{},
),
}));
});
});
}, [api, createAPIKeyCredentials, deleteCredentials, oAuthCallback]);

View File

@@ -212,8 +212,12 @@ export default class BaseAutoGPTServerAPI {
);
}
listCredentials(provider: string): Promise<CredentialsMetaResponse[]> {
return this._get(`/integrations/${provider}/credentials`);
listCredentials(provider?: string): Promise<CredentialsMetaResponse[]> {
return this._get(
provider
? `/integrations/${provider}/credentials`
: "/integrations/credentials",
);
}
getCredentials(

View File

@@ -260,6 +260,7 @@ export type NodeExecutionResult = {
/* Mirror of backend/server/integrations/router.py:CredentialsMetaResponse */
export type CredentialsMetaResponse = {
id: string;
provider: CredentialsProviderName;
type: CredentialsType;
title?: string;
scopes?: Array<string>;
@@ -292,7 +293,7 @@ type BaseCredentials = {
id: string;
type: CredentialsType;
title?: string;
provider: string;
provider: CredentialsProviderName;
};
/* Mirror of autogpt_libs/supabase_integration_credentials_store/types.py:OAuth2Credentials */