@teleporthq/teleport-plugin-next-workflows 0.43.46 → 0.43.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/__tests__/auth-env-secret-preservation.test.ts +35 -0
  2. package/__tests__/auth-nextauth-url.test.ts +28 -0
  3. package/__tests__/auth-session-cost.test.ts +614 -0
  4. package/__tests__/custom-js-params-env-invariance.test.ts +134 -0
  5. package/__tests__/data-node-not-awaited.test.ts +644 -0
  6. package/__tests__/element-trigger-static-attr-replace.test.ts +99 -0
  7. package/__tests__/element-visible-trigger-scoping.test.ts +296 -0
  8. package/__tests__/payment-charge-user-handler-serialization.test.ts +7 -5
  9. package/__tests__/payment-redirect-and-order-notification.test.ts +6 -4
  10. package/__tests__/raw-sql-param-binding.test.ts +82 -0
  11. package/__tests__/reserved-word-state-binding.test.ts +112 -0
  12. package/__tests__/runtime-stringified-boolean-truthiness.test.ts +76 -0
  13. package/__tests__/strip-event-handler-attrs.test.ts +59 -0
  14. package/__tests__/workflow-auth-guard.test.ts +277 -0
  15. package/dist/cjs/api-route-generator.d.ts +3 -3
  16. package/dist/cjs/api-route-generator.d.ts.map +1 -1
  17. package/dist/cjs/api-route-generator.js +136 -11
  18. package/dist/cjs/api-route-generator.js.map +1 -1
  19. package/dist/cjs/auth-generator.d.ts +67 -1
  20. package/dist/cjs/auth-generator.d.ts.map +1 -1
  21. package/dist/cjs/auth-generator.js +458 -20
  22. package/dist/cjs/auth-generator.js.map +1 -1
  23. package/dist/cjs/await-result.d.ts +31 -0
  24. package/dist/cjs/await-result.d.ts.map +1 -0
  25. package/dist/cjs/await-result.js +50 -0
  26. package/dist/cjs/await-result.js.map +1 -0
  27. package/dist/cjs/executor-generator.d.ts.map +1 -1
  28. package/dist/cjs/executor-generator.js +162 -3
  29. package/dist/cjs/executor-generator.js.map +1 -1
  30. package/dist/cjs/nodes/account/account-get-current.d.ts.map +1 -1
  31. package/dist/cjs/nodes/account/account-get-current.js +44 -11
  32. package/dist/cjs/nodes/account/account-get-current.js.map +1 -1
  33. package/dist/cjs/nodes/account/account-login.d.ts.map +1 -1
  34. package/dist/cjs/nodes/account/account-login.js +10 -1
  35. package/dist/cjs/nodes/account/account-login.js.map +1 -1
  36. package/dist/cjs/raw-sql-param-binding.d.ts.map +1 -1
  37. package/dist/cjs/raw-sql-param-binding.js +32 -0
  38. package/dist/cjs/raw-sql-param-binding.js.map +1 -1
  39. package/dist/cjs/segment-splitter.d.ts.map +1 -1
  40. package/dist/cjs/segment-splitter.js +15 -7
  41. package/dist/cjs/segment-splitter.js.map +1 -1
  42. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  43. package/dist/cjs/workflow-auth-generator.d.ts +26 -0
  44. package/dist/cjs/workflow-auth-generator.d.ts.map +1 -0
  45. package/dist/cjs/workflow-auth-generator.js +166 -0
  46. package/dist/cjs/workflow-auth-generator.js.map +1 -0
  47. package/dist/cjs/workflow-component-plugin.d.ts.map +1 -1
  48. package/dist/cjs/workflow-component-plugin.js +107 -16
  49. package/dist/cjs/workflow-component-plugin.js.map +1 -1
  50. package/dist/cjs/workflow-project-plugin.d.ts +2 -1
  51. package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
  52. package/dist/cjs/workflow-project-plugin.js +170 -20
  53. package/dist/cjs/workflow-project-plugin.js.map +1 -1
  54. package/dist/esm/api-route-generator.d.ts +3 -3
  55. package/dist/esm/api-route-generator.d.ts.map +1 -1
  56. package/dist/esm/api-route-generator.js +136 -11
  57. package/dist/esm/api-route-generator.js.map +1 -1
  58. package/dist/esm/auth-generator.d.ts +67 -1
  59. package/dist/esm/auth-generator.d.ts.map +1 -1
  60. package/dist/esm/auth-generator.js +453 -19
  61. package/dist/esm/auth-generator.js.map +1 -1
  62. package/dist/esm/await-result.d.ts +31 -0
  63. package/dist/esm/await-result.d.ts.map +1 -0
  64. package/dist/esm/await-result.js +45 -0
  65. package/dist/esm/await-result.js.map +1 -0
  66. package/dist/esm/executor-generator.d.ts.map +1 -1
  67. package/dist/esm/executor-generator.js +162 -3
  68. package/dist/esm/executor-generator.js.map +1 -1
  69. package/dist/esm/nodes/account/account-get-current.d.ts.map +1 -1
  70. package/dist/esm/nodes/account/account-get-current.js +44 -11
  71. package/dist/esm/nodes/account/account-get-current.js.map +1 -1
  72. package/dist/esm/nodes/account/account-login.d.ts.map +1 -1
  73. package/dist/esm/nodes/account/account-login.js +10 -1
  74. package/dist/esm/nodes/account/account-login.js.map +1 -1
  75. package/dist/esm/raw-sql-param-binding.d.ts.map +1 -1
  76. package/dist/esm/raw-sql-param-binding.js +32 -0
  77. package/dist/esm/raw-sql-param-binding.js.map +1 -1
  78. package/dist/esm/segment-splitter.d.ts.map +1 -1
  79. package/dist/esm/segment-splitter.js +15 -7
  80. package/dist/esm/segment-splitter.js.map +1 -1
  81. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  82. package/dist/esm/workflow-auth-generator.d.ts +26 -0
  83. package/dist/esm/workflow-auth-generator.d.ts.map +1 -0
  84. package/dist/esm/workflow-auth-generator.js +161 -0
  85. package/dist/esm/workflow-auth-generator.js.map +1 -0
  86. package/dist/esm/workflow-component-plugin.d.ts.map +1 -1
  87. package/dist/esm/workflow-component-plugin.js +108 -17
  88. package/dist/esm/workflow-component-plugin.js.map +1 -1
  89. package/dist/esm/workflow-project-plugin.d.ts +2 -1
  90. package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
  91. package/dist/esm/workflow-project-plugin.js +169 -20
  92. package/dist/esm/workflow-project-plugin.js.map +1 -1
  93. package/package.json +4 -4
  94. package/src/api-route-generator.ts +139 -11
  95. package/src/auth-generator.ts +471 -19
  96. package/src/await-result.ts +52 -0
  97. package/src/executor-generator.ts +162 -3
  98. package/src/nodes/account/account-get-current.ts +44 -9
  99. package/src/nodes/account/account-login.ts +12 -1
  100. package/src/raw-sql-param-binding.ts +33 -0
  101. package/src/segment-splitter.ts +15 -7
  102. package/src/workflow-auth-generator.ts +187 -0
  103. package/src/workflow-component-plugin.ts +129 -20
  104. package/src/workflow-project-plugin.ts +186 -19
@@ -0,0 +1,76 @@
1
+ import { generateSharedRuntimeUtilsCode } from '../src'
2
+
3
+ // Producers that cannot type their output emit booleans as the strings
4
+ // "true"/"false" — the evaluate-auth custom-js most notably (see
5
+ // is-logged-in-gate.ts), plus any inspector text input. `is-true`/`is-false`
6
+ // and coerceForComparison already read those as booleans, but `is-truthy` was
7
+ // plain `!!left`, and the string "false" is truthy in JS: an is-truthy gate on
8
+ // isLoggedIn admitted EVERY visitor, and an is-falsy check never fired.
9
+
10
+ type SharedUtils = {
11
+ evaluateSingleComparison: (cfg: unknown, ctx: Record<string, unknown>) => boolean
12
+ }
13
+
14
+ function loadSharedRuntime(): SharedUtils {
15
+ const src = generateSharedRuntimeUtilsCode()
16
+ const wrapper: { exports: Record<string, unknown> } = { exports: {} }
17
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
18
+ new Function('module', 'exports', src)(wrapper, wrapper.exports)
19
+ return wrapper.exports as unknown as SharedUtils
20
+ }
21
+
22
+ describe('workflow runtime truthiness of stringified booleans', () => {
23
+ const utils = loadSharedRuntime()
24
+
25
+ const isTruthy = (leftValue: unknown) =>
26
+ utils.evaluateSingleComparison({ leftValue, operator: 'is-truthy' }, {})
27
+ const isFalsy = (leftValue: unknown) =>
28
+ utils.evaluateSingleComparison({ leftValue, operator: 'is-falsy' }, {})
29
+
30
+ it('treats the string "false" as false, matching is-false and coerceForComparison', () => {
31
+ expect(isTruthy('false')).toBe(false)
32
+ expect(isFalsy('false')).toBe(true)
33
+ })
34
+
35
+ it('keeps the string "true" true', () => {
36
+ expect(isTruthy('true')).toBe(true)
37
+ expect(isFalsy('true')).toBe(false)
38
+ })
39
+
40
+ it('agrees with is-true/is-false on the same value', () => {
41
+ for (const value of ['true', 'false', true, false]) {
42
+ expect(isTruthy(value)).toBe(
43
+ utils.evaluateSingleComparison({ leftValue: value, operator: 'is-true' }, {})
44
+ )
45
+ expect(isFalsy(value)).toBe(
46
+ utils.evaluateSingleComparison({ leftValue: value, operator: 'is-false' }, {})
47
+ )
48
+ }
49
+ })
50
+
51
+ it('leaves ordinary JS truthiness untouched', () => {
52
+ expect(isTruthy(true)).toBe(true)
53
+ expect(isTruthy(false)).toBe(false)
54
+ expect(isTruthy('anything')).toBe(true)
55
+ expect(isTruthy('')).toBe(false)
56
+ expect(isTruthy(0)).toBe(false)
57
+ expect(isTruthy(1)).toBe(true)
58
+ expect(isTruthy(null)).toBe(false)
59
+ expect(isTruthy(undefined)).toBe(false)
60
+ expect(isFalsy('')).toBe(true)
61
+ expect(isFalsy(0)).toBe(true)
62
+ expect(isFalsy('anything')).toBe(false)
63
+ })
64
+
65
+ it('normalizes the snake_case and camelCase spellings the same way', () => {
66
+ expect(utils.evaluateSingleComparison({ leftValue: 'false', operator: 'is_truthy' }, {})).toBe(
67
+ false
68
+ )
69
+ expect(utils.evaluateSingleComparison({ leftValue: 'false', operator: 'isTruthy' }, {})).toBe(
70
+ false
71
+ )
72
+ expect(utils.evaluateSingleComparison({ leftValue: 'false', operator: 'is_falsy' }, {})).toBe(
73
+ true
74
+ )
75
+ })
76
+ })
@@ -0,0 +1,59 @@
1
+ import { stripEventHandlerAttrs } from '../src/workflow-project-plugin'
2
+
3
+ // UIDL `attrs` hold data, never functions — an `on*`-named attr (the alerts
4
+ // fixture shipped `onClick: ""` on the Resolved pill) becomes a non-function
5
+ // JSX event prop that React invokes on click: `func.apply is not a function`.
6
+ // runBefore strips them so behavior stays on `events` / workflow triggers.
7
+
8
+ const element = (name: string, attrs: Record<string, unknown>, children: unknown[] = []) => ({
9
+ type: 'element',
10
+ content: { elementType: 'button', name, attrs, children },
11
+ })
12
+
13
+ const projectUidl = (): any => ({
14
+ root: {
15
+ name: 'Root',
16
+ stateDefinitions: {},
17
+ node: element('Page', { id: { type: 'static', content: 'page' } }, [
18
+ element('ResolvedPill', {
19
+ id: { type: 'static', content: 'status-resolved-btn' },
20
+ 'data-value': { type: 'static', content: 'Resolved' },
21
+ onClick: { type: 'static', content: '' },
22
+ }),
23
+ ]),
24
+ },
25
+ components: {
26
+ Card: {
27
+ name: 'Card',
28
+ node: element('Card', {
29
+ onMouseEnter: { type: 'static', content: 'highlight()' },
30
+ // lowercase second letter — NOT an event handler pattern, must survive
31
+ onboarding: { type: 'static', content: 'step-1' },
32
+ }),
33
+ },
34
+ },
35
+ })
36
+
37
+ describe('stripEventHandlerAttrs', () => {
38
+ it('removes on[A-Z]* attrs from pages and components, keeps everything else', () => {
39
+ const uidl = projectUidl()
40
+ stripEventHandlerAttrs(uidl)
41
+
42
+ const pill = uidl.root.node.content.children[0].content
43
+ expect(pill.attrs.onClick).toBeUndefined()
44
+ expect(pill.attrs.id.content).toBe('status-resolved-btn')
45
+ expect(pill.attrs['data-value'].content).toBe('Resolved')
46
+
47
+ const card = uidl.components.Card.node.content
48
+ expect(card.attrs.onMouseEnter).toBeUndefined()
49
+ expect(card.attrs.onboarding.content).toBe('step-1')
50
+ })
51
+
52
+ it('tolerates projects without components or attrs', () => {
53
+ const uidl: any = {
54
+ root: { name: 'Root', node: element('Bare', undefined as any) },
55
+ }
56
+ delete uidl.root.node.content.attrs
57
+ expect(() => stripEventHandlerAttrs(uidl)).not.toThrow()
58
+ })
59
+ })
@@ -0,0 +1,277 @@
1
+ import {
2
+ generateWorkflowAuthHelperFile,
3
+ buildWorkflowAuthInjection,
4
+ } from '../src/workflow-auth-generator'
5
+ import {
6
+ generateServerSegmentAPIRoute,
7
+ generateStreamingServerSegmentAPIRoute,
8
+ } from '../src/api-route-generator'
9
+ import { UIDLWorkflowProtection } from '@teleporthq/teleport-types'
10
+ import { WorkflowSegment } from '../src/types'
11
+
12
+ // The generated workflow-auth.js guard is the single stateless enforcement
13
+ // point. Booting it with a mocked next-auth/jwt proves the runtime behaviour;
14
+ // the route generators prove the guard is wired in BEFORE any node runs.
15
+
16
+ type Guard = (
17
+ req: any,
18
+ context: any,
19
+ policy: any
20
+ ) => Promise<{ status: number; message: string } | null>
21
+
22
+ /** Boots the emitted guard file with getToken stubbed to read `req.__token`. */
23
+ function bootGuard(secret: string | undefined): Guard {
24
+ const code = generateWorkflowAuthHelperFile()
25
+ const moduleObj: { exports: any } = { exports: {} }
26
+ const fakeRequire = (name: string): any => {
27
+ if (name === 'next-auth/jwt') {
28
+ return { getToken: async ({ req }: any) => (req && req.__token) || null }
29
+ }
30
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
31
+ return require(name)
32
+ }
33
+ // tslint:disable-next-line:function-constructor
34
+ new Function('require', 'module', 'exports', 'process', code)(
35
+ fakeRequire,
36
+ moduleObj,
37
+ moduleObj.exports,
38
+ { env: { NEXTAUTH_SECRET: secret } }
39
+ )
40
+ return moduleObj.exports.guardWorkflowRequest as Guard
41
+ }
42
+
43
+ const reqWith = (extra: Record<string, unknown> = {}): any => ({ headers: {}, ...extra })
44
+
45
+ describe('guardWorkflowRequest (runtime enforcement)', () => {
46
+ const guard = bootGuard('server-secret')
47
+
48
+ it('is a no-op when there is no policy', async () => {
49
+ const context = { n: { userId: 'victim' } }
50
+ expect(await guard(reqWith(), context, null)).toBeNull()
51
+ expect(context.n.userId).toBe('victim')
52
+ })
53
+
54
+ it('rejects with 401 when auth is required and there is no session', async () => {
55
+ const res = await guard(reqWith(), {}, { requiresAuth: true, allowedRoles: [] })
56
+ expect(res).toEqual({ status: 401, message: 'Unauthenticated' })
57
+ })
58
+
59
+ it('allows any authenticated user when allowedRoles is empty', async () => {
60
+ const res = await guard(
61
+ reqWith({ __token: { id: 'u1', role: 'user' } }),
62
+ {},
63
+ {
64
+ requiresAuth: true,
65
+ allowedRoles: [],
66
+ }
67
+ )
68
+ expect(res).toBeNull()
69
+ })
70
+
71
+ it('rejects with 403 when the session role is not allowed', async () => {
72
+ const res = await guard(
73
+ reqWith({ __token: { id: 'u1', role: 'user' } }),
74
+ {},
75
+ {
76
+ requiresAuth: true,
77
+ allowedRoles: ['admin'],
78
+ }
79
+ )
80
+ expect(res).toEqual({ status: 403, message: 'Forbidden' })
81
+ })
82
+
83
+ it('allows a matching role (incl. roleName / roles[] spellings)', async () => {
84
+ for (const token of [
85
+ { id: 'u1', role: 'admin' },
86
+ { id: 'u1', roleName: 'admin' },
87
+ { id: 'u1', roles: ['admin'] },
88
+ ]) {
89
+ expect(
90
+ await guard(
91
+ reqWith({ __token: token }),
92
+ {},
93
+ { requiresAuth: true, allowedRoles: ['admin'] }
94
+ )
95
+ ).toBeNull()
96
+ }
97
+ })
98
+
99
+ it('OVERWRITES a forged user id with the session id (the IDOR fix)', async () => {
100
+ const context = { resolver: { userId: 'victim-id', email: 'x' } }
101
+ const res = await guard(reqWith({ __token: { id: 'attacker-real-id' } }), context, {
102
+ requiresAuth: true,
103
+ allowedRoles: [],
104
+ userScoped: { ownerColumn: 'user_id', bindings: [{ nodeId: 'resolver', path: ['userId'] }] },
105
+ })
106
+ expect(res).toBeNull()
107
+ expect(context.resolver.userId).toBe('attacker-real-id') // forced to the caller
108
+ expect(context.resolver.email).toBe('x') // other fields untouched
109
+ })
110
+
111
+ it('binds a nested path and creates missing context nodes', async () => {
112
+ const context: any = {}
113
+ await guard(reqWith({ __token: { id: 'sess-1' } }), context, {
114
+ requiresAuth: false,
115
+ allowedRoles: [],
116
+ userScoped: { ownerColumn: 'user_id', bindings: [{ nodeId: 'n', path: ['user', 'id'] }] },
117
+ })
118
+ expect(context.n.user.id).toBe('sess-1')
119
+ })
120
+
121
+ it('falls back to token.sub when token.id is absent', async () => {
122
+ const context = { n: { userId: 'victim' } }
123
+ await guard(reqWith({ __token: { sub: 'sub-id' } }), context, {
124
+ requiresAuth: false,
125
+ allowedRoles: [],
126
+ userScoped: { ownerColumn: 'user_id', bindings: [{ nodeId: 'n', path: ['userId'] }] },
127
+ })
128
+ expect(context.n.userId).toBe('sub-id')
129
+ })
130
+
131
+ it('leaves the client value for a GUEST (no session) on a non-required userScoped write', async () => {
132
+ // Guest checkout: no token, requiresAuth false → the anonymous client id is
133
+ // kept (there is no server session to bind), and the request proceeds.
134
+ const context = { n: { userId: 'guest-anon-uuid' } }
135
+ const res = await guard(reqWith(), context, {
136
+ requiresAuth: false,
137
+ allowedRoles: [],
138
+ userScoped: { ownerColumn: 'user_id', bindings: [{ nodeId: 'n', path: ['userId'] }] },
139
+ })
140
+ expect(res).toBeNull()
141
+ expect(context.n.userId).toBe('guest-anon-uuid')
142
+ })
143
+
144
+ it('honours the internal server-to-server secret bypass', async () => {
145
+ const res = await guard(
146
+ reqWith({ headers: { 'x-internal-data-secret': 'server-secret' } }),
147
+ {},
148
+ { requiresAuth: true, allowedRoles: ['admin'] }
149
+ )
150
+ expect(res).toBeNull()
151
+ })
152
+
153
+ it('does not treat a wrong internal secret as a bypass', async () => {
154
+ const res = await guard(
155
+ reqWith({ headers: { 'x-internal-data-secret': 'wrong' } }),
156
+ {},
157
+ { requiresAuth: true, allowedRoles: [] }
158
+ )
159
+ expect(res).toEqual({ status: 401, message: 'Unauthenticated' })
160
+ })
161
+ })
162
+
163
+ describe('guardWorkflowRequest with next-auth absent / no secret', () => {
164
+ it('treats a missing NEXTAUTH_SECRET as no session (401 when required)', async () => {
165
+ const guard = bootGuard(undefined)
166
+ const res = await guard(
167
+ reqWith({ __token: { id: 'u1' } }),
168
+ {},
169
+ { requiresAuth: true, allowedRoles: [] }
170
+ )
171
+ expect(res).toEqual({ status: 401, message: 'Unauthenticated' })
172
+ })
173
+ })
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Route wiring
177
+ // ---------------------------------------------------------------------------
178
+
179
+ const dataSegment = (): WorkflowSegment =>
180
+ ({
181
+ id: 'server-1',
182
+ env: 'server',
183
+ nodes: [
184
+ {
185
+ id: 'w1',
186
+ type: 'data-create-item',
187
+ label: 'Write',
188
+ config: { tableName: 'teleport_favourites' },
189
+ stepNumber: 1,
190
+ },
191
+ ],
192
+ edges: [],
193
+ } as unknown as WorkflowSegment)
194
+
195
+ const protection: UIDLWorkflowProtection = {
196
+ requiresAuth: true,
197
+ allowedRoles: ['admin'],
198
+ derivedFrom: 'page',
199
+ }
200
+
201
+ describe('route generators wire the guard in', () => {
202
+ it('injects the guard into a protected non-streaming route BEFORE the node loop', () => {
203
+ const code = generateServerSegmentAPIRoute(dataSegment(), 'Admin CRUD', protection)
204
+ expect(code).toContain("require('../../../utils/workflows/workflow-auth')")
205
+ expect(code).toContain('const __WF_AUTH = {')
206
+ expect(code).toContain('__wfAuth.guardWorkflowRequest(req, context, __WF_AUTH)')
207
+ // The guard (and its early 401/403 return) must precede the node loop.
208
+ expect(code.indexOf('guardWorkflowRequest')).toBeLessThan(
209
+ code.indexOf('SEGMENT_CONFIG.nodes.slice()')
210
+ )
211
+ // The baked policy carries the runtime fields, not the build-only derivedFrom.
212
+ expect(code).toContain('"requiresAuth":true')
213
+ expect(code).toContain('"allowedRoles":["admin"]')
214
+ expect(code).not.toContain('derivedFrom')
215
+ })
216
+
217
+ it('injects the guard into a protected streaming route BEFORE the stream starts', () => {
218
+ const code = generateStreamingServerSegmentAPIRoute(dataSegment(), 'Streaming', {
219
+ requiresAuth: true,
220
+ allowedRoles: [],
221
+ derivedFrom: 'graph',
222
+ })
223
+ expect(code).toContain('__wfAuth.guardWorkflowRequest(req, context, __WF_AUTH)')
224
+ // The guard runs before the node loop. `ensureStream()` (which calls
225
+ // res.writeHead for the event-stream) is only invoked DURING node execution,
226
+ // so a 401/403 is sent as an HTTP status before the stream ever starts.
227
+ expect(code.indexOf('guardWorkflowRequest')).toBeLessThan(
228
+ code.indexOf('SEGMENT_CONFIG.nodes.slice()')
229
+ )
230
+ // The only ensureStream CALL (not its definition) is inside the node loop.
231
+ expect(code.indexOf('guardWorkflowRequest')).toBeLessThan(code.lastIndexOf('ensureStream()'))
232
+ })
233
+
234
+ it('emits NOTHING guard-related for an unprotected route (byte-compatible)', () => {
235
+ const code = generateServerSegmentAPIRoute(dataSegment(), 'Public')
236
+ expect(code).not.toContain('workflow-auth')
237
+ expect(code).not.toContain('__WF_AUTH')
238
+ expect(code).not.toContain('guardWorkflowRequest')
239
+ })
240
+
241
+ it('bakes the userScoped bindings into the policy', () => {
242
+ const code = generateServerSegmentAPIRoute(dataSegment(), 'Favourites', {
243
+ requiresAuth: true,
244
+ allowedRoles: [],
245
+ userScoped: { ownerColumn: 'user_id', bindings: [{ nodeId: 'resolve', path: ['userId'] }] },
246
+ derivedFrom: 'graph',
247
+ })
248
+ expect(code).toContain('"userScoped"')
249
+ expect(code).toContain('"nodeId":"resolve"')
250
+ expect(code).toContain('"path":["userId"]')
251
+ })
252
+ })
253
+
254
+ describe('buildWorkflowAuthInjection', () => {
255
+ it('returns empty pieces when there is no meaningful policy', () => {
256
+ expect(buildWorkflowAuthInjection(undefined)).toEqual({
257
+ requireLine: '',
258
+ policyConst: '',
259
+ guardCall: '',
260
+ })
261
+ expect(
262
+ buildWorkflowAuthInjection({ requiresAuth: false, allowedRoles: [], derivedFrom: 'default' })
263
+ ).toEqual({ requireLine: '', policyConst: '', guardCall: '' })
264
+ })
265
+
266
+ it('emits pieces for a userScoped-only (guest-capable) policy', () => {
267
+ const injection = buildWorkflowAuthInjection({
268
+ requiresAuth: false,
269
+ allowedRoles: [],
270
+ userScoped: { ownerColumn: 'user_id', bindings: [{ nodeId: 'n', path: ['userId'] }] },
271
+ derivedFrom: 'graph',
272
+ })
273
+ expect(injection.requireLine).toContain('workflow-auth')
274
+ expect(injection.policyConst).toContain('"userScoped"')
275
+ expect(injection.guardCall).toContain('guardWorkflowRequest')
276
+ })
277
+ })
@@ -1,8 +1,8 @@
1
- import { UIDLWorkflow, UIDLCustomWorkflowNode } from '@teleporthq/teleport-types';
1
+ import { UIDLWorkflow, UIDLCustomWorkflowNode, UIDLWorkflowProtection } from '@teleporthq/teleport-types';
2
2
  import { WorkflowSegment } from './types';
3
- export declare const generateServerSegmentAPIRoute: (segment: WorkflowSegment, workflowName?: string) => string;
3
+ export declare const generateServerSegmentAPIRoute: (segment: WorkflowSegment, workflowName?: string, protection?: UIDLWorkflowProtection) => string;
4
4
  export declare const hasStreamingAINode: (segment: WorkflowSegment) => boolean;
5
- export declare const generateStreamingServerSegmentAPIRoute: (segment: WorkflowSegment, workflowName?: string) => string;
5
+ export declare const generateStreamingServerSegmentAPIRoute: (segment: WorkflowSegment, workflowName?: string, protection?: UIDLWorkflowProtection) => string;
6
6
  export declare const generateCronAPIRoute: (workflow: UIDLWorkflow, customNodes?: Record<string, UIDLCustomWorkflowNode>) => string;
7
7
  export declare const getAPIRouteFileName: (workflowId: string, segmentId: string, workflowName?: string) => string;
8
8
  export declare const getCronRouteFileName: (workflow: UIDLWorkflow) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"api-route-generator.d.ts","sourceRoot":"","sources":["../../src/api-route-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIZ,sBAAsB,EACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAoBzC,eAAO,MAAM,6BAA6B,YAC/B,eAAe,iBACT,MAAM,KACpB,MAwUF,CAAA;AAED,eAAO,MAAM,kBAAkB,YAAa,eAAe,KAAG,OAE7D,CAAA;AAED,eAAO,MAAM,sCAAsC,YACxC,eAAe,iBACT,MAAM,KACpB,MAwYF,CAAA;AA0HD,eAAO,MAAM,oBAAoB,aACrB,YAAY,gBACR,OAAO,MAAM,EAAE,sBAAsB,CAAC,KACnD,MAoFF,CAAA;AAED,eAAO,MAAM,mBAAmB,eAClB,MAAM,aACP,MAAM,iBACF,MAAM,KACpB,MAuBF,CAAA;AAED,eAAO,MAAM,oBAAoB,aAAc,YAAY,KAAG,MAa7D,CAAA;AAkQD,eAAO,MAAM,+BAA+B,aAChC,YAAY,gBACR,OAAO,MAAM,EAAE,sBAAsB,CAAC,KACnD,MA6LF,CAAA;AAED,eAAO,MAAM,uBAAuB,aAAc,YAAY,KAAG,MAiBhE,CAAA;AAED,eAAO,MAAM,mBAAmB,aAAc,YAAY,KAAG,MAAM,EAqBlE,CAAA"}
1
+ {"version":3,"file":"api-route-generator.d.ts","sourceRoot":"","sources":["../../src/api-route-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIZ,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAqBzC,eAAO,MAAM,6BAA6B,YAC/B,eAAe,iBACT,MAAM,eACR,sBAAsB,KAClC,MAgXF,CAAA;AAED,eAAO,MAAM,kBAAkB,YAAa,eAAe,KAAG,OAE7D,CAAA;AAED,eAAO,MAAM,sCAAsC,YACxC,eAAe,iBACT,MAAM,eACR,sBAAsB,KAClC,MAsbF,CAAA;AA0HD,eAAO,MAAM,oBAAoB,aACrB,YAAY,gBACR,OAAO,MAAM,EAAE,sBAAsB,CAAC,KACnD,MAuFF,CAAA;AAED,eAAO,MAAM,mBAAmB,eAClB,MAAM,aACP,MAAM,iBACF,MAAM,KACpB,MAuBF,CAAA;AAED,eAAO,MAAM,oBAAoB,aAAc,YAAY,KAAG,MAa7D,CAAA;AA4RD,eAAO,MAAM,+BAA+B,aAChC,YAAY,gBACR,OAAO,MAAM,EAAE,sBAAsB,CAAC,KACnD,MAsMF,CAAA;AAED,eAAO,MAAM,uBAAuB,aAAc,YAAY,KAAG,MAiBhE,CAAA;AAED,eAAO,MAAM,mBAAmB,aAAc,YAAY,KAAG,MAAM,EAqBlE,CAAA"}