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

View File

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

View File

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

View File

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