Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/docs/content/docs/cli/knowledge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,7 @@ sim knowledge connectors create <knowledgeBaseId> [options]
| Option | Required | Description |
| --- | --- | --- |
| `--connector-type <value>` | Yes | Registered connector type. |
| `--credential-id <value>` | No | OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types); omit it for `apiKey` connectors. |
| `--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. |
| `--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. |
| `--source-config <json\|@file>` | Yes | Connector-specific source selection and filtering configuration. (JSON, or @path / @- to read a file or stdin). |
| `--sync-interval-minutes <value>` | No | Scheduled synchronization interval in minutes; zero disables scheduling. Defaults to 1440. |
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2645,7 +2645,7 @@ sim knowledge connectors create <knowledgeBaseId> [options]
| Option | Required | Description |
| --- | --- | --- |
| `--connector-type <value>` | Yes | Registered connector type. |
| `--credential-id <value>` | No | OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types); omit it for `apiKey` connectors. |
| `--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. |
| `--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. |
| `--source-config <json\|@file>` | Yes | Connector-specific source selection and filtering configuration. (JSON, or @path / @- to read a file or stdin). |
| `--sync-interval-minutes <value>` | No | Scheduled synchronization interval in minutes; zero disables scheduling. Defaults to 1440. |
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/knowledgebase/connectors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ API-key and personal-access-token connections use the credential shown in their
| Azure DevOps | PAT with Project and Team Read, Wiki Read, Work Items Read, and Code Read. |
| GitLab | PAT with `read_api` and access to the selected project; permission-aware methods have additional requirements. |
| YouTube | YouTube Data API key from Google Cloud Console. |
| Amazon S3 | Secret Access Key; enter Access Key ID, region, and bucket as configuration fields. |
| 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. |
| Sentry | Auth token with `project:read` and `event:read`. |
| PagerDuty | Create a [REST API key](https://support.pagerduty.com/main/docs/api-access-keys) under **Integrations → Developer Tools → API Access Keys**. |
| SFTP | Password or unencrypted private key, plus a required SHA-256 host-key fingerprint; enter host, port, username, and root path as configuration fields. |
Expand Down
6 changes: 3 additions & 3 deletions apps/docs/openapi-v2-knowledge.json
Original file line number Diff line number Diff line change
Expand Up @@ -6933,7 +6933,7 @@
"type": "null"
}
],
"description": "OAuth credential identifier, or null for API-key and unauthenticated sources."
"description": "OAuth credential identifier, or the saved credential an `apiKey` source syncs with (see `auth.savedCredential`); null for API-key and unauthenticated sources."
},
"sourceConfig": {
"type": "object",
Expand Down Expand Up @@ -7155,7 +7155,7 @@
"description": "Registered connector type."
},
"credentialId": {
"description": "OAuth credential identifier for connector types whose `auth.mode` is `oauth` (see connector types); omit it for `apiKey` connectors.",
"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.",
"type": "string",
"minLength": 1,
"maxLength": 255
Expand Down Expand Up @@ -7331,7 +7331,7 @@
"type": "null"
}
],
"description": "OAuth credential identifier, or null for API-key and unauthenticated sources."
"description": "OAuth credential identifier, or the saved credential an `apiKey` source syncs with (see `auth.savedCredential`); null for API-key and unauthenticated sources."
},
"sourceConfig": {
"type": "object",
Expand Down
26 changes: 23 additions & 3 deletions apps/docs/openapi-v2-resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -21713,7 +21713,7 @@
"mode": {
"type": "string",
"const": "apiKey",
"description": "Authenticates with a stored API key; pass `apiKey`, and no `credentialId`, when creating the connector."
"description": "Authenticates with a stored API key; pass `apiKey` when creating the connector, or a saved `credentialId` instead when `savedCredential` is present."
},
"label": {
"description": "Label shown above the key field.",
Expand All @@ -21726,13 +21726,29 @@
"optional": {
"type": "boolean",
"description": "Whether the key may be left blank, for a source reachable without authentication."
},
"savedCredential": {
"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.",
"type": "object",
"properties": {
"provider": {
"type": "string",
"description": "Service the saved credential must authenticate with, e.g. `aws`."
},
"label": {
"type": "string",
"description": "Label for the saved-credential option."
}
},
"required": ["provider", "label"],
"additionalProperties": false
}
},
"required": ["mode", "optional"],
"additionalProperties": false
}
],
"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`."
"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`)."
},
"configFields": {
"type": "array",
Expand Down Expand Up @@ -21882,6 +21898,10 @@
"multi": {
"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.",
"type": "boolean"
},
"apiKeyOnly": {
"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.",
"type": "boolean"
}
},
"required": ["id", "title", "type"],
Expand Down Expand Up @@ -21910,7 +21930,7 @@
"mode": {
"type": "string",
"enum": ["oauth", "apiKey"],
"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`."
"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`)."
}
},
"required": ["mode"],
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import {
kbDocumentSortParams,
} from '@/app/workspace/[workspaceId]/knowledge/[id]/search-params'
import { canDeleteKnowledgeBase } from '@/app/workspace/[workspaceId]/knowledge/permissions'
import { useFeatureFlag } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider'
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { BrandIcon } from '@/blocks/brand-icon'
Expand Down Expand Up @@ -420,6 +421,7 @@ export function KnowledgeBase({
const [showDocumentTagsModal, setShowDocumentTagsModal] = useState(false)
const [documentForTagsId, setDocumentForTagsId] = useState<string | null>(null)
const showAddConnectorModal = addConnectorType != null
const savedCredentialSetupEnabled = useFeatureFlag('knowledge-connector-saved-credentials')
const updateAddConnectorParam = useCallback(
(value: string | null) => {
void setAddConnectorType(value, { history: 'replace', scroll: false })
Expand Down Expand Up @@ -1472,6 +1474,7 @@ export function KnowledgeBase({
knowledgeBaseId={id}
initialConnectorType={addConnectorType || undefined}
isSearchIndex={knowledgeBase?.isSearchIndex}
savedCredentialSetupEnabled={savedCredentialSetupEnabled}
/>
)}

Expand Down Expand Up @@ -1513,6 +1516,7 @@ export function KnowledgeBase({
isLoading={isLoadingConnectors}
canEdit={userPermissions.canEdit}
className='mt-0'
savedCredentialSetupEnabled={savedCredentialSetupEnabled}
/>
</ChipModalBody>
</ChipModal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ import {
} from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
import { withBrandIcon } from '@/blocks/brand-icon'
import { getConnectorApiKeyConfig, isConnectorCredentialTypeAllowed } from '@/connectors/auth'
import {
getConnectorApiKeyConfig,
isConnectorCredentialTypeAllowed,
isConnectorFieldForAuth,
} from '@/connectors/auth'
import { GitHubInstallationConnectionField } from '@/connectors/github/installation-connection-field'
import {
GitLabPermissionTabs,
Expand Down Expand Up @@ -109,6 +113,8 @@ interface AddConnectorModalProps {
initialSyncIntervalMinutes?: number
onCreated?: (connectorType: string, connector: ConnectorData) => void
setupDraftKey?: string
/** Offers a key connector's saved-credential alternative, such as a saved AWS credential for S3. */
savedCredentialSetupEnabled?: boolean
}

type Step = 'select-type' | 'configure'
Expand All @@ -125,6 +131,7 @@ export function AddConnectorModal({
lockedAccessMode,
initialSyncIntervalMinutes = 1440,
onCreated,
savedCredentialSetupEnabled = false,
setupDraftKey,
scope: explicitScope,
}: AddConnectorModalProps) {
Expand Down Expand Up @@ -213,8 +220,14 @@ export function AddConnectorModal({
const showGitLabPermissions = selectedType === 'gitlab' && access.accessMode === 'admin'
const isMembersMode = access.accessMode === 'members'
const apiKeyConfig = connectorConfig ? getConnectorApiKeyConfig(connectorConfig.auth) : undefined
const savedCredentialConfig =
savedCredentialSetupEnabled && connectorConfig?.auth.mode === 'apiKey'
? connectorConfig.auth.savedCredential
: undefined
const isApiKeyMode =
connectorConfig?.auth.mode === 'apiKey' || Boolean(apiKeyConfig && !isMembersMode && useApiKey)
connectorConfig?.auth.mode === 'apiKey'
? !savedCredentialConfig || useApiKey
: Boolean(apiKeyConfig && !isMembersMode && useApiKey)
const {
integrationAvailability,
oauthServiceAvailability,
Expand Down Expand Up @@ -249,10 +262,13 @@ export function AddConnectorModal({
/** True when the connector declares its key optional (public sources need none). */
const isApiKeyOptional =
connectorConfig?.auth.mode === 'apiKey' && connectorConfig.auth.optional === true
const connectorProviderId =
const credentialServiceId =
connectorConfig?.auth.mode === 'oauth'
? (getProviderIdFromServiceId(connectorConfig.auth.provider) as OAuthProvider)
: null
? connectorConfig.auth.provider
: savedCredentialConfig?.provider
const connectorProviderId = credentialServiceId
? (getProviderIdFromServiceId(credentialServiceId) as OAuthProvider)
: null

const serviceAccountProviderId = connectorProviderId
? getServiceAccountProviderForProviderId(connectorProviderId)
Expand All @@ -263,7 +279,7 @@ export function AddConnectorModal({
!isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, 'oauth')
const serviceAccountTarget = useServiceAccountConnectTarget({
serviceAccountProviderId:
isSearchIndex || requiresServiceAccount
isSearchIndex || requiresServiceAccount || savedCredentialConfig
? asServiceAccountProviderId(serviceAccountProviderId)
: undefined,
serviceName: connectorConfig?.name,
Expand Down Expand Up @@ -303,7 +319,7 @@ export function AddConnectorModal({
isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, credential.type)
)
const canConnectOAuth =
connectorConfig &&
connectorConfig?.auth.mode === 'oauth' &&
isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, 'oauth')
const effectiveCredentialId =
selectedCredentialId && credentials.some((credential) => credential.id === selectedCredentialId)
Expand Down Expand Up @@ -367,6 +383,7 @@ export function AddConnectorModal({
)
const isFieldVisible = (field: ConnectorConfigField) =>
isConfigFieldVisible(field) &&
isConnectorFieldForAuth(field, isApiKeyMode) &&
(connectorConfig?.auth.mode !== 'oauth' ||
connectorConfig.auth.serviceAccountSubjectFieldId !== field.id ||
indexingCredential?.type === 'service_account')
Expand Down Expand Up @@ -541,14 +558,21 @@ export function AddConnectorModal({
)
)

/** Key-only settings typed before switching to a saved credential are never sent with it. */
const keyOnlyFieldIds = new Set(
(connectorConfig?.configFields ?? [])
.filter((field) => !isConnectorFieldForAuth(field, isApiKeyMode))
.map((field) => field.canonicalParamId ?? field.id)
)

const handleSubmit = () => {
if (!selectedType || !canSubmit) return

setError(null)

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

{!slackSetupRequired && (
<>
{connectorConfig.auth.mode === 'oauth' && apiKeyConfig && !isMembersMode && (
<ChipModalField type='custom' title='Authentication'>
<ChipCombobox
disabled={isCreating}
value={isApiKeyMode ? 'apiKey' : 'oauth'}
options={[
{ label: apiKeyConfig.label || 'API key', value: 'apiKey' },
{ label: 'Connected account', value: 'oauth' },
]}
onChange={(value) => {
setUseApiKey(value === 'apiKey')
setApiKeyValue('')
setSelectedCredentialId(null)
}}
/>
</ChipModalField>
)}
{apiKeyConfig &&
(savedCredentialConfig ||
(connectorConfig.auth.mode === 'oauth' && !isMembersMode)) && (
<ChipModalField type='custom' title='Authentication'>
<ChipCombobox
disabled={isCreating}
value={isApiKeyMode ? 'apiKey' : 'oauth'}
options={[
{ label: apiKeyConfig.label || 'API key', value: 'apiKey' },
{
label: savedCredentialConfig?.label ?? 'Connected account',
value: 'oauth',
},
]}
onChange={(value) => {
setUseApiKey(value === 'apiKey')
setApiKeyValue('')
setSelectedCredentialId(null)
}}
/>
</ChipModalField>
)}
{isApiKeyMode ? (
<ChipModalField type='custom' title={apiKeyConfig?.label || 'API Key'}>
<ConnectorApiKeyInput
Expand All @@ -748,7 +777,7 @@ export function AddConnectorModal({
? 'Account for browsing'
: canConnectOAuth
? 'Account'
: 'Service account'
: (savedCredentialConfig?.label ?? 'Service account')
}
>
{credentialsError && rawCredentials.length === 0 ? (
Expand Down Expand Up @@ -801,7 +830,9 @@ export function AddConnectorModal({
placeholder={
canConnectOAuth
? `Select ${connectorConfig.name} account`
: 'Select a service account'
: savedCredentialConfig
? 'Select a credential'
: 'Select a service account'
}
isLoading={credentialsLoading || isIntegrationAvailabilityLoading}
disabled={isCreating || !isIntegrationAvailabilityReady}
Expand Down
Loading
Loading