Skip to content

Commit 14bf9fc

Browse files
waleedlatif1claude
andcommitted
fix(quickbooks): stop persisting the Intuit identity token and share the webhook batch ceiling
The OIDC identity JWT is only meaningful at connection time, where profile.accountId is already derived from it. Persisting it projected the token into the credential payload of every QuickBooks tool call, none of which read it. Gating it in token-resolution instead would break Shopify, which reads params.idToken as a shop domain fallback. Also exports QUICKBOOKS_WEBHOOK_MAX_EVENTS from the contract so the route no longer carries a second copy of the batch ceiling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB
1 parent 9a91e4b commit 14bf9fc

4 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎apps/sim/app/api/webhooks/quickbooks/[appKey]/route.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { getErrorMessage } from '@sim/utils/errors'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import {
5+
QUICKBOOKS_WEBHOOK_MAX_EVENTS,
56
type QuickBooksWebhookEvent,
67
quickBooksWebhookEventSchema,
78
quickBooksWebhookParamsSchema,
@@ -24,8 +25,6 @@ import {
2425

2526
const logger = createLogger('QuickBooksWebhookIngress')
2627
const BODY_LABEL = 'QuickBooks webhook body'
27-
/** Mirrors the batch ceiling `quickBooksWebhookEventsSchema` declares for the same envelope. */
28-
const MAX_WEBHOOK_EVENTS = 1000
2928

3029
export const dynamic = 'force-dynamic'
3130
export const runtime = 'nodejs'
@@ -79,7 +78,11 @@ export const POST = withRouteHandler(
7978
} catch {
8079
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
8180
}
82-
if (!Array.isArray(json) || json.length === 0 || json.length > MAX_WEBHOOK_EVENTS) {
81+
if (
82+
!Array.isArray(json) ||
83+
json.length === 0 ||
84+
json.length > QUICKBOOKS_WEBHOOK_MAX_EVENTS
85+
) {
8386
logger.warn(`[${requestId}] Invalid QuickBooks webhook envelope`)
8487
return NextResponse.json({ error: 'Invalid webhook envelope' }, { status: 400 })
8588
}

‎apps/sim/lib/api/contracts/webhooks.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,13 @@ export const quickBooksWebhookEventSchema = z.object({
359359
data: z.unknown().optional(),
360360
})
361361

362-
export const quickBooksWebhookEventsSchema = z.array(quickBooksWebhookEventSchema).min(1).max(1000)
362+
/** Maximum CloudEvents Intuit batches into a single webhook delivery. */
363+
export const QUICKBOOKS_WEBHOOK_MAX_EVENTS = 1000
364+
365+
export const quickBooksWebhookEventsSchema = z
366+
.array(quickBooksWebhookEventSchema)
367+
.min(1)
368+
.max(QUICKBOOKS_WEBHOOK_MAX_EVENTS)
363369

364370
export type QuickBooksWebhookEvent = z.input<typeof quickBooksWebhookEventSchema>
365371

‎apps/sim/lib/credentials/application/complete-quickbooks-connection.test.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,30 @@ describe('completeQuickBooksConnection', () => {
159159
})
160160
})
161161

162+
it('never persists the Intuit identity token', async () => {
163+
queueTableRows(account, [])
164+
mocks.exchangeAuthorizationCode.mockResolvedValue({
165+
accessToken: 'access-token',
166+
refreshToken: 'refresh-token',
167+
idToken: 'intuit-oidc-identity-jwt',
168+
accessTokenExpiresIn: 3600,
169+
refreshTokenExpiresIn: 8_726_400,
170+
scope: '',
171+
})
172+
173+
await completeQuickBooksConnection.execute({
174+
principal,
175+
input: {
176+
draftId: 'draft-1',
177+
code: 'authorization-code',
178+
realmId: '1234567890',
179+
redirectUri: 'https://sim.test/api/auth/oauth2/callback/quickbooks',
180+
},
181+
})
182+
183+
expect(dbChainMockFns.values).toHaveBeenCalledWith(expect.objectContaining({ idToken: null }))
184+
})
185+
162186
it('fails before token exchange when the draft does not carry encrypted app credentials', async () => {
163187
mocks.getActiveDraft.mockResolvedValueOnce({
164188
id: 'draft-1',

‎apps/sim/lib/credentials/application/complete-quickbooks-connection.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,13 @@ export const completeQuickBooksConnection = defineAuthorizedWorkspaceUseCase({
9898
const accountValues = {
9999
accessToken: tokens.accessToken,
100100
refreshToken: tokens.refreshToken,
101-
idToken: tokens.idToken ?? null,
101+
/**
102+
* Intuit's OIDC identity JWT is only meaningful at connection time, where
103+
* `profile.accountId` is already derived from it. Persisting it would project
104+
* the token into the credential payload of every QuickBooks tool call, none of
105+
* which read it.
106+
*/
107+
idToken: null,
102108
accessTokenExpiresAt,
103109
refreshTokenExpiresAt,
104110
scope: tokens.scope || getCanonicalScopesForProvider('quickbooks').join(' '),

0 commit comments

Comments
 (0)