mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-19 09:30:49 +08:00
fix(slack): stop requesting the unapproved scopes, gate them behind an opt-in flag (#5977)
* fix(slack): stop requesting the unapproved mention/assistant/DM scopes * feat(slack): gate the approval-pending scopes behind an opt-in env flag
This commit is contained in:
@@ -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',
|
||||
@@ -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: {
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -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',
|
||||
@@ -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',
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface EnvFlagsMockState {
|
||||
isEmailPasswordEnabled: boolean
|
||||
isSignupMxValidationEnabled: boolean
|
||||
isAppConfigEnabled: boolean
|
||||
isSlackExtendedScopesEnabled: boolean
|
||||
isTriggerDevEnabled: boolean
|
||||
isSsoEnabled: boolean
|
||||
isAccessControlEnabled: boolean
|
||||
@@ -61,6 +62,7 @@ const defaultEnvFlagsState: EnvFlagsMockState = {
|
||||
isEmailPasswordEnabled: true,
|
||||
isSignupMxValidationEnabled: false,
|
||||
isAppConfigEnabled: false,
|
||||
isSlackExtendedScopesEnabled: false,
|
||||
isTriggerDevEnabled: false,
|
||||
isSsoEnabled: false,
|
||||
isAccessControlEnabled: false,
|
||||
|
||||
Reference in New Issue
Block a user