Skip to content

Commit bf62b4a

Browse files
committed
fix(snowflake): list the key-pair connection in the credential provider catalog
1 parent e8f643f commit bf62b4a

2 files changed

Lines changed: 58 additions & 7 deletions

File tree

‎apps/sim/lib/credentials/application/provider-catalog-contract.test.ts‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,12 @@ describe('v2 credential provider catalog contract', () => {
145145
const snowflake = keyless.find(
146146
(provider) => provider.providerId === 'snowflake-service-account'
147147
)
148-
expect(snowflake?.fields.find((field) => field.id === 'serviceAccountJson')).toMatchObject({
149-
required: false,
150-
secret: true,
151-
})
148+
const snowflakeConfiguration = snowflake?.fields.find(
149+
(field) => field.id === 'serviceAccountJson'
150+
)
151+
expect(snowflakeConfiguration).toMatchObject({ required: false, secret: true })
152+
expect(snowflakeConfiguration?.hint).toContain('"key_pair"')
153+
expect(snowflakeConfiguration?.hint).toContain('Keyless')
152154
})
153155

154156
it('offers no keyless mode where this deployment cannot sign', async () => {
@@ -169,7 +171,32 @@ describe('v2 credential provider catalog contract', () => {
169171
(provider) =>
170172
provider.type === 'service_account' && provider.providerId === 'snowflake-service-account'
171173
)
172-
expect(snowflake?.fields.map((field) => field.id)).toEqual(['domain', 'apiToken'])
174+
expect(snowflake?.fields.map((field) => field.id)).toEqual([
175+
'domain',
176+
'apiToken',
177+
'serviceAccountJson',
178+
])
179+
const configuration = snowflake?.fields.find((field) => field.id === 'serviceAccountJson')
180+
expect(configuration).toMatchObject({ required: false, secret: true, multiline: true })
181+
expect(configuration?.hint).toContain('"key_pair"')
182+
expect(configuration?.hint).not.toContain('Keyless')
183+
})
184+
185+
it('offers the Snowflake key pair to organization credentials, which have no keyless mode', async () => {
186+
Reflect.set(env, 'WORKLOAD_IDENTITY_SIGNING_KEY', SIGNING_KEY)
187+
Reflect.set(env, 'WORKLOAD_IDENTITY_ISSUER', 'https://sim.example.com/oidc/workload')
188+
const catalog = await listCredentialProviderCatalog(createSessionPrincipal(), {
189+
organizationId: 'org-1',
190+
})
191+
const snowflake = catalog.find(
192+
(provider) =>
193+
provider.type === 'service_account' && provider.providerId === 'snowflake-service-account'
194+
)
195+
196+
expect(snowflake?.type === 'service_account' && snowflake.keyless).toBeFalsy()
197+
const configuration = snowflake?.fields.find((field) => field.id === 'serviceAccountJson')
198+
expect(configuration?.hint).toContain('"key_pair"')
199+
expect(configuration?.hint).not.toContain('Keyless')
173200
})
174201

175202
it('lists the native Claude Platform provider when filtered', async () => {

‎apps/sim/lib/credentials/application/provider-catalog.ts‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type ClientCredentialAccountField,
77
} from '@/lib/credentials/client-credential-accounts/descriptors'
88
import {
9+
SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID,
910
TOKEN_SERVICE_ACCOUNT_DESCRIPTORS,
1011
type TokenServiceAccountField,
1112
} from '@/lib/credentials/token-service-accounts/descriptors'
@@ -164,8 +165,8 @@ function keylessModeFor(providerId: string): KeylessCatalogMode | undefined {
164165
/**
165166
* Adds a provider's keyless mode to its key-based descriptor, the same way for
166167
* every keyless provider: one description suffix, and a hint on the
167-
* `serviceAccountJson` field — which a token provider such as Snowflake gains
168-
* as an optional alternative to its token fields.
168+
* `serviceAccountJson` field, which is added as an optional alternative to the
169+
* other fields when the descriptor has none.
169170
*/
170171
function withKeylessMode(
171172
descriptor: ServiceAccountDescriptor,
@@ -441,6 +442,29 @@ function getServiceAccountDescriptor(providerId: string): ServiceAccountDescript
441442
}
442443
}
443444

445+
if (providerId === SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID) {
446+
const tokenDescriptor = TOKEN_SERVICE_ACCOUNT_DESCRIPTORS[SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID]
447+
return {
448+
name: 'Snowflake connection',
449+
description: 'Connect Snowflake with a programmatic access token or an RSA key pair.',
450+
docsUrl: tokenDescriptor.docsUrl,
451+
helpText: tokenDescriptor.helpText,
452+
fields: [
453+
...tokenDescriptor.fields.map(providerField),
454+
{
455+
id: 'serviceAccountJson',
456+
label: 'Connection configuration',
457+
placeholder:
458+
'{"authType":"key_pair","host":"myorg-myaccount.snowflakecomputing.com","user":"SIM_SERVICE","privateKey":"-----BEGIN PRIVATE KEY-----..."}',
459+
required: false,
460+
secret: true,
461+
multiline: true,
462+
hint: 'Send instead of the other fields. Key pair: JSON object with authType: "key_pair", host, user, and privateKey (an unencrypted RSA private key as PEM, 2048 to 4096 bits, whose public key is set on the user).',
463+
},
464+
],
465+
}
466+
}
467+
444468
const tokenDescriptor = Object.hasOwn(TOKEN_SERVICE_ACCOUNT_DESCRIPTORS, providerId)
445469
? TOKEN_SERVICE_ACCOUNT_DESCRIPTORS[
446470
providerId as keyof typeof TOKEN_SERVICE_ACCOUNT_DESCRIPTORS

0 commit comments

Comments
 (0)