Skip to content

Commit d79c22d

Browse files
committed
fix(salesforce): carry alternate provider ids through chat connect verification
The chip's live target was widened to match a sandbox credential, but the post-connect verification leg re-reads the STORED attempt, which did not carry the ids — so completing a sandbox connect from Chat was detected as a failure and the chip was marked failed. The attempt now persists them; attempts written before this simply match as they did, and they expire within 15 minutes. Also marks the auth-method picker required while it is the field blocking submit on a reconnect, so the greyed button has a visible cause.
1 parent 3b04abc commit d79c22d

4 files changed

Lines changed: 46 additions & 3 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/use-oauth-chip-connection.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ export function useOAuthChipConnection({
479479
workspaceId,
480480
providerId,
481481
baseProviderId,
482+
additionalProviderIds: credentialTarget.additionalProviderIds,
482483
displayName,
483484
controlId,
484485
credentialId: reconnectCredentialId,

apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/client-credential-account-modal.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,12 @@ export function ClientCredentialAccountModal({
139139
const visibleFields = mustRestateAuthMethod
140140
? visible.filter((field) => !field.requiredForAuthMethods)
141141
: visible
142-
// Markers always reflect the descriptor's real requirements, so `clientId`
143-
// and the host don't lose their asterisk while the method is unset. Submit is
144-
// gated separately below — picking a method is what unblocks it.
142+
// Markers reflect the descriptor's real requirements, so `clientId` and the
143+
// host keep their asterisk while the method is unset — plus the picker itself
144+
// while it is the thing blocking submit, so the greyed button has a visible
145+
// cause.
145146
const requiredFieldIds = new Set(required.map((field) => field.id))
147+
if (mustRestateAuthMethod) requiredFieldIds.add(AUTH_METHOD_FIELD_ID)
146148
/**
147149
* On a create the form already behaves as the descriptor's default grant, so
148150
* the picker shows it rather than an empty placeholder implying no choice.

apps/sim/lib/credentials/oauth-chat-attempt.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,32 @@ describe('credential matching for a chat chip', () => {
311311
).toBe(false)
312312
})
313313
})
314+
315+
describe('attempt carries the alternate provider ids through verification', () => {
316+
it('lets hasOAuthCredentialChanged see a credential from an alternate server', () => {
317+
// The post-connect leg re-reads the STORED attempt, not the live target, so
318+
// the ids must survive the round trip or a sandbox connect reads as failed.
319+
const attempt = createOAuthChatAttempt({
320+
workspaceId: 'workspace-1',
321+
providerId: 'salesforce',
322+
baseProviderId: 'salesforce',
323+
additionalProviderIds: ['salesforce-sandbox'],
324+
displayName: 'Salesforce',
325+
controlId: 'control-1',
326+
baselineCredentialIds: [],
327+
})
328+
329+
const stored = readOAuthChatAttempt(attempt.id)
330+
expect(stored?.additionalProviderIds).toEqual(['salesforce-sandbox'])
331+
332+
expect(
333+
hasOAuthCredentialChanged(stored as NonNullable<typeof stored>, [
334+
{
335+
id: 'cred-sandbox',
336+
providerId: 'salesforce-sandbox',
337+
updatedAt: '2026-08-11T00:00:00.000Z',
338+
},
339+
])
340+
).toBe(true)
341+
})
342+
})

apps/sim/lib/credentials/oauth-chat-attempt.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ export interface OAuthChatAttempt {
4343
workspaceId: string
4444
providerId: string
4545
baseProviderId: string
46+
/**
47+
* See {@link OAuthCredentialTarget.additionalProviderIds}. Persisted on the
48+
* attempt because the post-connect verification leg re-reads the stored
49+
* attempt rather than the live target — without it, a credential from an
50+
* alternate authorization server would not register as "connected".
51+
* Absent on attempts written before this existed; those simply match as they
52+
* did, and attempts expire after {@link OAUTH_CHAT_ATTEMPT_MAX_AGE_MS}.
53+
*/
54+
additionalProviderIds?: readonly string[]
4655
displayName: string
4756
controlId: string
4857
credentialId?: string
@@ -56,6 +65,8 @@ interface CreateOAuthChatAttemptInput {
5665
workspaceId: string
5766
providerId: string
5867
baseProviderId: string
68+
/** See {@link OAuthCredentialTarget.additionalProviderIds}. */
69+
additionalProviderIds?: readonly string[]
5970
displayName: string
6071
controlId: string
6172
credentialId?: string

0 commit comments

Comments
 (0)