Skip to content

Commit 23a85f4

Browse files
committed
fix(credentials): cold-gate round — wire client-credential fields through the create route
The POST /api/credentials route never forwarded clientId/clientSecret/orgId to the secret builder, so client-credential service accounts could only be reconnected, never created — caught by the zero-context audit and now pinned by a route-level regression test (the suite this route never had). Also: minter hint dedupe, Salesforce openid scope guidance + exported host normalizer (kills a false modal hint on pasted URLs), Salesforce granted scopes persisted, Pipedrive file-download host allowlist for auth headers, 502 for provider_unavailable mint failures at the token route, and docs corrections attributing vendor error strings to vendor endpoints
1 parent f9e356a commit 23a85f4

14 files changed

Lines changed: 234 additions & 32 deletions

File tree

apps/docs/content/docs/en/integrations/box-service-account.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,14 @@ Sim's Box block includes Box Sign operations. These need the **Manage signature
118118

119119
## Token Behavior
120120

121-
Access tokens minted from the app are short-lived (typically one hour) and there is no refresh token — Sim simply mints a new token when one is needed. The stored Client ID, secret, and Enterprise ID stay valid until you rotate the secret in the Developer Console or the admin removes the app's authorization. If you rotate the secret, update the credential in Sim right away.
121+
Access tokens minted from the app are short-lived (typically one hour) and there is no refresh token — Sim simply mints a new token when one is needed. The stored Client ID, secret, and Enterprise ID stay valid until you rotate the secret in the Developer Console or the admin removes the app's authorization. If you rotate the secret, update the credential in Sim right away — reconnecting asks you to re-enter all three values.
122122

123123
<FAQ items={[
124124
{ question: "Why a Service Account instead of OAuth?", answer: "The Service Account is its own Box user, owned by the app — nothing expires when someone leaves or their login lapses. Access is granted folder by folder through collaborations, which makes the credential's reach explicit and auditable." },
125125
{ question: "The credential validates but every block returns nothing or 404s — why?", answer: "The Service Account's folder tree starts empty. It only sees folders it has been invited into as a collaborator. Invite its @boxdevedition.com email (shown on the app's General Settings tab) as an Editor on the folders your workflows use." },
126-
{ question: "Adding the credential fails with 'This app is not authorized by the enterprise admin' — what now?", answer: "A Box admin or co-admin must authorize the app in Admin Console → Apps → Platform Apps Manager (or Platform → Platform Apps in some tenants) by adding its Client ID. Until then, all token requests fail." },
126+
{ question: "Adding the credential fails and Box's token endpoint reports 'This app is not authorized by the enterprise admin' — what now?", answer: "Sim surfaces this as its general authentication error. A Box admin or co-admin must authorize the app in Admin Console → Apps → Platform Apps Manager (or Platform → Platform Apps in some tenants) by adding its Client ID. Until then, all token requests fail." },
127127
{ question: "I added a scope but tools still fail with 403 — why?", answer: "Authorization is a snapshot of the app's scopes at the time the admin approved it. After any scope or access-level change, the admin must re-authorize the app in Platform Apps Manager before the new scopes apply." },
128-
{ question: "It says 'The grant type is unauthorized for this client_id' — why?", answer: "The app was created with user authentication (OAuth 2.0) instead of Server Authentication. Create a new Platform App and choose Server Authentication (with Client Credentials Grant)." },
129-
{ question: "It says 'Grant credentials are invalid' — why?", answer: "Either the Client ID, Client secret, and Enterprise ID don't all belong to the same Box app and enterprise (most often a mismatched ID/secret pair), or the app hasn't been authorized in the Admin Console yet. Re-copy all three values from the same app in the Developer Console, and make sure an admin has authorized the app in Platform Apps Manager." },
128+
{ question: "Box rejects the token request with 'The grant type is unauthorized for this client_id' — why?", answer: "The app was created with user authentication (OAuth 2.0) instead of Server Authentication, and Sim reports it couldn't authenticate with those credentials. Create a new Platform App and choose Server Authentication (with Client Credentials Grant)." },
129+
{ question: "Box rejects the token request with 'Grant credentials are invalid' — why?", answer: "Either the Client ID, Client secret, and Enterprise ID don't all belong to the same Box app and enterprise (most often a mismatched ID/secret pair), or the app hasn't been authorized in the Admin Console yet. Re-copy all three values from the same app in the Developer Console, and make sure an admin has authorized the app in Platform Apps Manager." },
130130
{ question: "Do Box Sign operations work with a service account?", answer: "Yes, if the app has the Manage signature requests scope, the admin has (re-)authorized the app since it was added, and Box Sign is enabled on your enterprise plan. File and folder operations work regardless." },
131131
]} />

apps/docs/content/docs/en/integrations/pipedrive-service-account.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Each user has exactly **one** active API token per company. Regenerating it imme
2323

2424
<Steps>
2525
<Step>
26-
In Pipedrive, click your account name in the top right, then **Company settings** **Personal preferences****API** — or go directly to `https://app.pipedrive.com/settings/api`
26+
In Pipedrive, click your profile picture in the top right, then **Personal preferences****API** — or go directly to `https://app.pipedrive.com/settings/api`
2727
</Step>
2828
<Step>
2929
Copy **Your personal API token** (generate one if the field is empty)

apps/docs/content/docs/en/integrations/salesforce-service-account.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ External Client Apps are Salesforce's current-generation connected apps and the
5151
Enter any placeholder **Callback URL** (e.g. `https://login.salesforce.com/services/oauth2/callback`) — it's required by the form but unused by this flow
5252
</Step>
5353
<Step>
54-
Add the OAuth scope **Manage user data via APIs (api)**
54+
Add the OAuth scopes **Manage user data via APIs (api)** and **Access unique user identifiers (openid)**`openid` lets Sim look up the integration user's name and your org's instance URL
5555
</Step>
5656
<Step>
5757
Under **Flow Enablement**, check **Enable Client Credentials Flow**, acknowledge the warning, and create the app
@@ -95,10 +95,10 @@ If you already have a classic Connected App, it keeps working and the credential
9595

9696
<Steps>
9797
<Step>
98-
In **Setup****App Manager**, confirm the app has **Enable OAuth Settings**, the **Manage user data via APIs (api)** scope, and **Enable Client Credentials Flow** checked
98+
In **Setup****App Manager**, confirm the app has **Enable OAuth Settings**, the **Manage user data via APIs (api)** and **Access unique user identifiers (openid)** scopes, and **Enable Client Credentials Flow** checked
9999
</Step>
100100
<Step>
101-
From **App Manager**, open the app's **Manage** page, click **Edit Policies**, set **Permitted Users** to **Admin approved users are pre-authorized**, and set **Run As** under **Client Credentials Flow** to your integration user. If you pre-authorize via profiles or permission sets, also add the integration user's profile or permission set on the Manage page
101+
From **App Manager**, open the app's **Manage** page, click **Edit Policies**, and set **Run As** under **Client Credentials Flow** to your integration user. Permitted Users policies don't apply to the Client Credentials Flow's execution user, so no pre-authorization is needed
102102
</Step>
103103
<Step>
104104
Open the app with **View** and click **Manage Consumer Details** to copy the **Consumer Key** and **Consumer Secret**
@@ -117,7 +117,7 @@ Sim also accepts other partitioned My Domain hosts (`scratch`, `demo`, `patch`,
117117

118118
## Permissions Instead of Scopes
119119

120-
There's no scope picking beyond the single **api** scope on the app — what the credential can actually do is the integration user's profile plus permission sets. In particular:
120+
There's no scope picking beyond the **api** and **openid** scopes on the app — what the credential can actually do is the integration user's profile plus permission sets. In particular:
121121

122122
- Object and field access for every object your workflows read or write, plus **API Enabled**
123123
- **Customize Application** for tools that manage custom fields and custom objects (Tooling API) — a Minimum Access API-only user passes validation but fails these specific tools without it
@@ -142,7 +142,7 @@ A permissions gap surfaces at run time as a Salesforce API error; fix it on the
142142
{/* TODO(screenshot): Add Salesforce integration user app dialog with all three fields filled in */}
143143
</Step>
144144
<Step>
145-
Click **Add integration user app**. Sim verifies the credentials by minting a real access token against your My Domain host — if it fails, you'll see a specific error explaining what went wrong (bad consumer key or secret, a host that doesn't match, or a flow that isn't fully configured).
145+
Click **Add integration user app**. Sim verifies the credentials by minting a real access token against your My Domain host. A host that doesn't resolve gets its own error message; a bad consumer key or secret and a flow that isn't fully configured both surface as a general authentication error — re-check all three values and the app's Client Credentials Flow policies.
146146
</Step>
147147
</Steps>
148148

@@ -164,7 +164,7 @@ Deactivating or freezing the Run As user stops all token minting with an `invali
164164

165165
<FAQ items={[
166166
{ question: "Why an integration user instead of OAuth?", answer: "The credential authenticates as a dedicated API-only user, not a person — nothing expires when someone leaves or their login lapses. What it can access is exactly the integration user's profile and permission sets, which makes the credential's reach explicit and auditable." },
167-
{ question: "Adding the credential fails with 'no client credentials user enabled' — what now?", answer: "The app either doesn't have Enable Client Credentials Flow checked, or no Run As user is set. Edit the app's OAuth policies (External Client App Manager → your app → Policies, or a legacy Connected App's Manage → Edit Policies), enable the flow, and set Run As to your integration user." },
167+
{ question: "Adding the credential fails with an authentication error even though the key and secret are right — what now?", answer: "Salesforce likely rejected the token request with invalid_grant (e.g. 'no client credentials user enabled'), which Sim reports as not being able to authenticate with those credentials. The app either doesn't have Enable Client Credentials Flow checked, or no Run As user is set. Edit the app's OAuth policies (External Client App Manager → your app → Policies, or a legacy Connected App's Manage → Edit Policies), enable the flow, and set Run As to your integration user." },
168168
{ question: "Setup → App Manager → New Connected App is greyed out — why?", answer: "Salesforce disabled new Connected App creation by default (new orgs since Summer '25, all orgs since Spring '26); re-enabling it requires a Salesforce Support request. You don't need it — create an External Client App instead (Setup → External Client App Manager), which supports the same Client Credentials Flow and produces the same consumer key and secret." },
169169
{ question: "Why do I have to enter a My Domain host instead of login.salesforce.com?", answer: "Salesforce only supports the Client Credentials Flow at your org's My Domain URL — login.salesforce.com and test.salesforce.com are explicitly rejected for this flow. Find yours under Setup → My Domain." },
170170
{ question: "The credential validates but a specific tool fails with a permission error — why?", answer: "Every call runs as the Run As user, so the failing tool needs a permission that user doesn't have. Common gaps: Customize Application for custom field/object tools, Run Reports plus folder access for report tools, and object or field permissions for the records involved. Fix it on the integration user's permission sets and re-run." },

apps/docs/content/docs/en/integrations/zoom-service-account.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ Access tokens minted from the app live for one hour and there is no refresh toke
142142

143143
<FAQ items={[
144144
{ question: "Why a Server-to-Server OAuth app instead of OAuth?", answer: "The app authenticates as your Zoom account, not as a person — nothing expires when someone leaves or their login lapses. An admin grants it explicit scopes once, and Sim mints short-lived tokens from the stored credentials whenever a workflow runs." },
145-
{ question: "My token mint fails with 'bad request' — why?", answer: "The most common cause is pasting the numeric account number from the Zoom web portal instead of the Account ID from the app's App Credentials page. They are different values — the App Credentials Account ID is an opaque string. Copy all three values from the App Credentials page." },
146-
{ question: "Zoom rejects my credentials even though I copied them correctly — why?", answer: "A common cause is that the Client ID and secret belong to a regular OAuth app, not a Server-to-Server OAuth app — Zoom reports this as an unsupported grant type. Only Server-to-Server OAuth apps support account-level token minting — create one under Develop → Build an app in the Zoom Marketplace. An app that hasn't been activated yet is rejected the same way." },
145+
{ question: "Zoom rejects the token request with 'bad request' — why?", answer: "Sim surfaces this as its general authentication error. The most common cause is pasting the numeric account number from the Zoom web portal instead of the Account ID from the app's App Credentials page. They are different values — the App Credentials Account ID is an opaque string. Copy all three values from the App Credentials page." },
146+
{ question: "Zoom rejects my credentials even though I copied them correctly — why?", answer: "A common cause is that the Client ID and secret belong to a regular OAuth app, not a Server-to-Server OAuth app — Zoom's token endpoint reports this as an unsupported grant type. Only Server-to-Server OAuth apps support account-level token minting — create one under Develop → Build an app in the Zoom Marketplace. An app that hasn't been activated yet also can't mint tokens — activate it in the Marketplace first." },
147147
{ question: "Why does my create-meeting block fail with an error about client credentials not being supported?", answer: "The block's user field is set to 'me', which only works with a personal OAuth connection. Service accounts have no user context — enter the target Zoom user's email address or user ID instead." },
148148
{ question: "My workflows suddenly fail with scope errors but nothing changed — why?", answer: "Zoom automatically strips admin-level scopes from the app if the admin who created it loses the matching role permissions. Check the app's Scopes section in the Marketplace, re-add what's missing (from an admin with sufficient permissions), and re-run." },
149149
{ question: "How do I rotate the credentials?", answer: "Regenerate the Client secret on the app's App Credentials page in the Zoom Marketplace, then update the credential in Sim with the new secret. Note that the old secret stops working as soon as it's regenerated, so update Sim promptly." },

apps/sim/app/api/auth/oauth/token/route.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access'
1111
import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
1212
import { generateRequestId } from '@/lib/core/utils/request'
1313
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
14+
import { TokenServiceAccountValidationError } from '@/lib/credentials/token-service-accounts/errors'
1415
import { captureServerEvent } from '@/lib/posthog/server'
1516
import {
1617
getCredential,
@@ -187,6 +188,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
187188
)
188189
} catch (error) {
189190
logger.error(`[${requestId}] Service account token error:`, error)
191+
// Classified provider outages are infra failures, not bad credentials.
192+
if (
193+
error instanceof TokenServiceAccountValidationError &&
194+
error.code === 'provider_unavailable'
195+
) {
196+
return NextResponse.json(
197+
{ error: 'Credential provider is temporarily unavailable' },
198+
{ status: 502 }
199+
)
200+
}
190201
return NextResponse.json({ error: 'Failed to get service account token' }, { status: 401 })
191202
}
192203
}

0 commit comments

Comments
 (0)