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
24 changes: 1 addition & 23 deletions apps/sim/blocks/blocks/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,6 @@ import { normalizeFileInput } from '@/blocks/utils'
import type { SlackResponse } from '@/tools/slack/types'
import { getTrigger } from '@/triggers'

/**
* Scopes added for the native Sim Slack app trigger (`slack_oauth`): the shared
* app's `app_mention`, assistant-thread, and DM events don't deliver without
* them. Advertised by `slack_v2` and the trigger only — the legacy block has no
* feature that needs them, and listing them there would flag every existing
* Slack credential as missing scopes and prompt a reconnect.
*
* This only controls what each picker *advertises* and treats as missing. The
* authorization request itself is provider-wide
* (`getCanonicalScopesForProvider('slack')`), so any reconnect grants the full
* set regardless of which block started it.
*/
const SLACK_V2_ONLY_SCOPES = new Set(['app_mentions:read', 'assistant:write', 'im:history'])

/** Slack scopes the legacy v1 block advertises — the set from before the trigger expansion. */
const SLACK_V1_ADVERTISED_SCOPES = getScopesForService('slack').filter(
(scope) => !SLACK_V2_ONLY_SCOPES.has(scope)
)

export const SlackBlock: BlockConfig<SlackResponse> = {
type: 'slack',
name: 'Slack',
Expand Down Expand Up @@ -126,7 +107,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
canonicalParamId: 'oauthCredential',
mode: 'basic',
serviceId: 'slack',
requiredScopes: SLACK_V1_ADVERTISED_SCOPES,
requiredScopes: getScopesForService('slack'),
placeholder: 'Select Slack workspace',
dependsOn: ['authMethod'],
condition: {
Expand Down Expand Up @@ -2661,9 +2642,6 @@ function adaptSubBlockForV2(sb: SubBlockConfig): SubBlockConfig {
...rest,
credentialKind: 'any',
placeholder: 'Select Slack account or bot',
// Full set, unlike v1: v2 hosts the native Sim app trigger, whose events
// need the mention/assistant/DM scopes.
requiredScopes: getScopesForService('slack'),
credentialLabels: {
oauthGroup: 'Sim app',
oauthConnect: 'Connect the Sim app',
Expand Down
22 changes: 22 additions & 0 deletions apps/sim/lib/core/config/env-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@ export const isSignupMxValidationEnabled = isTruthy(env.SIGNUP_MX_VALIDATION_ENA
export const isAppConfigEnabled =
isHosted && Boolean(env.APPCONFIG_APPLICATION && env.APPCONFIG_ENVIRONMENT)

/**
* Whether the deployment's Slack app is approved for `app_mentions:read`,
* `assistant:write`, and `im:history` — the scopes backing the native Sim app
* trigger's mention, assistant-thread, and DM events.
*
* Off by default because Slack rejects the ENTIRE authorization when it requests
* a scope the app is not approved for, breaking every Slack connect. Sim Cloud's
* app is directory-listed and pinned to its review-approved set, so this stays
* off there until review lands; a self-hosted deployment pointing at its own
* (unlisted) Slack app can opt in.
*
* Server code reads `SLACK_EXTENDED_SCOPES`. Server-only vars never reach browser
* bundles, so client evaluation reads the `NEXT_PUBLIC_SLACK_EXTENDED_SCOPES`
* twin (see {@link isBillingEnabled}) — deployments must set both together. The
* server value decides the grant; the client value only decides what the credential
* pickers advertise and treat as missing.
*/
export const isSlackExtendedScopesEnabled =
typeof window === 'undefined'
? isTruthy(env.SLACK_EXTENDED_SCOPES)
: isTruthy(getEnv('NEXT_PUBLIC_SLACK_EXTENDED_SCOPES'))

/**
* Is Trigger.dev enabled for async job processing
*/
Expand Down
3 changes: 3 additions & 0 deletions apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ export const env = createEnv({
SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID
SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret
SLACK_SIGNING_SECRET: z.string().optional(), // Official Sim Slack app signing secret (verifies inbound events for the native OAuth trigger)
SLACK_EXTENDED_SCOPES: z.boolean().optional(), // Request app_mentions:read, assistant:write, im:history — only where the Slack app is approved for them
REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID
REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret
WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID
Expand Down Expand Up @@ -571,6 +572,7 @@ export const env = createEnv({
// Feature Flags
NEXT_PUBLIC_SSO_ENABLED: z.boolean().optional(), // Enable SSO login UI components
NEXT_PUBLIC_ACCESS_CONTROL_ENABLED: z.boolean().optional(), // Enable access control (permission groups) on self-hosted
NEXT_PUBLIC_SLACK_EXTENDED_SCOPES: z.boolean().optional(), // Client twin of SLACK_EXTENDED_SCOPES — set both together
NEXT_PUBLIC_CUSTOM_BLOCKS_ENABLED: z.boolean().optional(), // Enable custom blocks (deploy-as-block) settings on self-hosted
NEXT_PUBLIC_WHITELABELING_ENABLED: z.boolean().optional(), // Enable whitelabeling on self-hosted (bypasses hosted requirements)
NEXT_PUBLIC_AUDIT_LOGS_ENABLED: z.boolean().optional(), // Enable audit logs on self-hosted (bypasses hosted requirements)
Expand Down Expand Up @@ -612,6 +614,7 @@ export const env = createEnv({
NEXT_PUBLIC_BRAND_BACKGROUND_COLOR: process.env.NEXT_PUBLIC_BRAND_BACKGROUND_COLOR,
NEXT_PUBLIC_SSO_ENABLED: process.env.NEXT_PUBLIC_SSO_ENABLED,
NEXT_PUBLIC_ACCESS_CONTROL_ENABLED: process.env.NEXT_PUBLIC_ACCESS_CONTROL_ENABLED,
NEXT_PUBLIC_SLACK_EXTENDED_SCOPES: process.env.NEXT_PUBLIC_SLACK_EXTENDED_SCOPES,
NEXT_PUBLIC_CUSTOM_BLOCKS_ENABLED: process.env.NEXT_PUBLIC_CUSTOM_BLOCKS_ENABLED,
NEXT_PUBLIC_WHITELABELING_ENABLED: process.env.NEXT_PUBLIC_WHITELABELING_ENABLED,
NEXT_PUBLIC_AUDIT_LOGS_ENABLED: process.env.NEXT_PUBLIC_AUDIT_LOGS_ENABLED,
Expand Down
15 changes: 12 additions & 3 deletions apps/sim/lib/oauth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ZoomIcon,
} from '@/components/icons'
import { env } from '@/lib/core/config/env'
import { isSlackExtendedScopesEnabled } from '@/lib/core/config/env-flags'
import {
DEFAULT_MAX_ERROR_BODY_BYTES,
readResponseTextWithLimit,
Expand All @@ -66,6 +67,16 @@ import type { OAuthProviderConfig } from './types'

const logger = createLogger('OAuth')

/**
* Slack scopes requested only where the app is approved for them, gated by
* {@link isSlackExtendedScopesEnabled}. Slack rejects the entire authorization
* with "unapproved permissions requested" when any requested scope is not on the
* app's approved list, so these stay out of the default grant.
*/
const SLACK_APPROVAL_GATED_SCOPES = isSlackExtendedScopesEnabled
? (['assistant:write', 'app_mentions:read', 'im:history'] as const)
: ([] as const)

export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
'claude-platform': {
name: 'Claude Platform',
Expand Down Expand Up @@ -782,9 +793,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
'groups:write',
'chat:write',
'chat:write.public',
'assistant:write',
'app_mentions:read',
'im:history',
...SLACK_APPROVAL_GATED_SCOPES,
'im:write',
'im:read',
'users:read',
Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/mocks/env-flags.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface EnvFlagsMockState {
isEmailPasswordEnabled: boolean
isSignupMxValidationEnabled: boolean
isAppConfigEnabled: boolean
isSlackExtendedScopesEnabled: boolean
isTriggerDevEnabled: boolean
isSsoEnabled: boolean
isAccessControlEnabled: boolean
Expand Down Expand Up @@ -61,6 +62,7 @@ const defaultEnvFlagsState: EnvFlagsMockState = {
isEmailPasswordEnabled: true,
isSignupMxValidationEnabled: false,
isAppConfigEnabled: false,
isSlackExtendedScopesEnabled: false,
isTriggerDevEnabled: false,
isSsoEnabled: false,
isAccessControlEnabled: false,
Expand Down
Loading