Skip to content

Commit e5af324

Browse files
authored
feat(knowledge): let the S3 connector sync with a saved AWS credential (#9934)
* feat(connectors): sign S3 connector requests with a seeded AWS session The S3 knowledge connector can now sign with keys a saved AWS credential resolved to, read from the run's syncContext. STS session tokens are sent as x-amz-security-token and included in the SigV4 signed headers. A saved credential signs only for Amazon S3 in an AWS region: a custom endpoint is refused so an AWS session never reaches an S3-compatible host. Inline keys sign exactly as before. * feat(knowledge): let a key connector sync with its declared saved credential A key-based connector may now declare a saved-credential alternative (auth.savedCredential); Amazon S3 declares a saved AWS credential. A row that names a credential resolves it for the knowledge base's own scope through the same owner check and STS exchange AWS tools use, and the resolved session is seeded into syncContext. Rows that store a key keep using it, and key-only connectors are unchanged. - connectorUsesCredential/connectorHasAuthSource decide the auth source in one place for the resolver, sync engine, validation and setup. - resolveConnectorAccessToken takes the knowledge base scope. - A run token reuses an STS session until shortly before it expires and re-seeds syncContext when it is replaced. - Setup with a saved credential is gated by the global knowledge-connector-saved-credentials flag: workers from an earlier release read a key connector without a key as credential-removed and unschedule it, so the flag stays off until the release is live on the app and Trigger.dev. - The connector catalog and v2 contract gain optional savedCredential and apiKeyOnly fields; generated OpenAPI, CLI API and CLI docs updated. - Adds a central mock for @/lib/internal/aws/credentials. * feat(knowledge): offer a saved AWS credential when setting up an S3 connector When knowledge-connector-saved-credentials is on, the add-connector modal lets Amazon S3 authenticate with a saved AWS credential instead of its access keys, hides the key-only Access Key ID and custom endpoint fields, and never sends values typed into them. The edit modal keeps the credential replaceable for a connector that syncs with one, including after its credential was removed. Inline keys stay the default. * docs(knowledge): describe the S3 connector's saved AWS credential option * fix(knowledge): refuse moving a stored-key connector onto its saved credential The access update accepted a credentialId for a key connector that still stored its key. The key stayed on the row and took over again, silently, once the credential was deleted. Switching auth source now means creating the connector anew, as it did before saved credentials. * fix(knowledge): classify saved AWS credential failures and labels consistently - Share the AWS failure message and transient check with the AWS tools, so STS throttling or an IdP outage during setup is no longer a 400. - Refuse another service's credential on S3 creation, as access updates do. - Return an empty access token for a saved AWS credential, not its id. - Record saved-credential connectors as authMode savedCredential in audit. - Narrow savedCredential.provider to aws, the only provider that resolves. - Derive create's key-vs-credential choice from connectorUsesCredential and rename the run-token refresh. * fix(connectors): sign saved AWS credentials only for commercial and GovCloud S3 China regions serve S3 under amazonaws.com.cn and the isolated partitions under their own domains, so a saved credential for them failed with an opaque DNS error. Refuse them with a clear message instead. * docs(knowledge): state saved-credential availability and rollback limits Catalog and connector credentialId descriptions say saved credentials work once the deployment enables them, the connector credentialId field covers saved credentials, and the flag notes that a release rollback is unsafe once any connector uses one. Regenerates OpenAPI, CLI API and CLI docs. * fix(knowledge): redact saved AWS session secrets and share the credential service check A saved AWS credential resolves to an empty bearer with its secret key and session token in awsSession, so redacting accessToken alone left them in provider validation text. Both validation sites now redact every secret the resolved token carries (connectorTokenSecrets). Setup and access updates now share requireConnectorCredentialService, so a key connector's saved-credential setup refuses a missing service config instead of skipping the provider check. * fix(knowledge): keep long-lived saved AWS keys per run and gate saved-credential edits on rollout - Reuse a saved AWS credential without a session token for the whole run instead of reloading it on every page and hydration - Hide the edit form's saved-credential replacement while saved-credential setup is disabled, matching the server gate - Classify a connector whose credential service has no config as internal, not caller-fixable - State in the v2 catalog that apiKey connectors with savedCredential accept credentialId * docs(knowledge): describe the saved-credential provider as a service, not an OAuth service * test(llm): mock AWS credentials through the central @sim/testing mock
1 parent ff166c1 commit e5af324

48 files changed

Lines changed: 1483 additions & 182 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/docs/content/docs/cli/knowledge.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@ sim knowledge connectors create <knowledgeBaseId> [options]
10401040
| Option | Required | Description |
10411041
| --- | --- | --- |
10421042
| `--connector-type <value>` | Yes | Registered connector type. |
1043-
| `--credential-id <value>` | No | OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types); omit it for `apiKey` connectors. |
1043+
| `--credential-id <value>` | No | OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types), or, when this deployment enables it, a saved credential for an `apiKey` connector type that declares `auth.savedCredential` (such as Amazon S3 with a saved AWS credential); otherwise omit it for `apiKey` connectors. |
10441044
| `--api-key <value>` | No | Write-only API key for connector types whose `auth.mode` is `apiKey` (see connector types), or a personal access token for an OAuth connector that also accepts one, such as GitHub. Send it instead of `credentialId`. Pass a raw key, or a secret reference written as the whole value `{{SECRET_NAME}}`, which the server resolves; `$SECRET_NAME` is not a reference. |
10451045
| `--source-config <json\|@file>` | Yes | Connector-specific source selection and filtering configuration. (JSON, or @path / @- to read a file or stdin). |
10461046
| `--sync-interval-minutes <value>` | No | Scheduled synchronization interval in minutes; zero disables scheduling. Defaults to 1440. |

‎apps/docs/content/docs/cli/reference.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2645,7 +2645,7 @@ sim knowledge connectors create <knowledgeBaseId> [options]
26452645
| Option | Required | Description |
26462646
| --- | --- | --- |
26472647
| `--connector-type <value>` | Yes | Registered connector type. |
2648-
| `--credential-id <value>` | No | OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types); omit it for `apiKey` connectors. |
2648+
| `--credential-id <value>` | No | OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types), or, when this deployment enables it, a saved credential for an `apiKey` connector type that declares `auth.savedCredential` (such as Amazon S3 with a saved AWS credential); otherwise omit it for `apiKey` connectors. |
26492649
| `--api-key <value>` | No | Write-only API key for connector types whose `auth.mode` is `apiKey` (see connector types), or a personal access token for an OAuth connector that also accepts one, such as GitHub. Send it instead of `credentialId`. Pass a raw key, or a secret reference written as the whole value `{{SECRET_NAME}}`, which the server resolves; `$SECRET_NAME` is not a reference. |
26502650
| `--source-config <json\|@file>` | Yes | Connector-specific source selection and filtering configuration. (JSON, or @path / @- to read a file or stdin). |
26512651
| `--sync-interval-minutes <value>` | No | Scheduled synchronization interval in minutes; zero disables scheduling. Defaults to 1440. |

‎apps/docs/content/docs/knowledgebase/connectors.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ API-key and personal-access-token connections use the credential shown in their
4646
| Azure DevOps | PAT with Project and Team Read, Wiki Read, Work Items Read, and Code Read. |
4747
| GitLab | PAT with `read_api` and access to the selected project; permission-aware methods have additional requirements. |
4848
| YouTube | YouTube Data API key from Google Cloud Console. |
49-
| Amazon S3 | Secret Access Key; enter Access Key ID, region, and bucket as configuration fields. |
49+
| Amazon S3 | Secret Access Key; enter Access Key ID, region, and bucket as configuration fields. Where enabled, **Authentication → Saved AWS credential** uses a workspace AWS credential instead (access keys, an assumed IAM role, or a keyless role trusted through Sim OIDC); it signs only for Amazon S3 in a commercial or GovCloud AWS region (not China or isolated regions), so it has no Access Key ID or custom endpoint. Either way, the identity needs `s3:ListBucket` on the bucket and `s3:GetObject` on the synced objects. |
5050
| Sentry | Auth token with `project:read` and `event:read`. |
5151
| PagerDuty | Create a [REST API key](https://support.pagerduty.com/main/docs/api-access-keys) under **Integrations → Developer Tools → API Access Keys**. |
5252
| SFTP | Password or unencrypted private key, plus a required SHA-256 host-key fingerprint; enter host, port, username, and root path as configuration fields. |

‎apps/docs/openapi-v2-knowledge.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6933,7 +6933,7 @@
69336933
"type": "null"
69346934
}
69356935
],
6936-
"description": "OAuth credential identifier, or null for API-key and unauthenticated sources."
6936+
"description": "OAuth credential identifier, or the saved credential an `apiKey` source syncs with (see `auth.savedCredential`); null for API-key and unauthenticated sources."
69376937
},
69386938
"sourceConfig": {
69396939
"type": "object",
@@ -7155,7 +7155,7 @@
71557155
"description": "Registered connector type."
71567156
},
71577157
"credentialId": {
7158-
"description": "OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types); omit it for `apiKey` connectors.",
7158+
"description": "OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types), or, when this deployment enables it, a saved credential for an `apiKey` connector type that declares `auth.savedCredential` (such as Amazon S3 with a saved AWS credential); otherwise omit it for `apiKey` connectors.",
71597159
"type": "string",
71607160
"minLength": 1,
71617161
"maxLength": 255
@@ -7331,7 +7331,7 @@
73317331
"type": "null"
73327332
}
73337333
],
7334-
"description": "OAuth credential identifier, or null for API-key and unauthenticated sources."
7334+
"description": "OAuth credential identifier, or the saved credential an `apiKey` source syncs with (see `auth.savedCredential`); null for API-key and unauthenticated sources."
73357335
},
73367336
"sourceConfig": {
73377337
"type": "object",

‎apps/docs/openapi-v2-resources.json‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21713,7 +21713,7 @@
2171321713
"mode": {
2171421714
"type": "string",
2171521715
"const": "apiKey",
21716-
"description": "Authenticates with a stored API key; pass `apiKey`, and no `credentialId`, when creating the connector."
21716+
"description": "Authenticates with a stored API key; pass `apiKey` when creating the connector, or a saved `credentialId` instead when `savedCredential` is present."
2171721717
},
2171821718
"label": {
2171921719
"description": "Label shown above the key field.",
@@ -21726,13 +21726,29 @@
2172621726
"optional": {
2172721727
"type": "boolean",
2172821728
"description": "Whether the key may be left blank, for a source reachable without authentication."
21729+
},
21730+
"savedCredential": {
21731+
"description": "When present, a saved credential of this service may be passed as `credentialId` instead of `apiKey`, once saved-credential setup is enabled on this deployment (until then it is refused with a validation error). Fields marked `apiKeyOnly` are then omitted.",
21732+
"type": "object",
21733+
"properties": {
21734+
"provider": {
21735+
"type": "string",
21736+
"description": "Service the saved credential must authenticate with, e.g. `aws`."
21737+
},
21738+
"label": {
21739+
"type": "string",
21740+
"description": "Label for the saved-credential option."
21741+
}
21742+
},
21743+
"required": ["provider", "label"],
21744+
"additionalProperties": false
2172921745
}
2173021746
},
2173121747
"required": ["mode", "optional"],
2173221748
"additionalProperties": false
2173321749
}
2173421750
],
21735-
"description": "How the connector authenticates against its source: `oauth` connectors take `credentialId` (GitHub also accepts a personal access token as `apiKey`), `apiKey` connectors take `apiKey`."
21751+
"description": "How the connector authenticates against its source: `oauth` connectors take `credentialId` (GitHub also accepts a personal access token as `apiKey`), `apiKey` connectors take `apiKey` (or, when they declare `savedCredential`, a saved `credentialId`)."
2173621752
},
2173721753
"configFields": {
2173821754
"type": "array",
@@ -21882,6 +21898,10 @@
2188221898
"multi": {
2188321899
"description": "When true the stored `sourceConfig` value is a `string[]`, not a `string`: a `selector` renders a multi-select picker and a `short-input` accepts a comma-separated list.",
2188421900
"type": "boolean"
21901+
},
21902+
"apiKeyOnly": {
21903+
"description": "When true the field describes the connector’s API key (such as its key ID or a custom host the key signs for). It is required as stated only with `apiKey`, and omitted when a saved `credentialId` is sent.",
21904+
"type": "boolean"
2188521905
}
2188621906
},
2188721907
"required": ["id", "title", "type"],
@@ -21910,7 +21930,7 @@
2191021930
"mode": {
2191121931
"type": "string",
2191221932
"enum": ["oauth", "apiKey"],
21913-
"description": "How the connector authenticates against its source: `oauth` connectors take `credentialId` (GitHub also accepts a personal access token as `apiKey`), `apiKey` connectors take `apiKey`."
21933+
"description": "How the connector authenticates against its source: `oauth` connectors take `credentialId` (GitHub also accepts a personal access token as `apiKey`), `apiKey` connectors take `apiKey` (or, when they declare `savedCredential`, a saved `credentialId`)."
2191421934
}
2191521935
},
2191621936
"required": ["mode"],

‎apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ import {
106106
kbDocumentSortParams,
107107
} from '@/app/workspace/[workspaceId]/knowledge/[id]/search-params'
108108
import { canDeleteKnowledgeBase } from '@/app/workspace/[workspaceId]/knowledge/permissions'
109+
import { useFeatureFlag } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider'
109110
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
110111
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
111112
import { BrandIcon } from '@/blocks/brand-icon'
@@ -420,6 +421,7 @@ export function KnowledgeBase({
420421
const [showDocumentTagsModal, setShowDocumentTagsModal] = useState(false)
421422
const [documentForTagsId, setDocumentForTagsId] = useState<string | null>(null)
422423
const showAddConnectorModal = addConnectorType != null
424+
const savedCredentialSetupEnabled = useFeatureFlag('knowledge-connector-saved-credentials')
423425
const updateAddConnectorParam = useCallback(
424426
(value: string | null) => {
425427
void setAddConnectorType(value, { history: 'replace', scroll: false })
@@ -1472,6 +1474,7 @@ export function KnowledgeBase({
14721474
knowledgeBaseId={id}
14731475
initialConnectorType={addConnectorType || undefined}
14741476
isSearchIndex={knowledgeBase?.isSearchIndex}
1477+
savedCredentialSetupEnabled={savedCredentialSetupEnabled}
14751478
/>
14761479
)}
14771480

@@ -1513,6 +1516,7 @@ export function KnowledgeBase({
15131516
isLoading={isLoadingConnectors}
15141517
canEdit={userPermissions.canEdit}
15151518
className='mt-0'
1519+
savedCredentialSetupEnabled={savedCredentialSetupEnabled}
15161520
/>
15171521
</ChipModalBody>
15181522
</ChipModal>

‎apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx‎

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ import {
6363
} from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
6464
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
6565
import { withBrandIcon } from '@/blocks/brand-icon'
66-
import { getConnectorApiKeyConfig, isConnectorCredentialTypeAllowed } from '@/connectors/auth'
66+
import {
67+
getConnectorApiKeyConfig,
68+
isConnectorCredentialTypeAllowed,
69+
isConnectorFieldForAuth,
70+
} from '@/connectors/auth'
6771
import { GitHubInstallationConnectionField } from '@/connectors/github/installation-connection-field'
6872
import {
6973
GitLabPermissionTabs,
@@ -109,6 +113,8 @@ interface AddConnectorModalProps {
109113
initialSyncIntervalMinutes?: number
110114
onCreated?: (connectorType: string, connector: ConnectorData) => void
111115
setupDraftKey?: string
116+
/** Offers a key connector's saved-credential alternative, such as a saved AWS credential for S3. */
117+
savedCredentialSetupEnabled?: boolean
112118
}
113119

114120
type Step = 'select-type' | 'configure'
@@ -125,6 +131,7 @@ export function AddConnectorModal({
125131
lockedAccessMode,
126132
initialSyncIntervalMinutes = 1440,
127133
onCreated,
134+
savedCredentialSetupEnabled = false,
128135
setupDraftKey,
129136
scope: explicitScope,
130137
}: AddConnectorModalProps) {
@@ -213,8 +220,14 @@ export function AddConnectorModal({
213220
const showGitLabPermissions = selectedType === 'gitlab' && access.accessMode === 'admin'
214221
const isMembersMode = access.accessMode === 'members'
215222
const apiKeyConfig = connectorConfig ? getConnectorApiKeyConfig(connectorConfig.auth) : undefined
223+
const savedCredentialConfig =
224+
savedCredentialSetupEnabled && connectorConfig?.auth.mode === 'apiKey'
225+
? connectorConfig.auth.savedCredential
226+
: undefined
216227
const isApiKeyMode =
217-
connectorConfig?.auth.mode === 'apiKey' || Boolean(apiKeyConfig && !isMembersMode && useApiKey)
228+
connectorConfig?.auth.mode === 'apiKey'
229+
? !savedCredentialConfig || useApiKey
230+
: Boolean(apiKeyConfig && !isMembersMode && useApiKey)
218231
const {
219232
integrationAvailability,
220233
oauthServiceAvailability,
@@ -249,10 +262,13 @@ export function AddConnectorModal({
249262
/** True when the connector declares its key optional (public sources need none). */
250263
const isApiKeyOptional =
251264
connectorConfig?.auth.mode === 'apiKey' && connectorConfig.auth.optional === true
252-
const connectorProviderId =
265+
const credentialServiceId =
253266
connectorConfig?.auth.mode === 'oauth'
254-
? (getProviderIdFromServiceId(connectorConfig.auth.provider) as OAuthProvider)
255-
: null
267+
? connectorConfig.auth.provider
268+
: savedCredentialConfig?.provider
269+
const connectorProviderId = credentialServiceId
270+
? (getProviderIdFromServiceId(credentialServiceId) as OAuthProvider)
271+
: null
256272

257273
const serviceAccountProviderId = connectorProviderId
258274
? getServiceAccountProviderForProviderId(connectorProviderId)
@@ -263,7 +279,7 @@ export function AddConnectorModal({
263279
!isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, 'oauth')
264280
const serviceAccountTarget = useServiceAccountConnectTarget({
265281
serviceAccountProviderId:
266-
isSearchIndex || requiresServiceAccount
282+
isSearchIndex || requiresServiceAccount || savedCredentialConfig
267283
? asServiceAccountProviderId(serviceAccountProviderId)
268284
: undefined,
269285
serviceName: connectorConfig?.name,
@@ -303,7 +319,7 @@ export function AddConnectorModal({
303319
isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, credential.type)
304320
)
305321
const canConnectOAuth =
306-
connectorConfig &&
322+
connectorConfig?.auth.mode === 'oauth' &&
307323
isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, 'oauth')
308324
const effectiveCredentialId =
309325
selectedCredentialId && credentials.some((credential) => credential.id === selectedCredentialId)
@@ -367,6 +383,7 @@ export function AddConnectorModal({
367383
)
368384
const isFieldVisible = (field: ConnectorConfigField) =>
369385
isConfigFieldVisible(field) &&
386+
isConnectorFieldForAuth(field, isApiKeyMode) &&
370387
(connectorConfig?.auth.mode !== 'oauth' ||
371388
connectorConfig.auth.serviceAccountSubjectFieldId !== field.id ||
372389
indexingCredential?.type === 'service_account')
@@ -541,14 +558,21 @@ export function AddConnectorModal({
541558
)
542559
)
543560

561+
/** Key-only settings typed before switching to a saved credential are never sent with it. */
562+
const keyOnlyFieldIds = new Set(
563+
(connectorConfig?.configFields ?? [])
564+
.filter((field) => !isConnectorFieldForAuth(field, isApiKeyMode))
565+
.map((field) => field.canonicalParamId ?? field.id)
566+
)
567+
544568
const handleSubmit = () => {
545569
if (!selectedType || !canSubmit) return
546570

547571
setError(null)
548572

549573
const resolvedConfig: Record<string, unknown> = {}
550574
for (const [key, value] of Object.entries(resolveSourceConfig())) {
551-
if (hiddenCapFieldIds.has(key)) continue
575+
if (hiddenCapFieldIds.has(key) || keyOnlyFieldIds.has(key)) continue
552576
if (Array.isArray(value)) {
553577
if (value.length > 0) resolvedConfig[key] = value
554578
} else if (typeof value === 'string') {
@@ -714,23 +738,28 @@ export function AddConnectorModal({
714738

715739
{!slackSetupRequired && (
716740
<>
717-
{connectorConfig.auth.mode === 'oauth' && apiKeyConfig && !isMembersMode && (
718-
<ChipModalField type='custom' title='Authentication'>
719-
<ChipCombobox
720-
disabled={isCreating}
721-
value={isApiKeyMode ? 'apiKey' : 'oauth'}
722-
options={[
723-
{ label: apiKeyConfig.label || 'API key', value: 'apiKey' },
724-
{ label: 'Connected account', value: 'oauth' },
725-
]}
726-
onChange={(value) => {
727-
setUseApiKey(value === 'apiKey')
728-
setApiKeyValue('')
729-
setSelectedCredentialId(null)
730-
}}
731-
/>
732-
</ChipModalField>
733-
)}
741+
{apiKeyConfig &&
742+
(savedCredentialConfig ||
743+
(connectorConfig.auth.mode === 'oauth' && !isMembersMode)) && (
744+
<ChipModalField type='custom' title='Authentication'>
745+
<ChipCombobox
746+
disabled={isCreating}
747+
value={isApiKeyMode ? 'apiKey' : 'oauth'}
748+
options={[
749+
{ label: apiKeyConfig.label || 'API key', value: 'apiKey' },
750+
{
751+
label: savedCredentialConfig?.label ?? 'Connected account',
752+
value: 'oauth',
753+
},
754+
]}
755+
onChange={(value) => {
756+
setUseApiKey(value === 'apiKey')
757+
setApiKeyValue('')
758+
setSelectedCredentialId(null)
759+
}}
760+
/>
761+
</ChipModalField>
762+
)}
734763
{isApiKeyMode ? (
735764
<ChipModalField type='custom' title={apiKeyConfig?.label || 'API Key'}>
736765
<ConnectorApiKeyInput
@@ -748,7 +777,7 @@ export function AddConnectorModal({
748777
? 'Account for browsing'
749778
: canConnectOAuth
750779
? 'Account'
751-
: 'Service account'
780+
: (savedCredentialConfig?.label ?? 'Service account')
752781
}
753782
>
754783
{credentialsError && rawCredentials.length === 0 ? (
@@ -801,7 +830,9 @@ export function AddConnectorModal({
801830
placeholder={
802831
canConnectOAuth
803832
? `Select ${connectorConfig.name} account`
804-
: 'Select a service account'
833+
: savedCredentialConfig
834+
? 'Select a credential'
835+
: 'Select a service account'
805836
}
806837
isLoading={credentialsLoading || isIntegrationAvailabilityLoading}
807838
disabled={isCreating || !isIntegrationAvailabilityReady}

0 commit comments

Comments
 (0)