11'use client'
22
33import { useMemo } from 'react'
4+ import { useQuery } from '@tanstack/react-query'
45import { getEnv , isTruthy } from '@/lib/core/config/env'
56import { isAccessControlEnabled , isHosted } from '@/lib/core/config/feature-flags'
67import {
@@ -21,12 +22,39 @@ export interface PermissionConfigResult {
2122 isInvitationsDisabled : boolean
2223}
2324
25+ interface AllowedIntegrationsResponse {
26+ allowedIntegrations : string [ ] | null
27+ }
28+
29+ function useAllowedIntegrationsFromEnv ( ) {
30+ return useQuery < AllowedIntegrationsResponse > ( {
31+ queryKey : [ 'allowedIntegrations' , 'env' ] ,
32+ queryFn : async ( ) => {
33+ const response = await fetch ( '/api/settings/allowed-integrations' )
34+ if ( ! response . ok ) return { allowedIntegrations : null }
35+ return response . json ( )
36+ } ,
37+ staleTime : 5 * 60 * 1000 ,
38+ } )
39+ }
40+
41+ /**
42+ * Intersects two allowlists. If either is null (unrestricted), returns the other.
43+ * If both are set, returns only items present in both.
44+ */
45+ function intersectAllowlists ( a : string [ ] | null , b : string [ ] | null ) : string [ ] | null {
46+ if ( a === null ) return b
47+ if ( b === null ) return a
48+ return a . filter ( ( i ) => b . includes ( i ) )
49+ }
50+
2451export function usePermissionConfig ( ) : PermissionConfigResult {
2552 const accessControlDisabled = ! isHosted && ! isAccessControlEnabled
2653 const { data : organizationsData } = useOrganizations ( )
2754 const activeOrganization = organizationsData ?. activeOrganization
2855
2956 const { data : permissionData , isLoading } = useUserPermissionConfig ( activeOrganization ?. id )
57+ const { data : envAllowlistData } = useAllowedIntegrationsFromEnv ( )
3058
3159 const config = useMemo ( ( ) => {
3260 if ( accessControlDisabled ) {
@@ -40,13 +68,18 @@ export function usePermissionConfig(): PermissionConfigResult {
4068
4169 const isInPermissionGroup = ! accessControlDisabled && ! ! permissionData ?. permissionGroupId
4270
71+ const mergedAllowedIntegrations = useMemo ( ( ) => {
72+ const envAllowlist = envAllowlistData ?. allowedIntegrations ?? null
73+ return intersectAllowlists ( config . allowedIntegrations , envAllowlist )
74+ } , [ config . allowedIntegrations , envAllowlistData ] )
75+
4376 const isBlockAllowed = useMemo ( ( ) => {
4477 return ( blockType : string ) => {
4578 if ( blockType === 'start_trigger' ) return true
46- if ( config . allowedIntegrations === null ) return true
47- return config . allowedIntegrations . includes ( blockType )
79+ if ( mergedAllowedIntegrations === null ) return true
80+ return mergedAllowedIntegrations . includes ( blockType )
4881 }
49- } , [ config . allowedIntegrations ] )
82+ } , [ mergedAllowedIntegrations ] )
5083
5184 const isProviderAllowed = useMemo ( ( ) => {
5285 return ( providerId : string ) => {
@@ -57,13 +90,12 @@ export function usePermissionConfig(): PermissionConfigResult {
5790
5891 const filterBlocks = useMemo ( ( ) => {
5992 return < T extends { type : string } > ( blocks : T [ ] ) : T [ ] => {
60- if ( config . allowedIntegrations === null ) return blocks
93+ if ( mergedAllowedIntegrations === null ) return blocks
6194 return blocks . filter (
62- ( block ) =>
63- block . type === 'start_trigger' || config . allowedIntegrations ! . includes ( block . type )
95+ ( block ) => block . type === 'start_trigger' || mergedAllowedIntegrations . includes ( block . type )
6496 )
6597 }
66- } , [ config . allowedIntegrations ] )
98+ } , [ mergedAllowedIntegrations ] )
6799
68100 const filterProviders = useMemo ( ( ) => {
69101 return ( providerIds : string [ ] ) : string [ ] => {
0 commit comments