Skip to content

Commit 698a8b3

Browse files
committed
fix(access-control): seed nothing restricted when the config is unknown
Block creation is one-shot, so the withholding pattern the editor's pickers use does not transfer: withholding the predicate there meant `prepareBlockState` seeded the declared `operation`/`model` defaults unchecked, and nothing revisits a field that already holds a value — so a block added before the permission config resolved kept a model the group may deny. Both restricted fields now seed empty until the config is known; the pickers fill them the moment it resolves. A preset operation is still honoured in that window: unlike a declared default it is the user's explicit pick, and the server gates the run.
1 parent 81146c9 commit 698a8b3

2 files changed

Lines changed: 28 additions & 9 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -896,13 +896,19 @@ const WorkflowContent = React.memo(
896896
if (extent) blockData.extent = extent
897897

898898
const operationGate = resolveOperationGate(getBlock(type))
899-
const seedGate = operationGate
900-
? (subBlockId: string, value: string) => {
901-
if (subBlockId === OPERATION_SUBBLOCK_ID) return operationGate(value)
902-
if (subBlockId === MODEL_SUBBLOCK_ID) return isModelUsable(value)
903-
return true
904-
}
905-
: undefined
899+
900+
/**
901+
* Creation is one-shot, so an unknown permission config cannot be
902+
* answered by waiting the way the editor's pickers do — a value written
903+
* here is never revisited. The restricted fields therefore seed empty
904+
* until the config resolves, and the pickers fill them the moment it
905+
* does. Seeding the declared default instead would persist it unchecked.
906+
*/
907+
const seedGate = (subBlockId: string, value: string) => {
908+
if (subBlockId !== OPERATION_SUBBLOCK_ID && subBlockId !== MODEL_SUBBLOCK_ID) return true
909+
if (!operationGate) return false
910+
return subBlockId === OPERATION_SUBBLOCK_ID ? operationGate(value) : isModelUsable(value)
911+
}
906912

907913
const block = prepareBlockState({
908914
id,
@@ -934,8 +940,9 @@ const WorkflowContent = React.memo(
934940
/* Search and the connection picker already drop denied operations, so
935941
this only catches one arriving by another route — a recent pick that
936942
outlived a permission change. Dropping the key rather than the whole
937-
preset leaves the permission-corrected default from
938-
`prepareBlockState` in place. */
943+
preset leaves whatever `prepareBlockState` seeded in place. Unlike a
944+
declared default, a preset is the user's explicit pick, so an
945+
unknown config honours it and leaves the server to gate the run. */
939946
const presetOperation = presetSubBlockValues[OPERATION_SUBBLOCK_ID]
940947
const presetOperationDenied =
941948
operationGate && typeof presetOperation === 'string' && !operationGate(presetOperation)

apps/sim/stores/workflows/utils.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,18 @@ describe('prepareBlockState — permission-group seed veto', () => {
11261126
expect(values.operation).toBe('send')
11271127
})
11281128

1129+
it('seeds nothing restricted when the gate answers no for an unknown config', () => {
1130+
/* Creation is one-shot: the caller vetoes both restricted fields while the
1131+
permission config is loading, since a value written here is never
1132+
revisited. Unrestricted fields still seed. */
1133+
const values = seededValues(
1134+
(subBlockId) => subBlockId !== 'operation' && subBlockId !== 'model'
1135+
)
1136+
expect(values.operation).toBeNull()
1137+
expect(values.model).toBeNull()
1138+
expect(values.channel).toBe('#general')
1139+
})
1140+
11291141
it('passes the seeded value to the gate, not just the field id', () => {
11301142
const seen: Array<[string, string]> = []
11311143
seededValues((subBlockId, value) => {

0 commit comments

Comments
 (0)