@open-mercato/core 0.6.6-develop.6429.1.2fba7258d1 → 0.6.6-develop.6450.1.b493fb430c
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customers/api/people/[id]/route.js +73 -2
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/planner/commands/shared.js +1 -8
- package/dist/modules/planner/commands/shared.js.map +2 -2
- package/dist/modules/resources/commands/shared.js +1 -7
- package/dist/modules/resources/commands/shared.js.map +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/points/route.js +2 -2
- package/dist/modules/shipping_carriers/api/points/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/rates/route.js +2 -2
- package/dist/modules/shipping_carriers/api/rates/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/route.js +2 -2
- package/dist/modules/shipping_carriers/api/tracking/route.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/upstream-error-response.js +13 -0
- package/dist/modules/shipping_carriers/lib/upstream-error-response.js.map +7 -0
- package/dist/modules/staff/commands/activities.js +46 -8
- package/dist/modules/staff/commands/activities.js.map +2 -2
- package/dist/modules/staff/commands/addresses.js +40 -7
- package/dist/modules/staff/commands/addresses.js.map +2 -2
- package/dist/modules/staff/commands/comments.js +46 -8
- package/dist/modules/staff/commands/comments.js.map +2 -2
- package/dist/modules/staff/commands/job-histories.js +40 -7
- package/dist/modules/staff/commands/job-histories.js.map +2 -2
- package/dist/modules/staff/commands/leave-requests.js +31 -9
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/commands/shared.js +56 -7
- package/dist/modules/staff/commands/shared.js.map +2 -2
- package/dist/modules/staff/commands/tag-assignments.js +18 -7
- package/dist/modules/staff/commands/tag-assignments.js.map +2 -2
- package/dist/modules/staff/commands/team-members.js +16 -5
- package/dist/modules/staff/commands/team-members.js.map +2 -2
- package/dist/modules/staff/commands/team-roles.js +16 -5
- package/dist/modules/staff/commands/team-roles.js.map +2 -2
- package/dist/modules/staff/commands/teams.js +16 -5
- package/dist/modules/staff/commands/teams.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +17 -5
- package/dist/modules/staff/commands/timesheets-entries.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-projects.js +22 -9
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/sync_excel/lib/upload-storage.js +9 -6
- package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
- package/dist/modules/translations/api/get/locales.js +2 -1
- package/dist/modules/translations/api/get/locales.js.map +2 -2
- package/dist/modules/translations/api/put/locales.js +1 -1
- package/dist/modules/translations/api/put/locales.js.map +2 -2
- package/dist/modules/workflows/examples/sales-pipeline-definition.json +8 -8
- package/dist/modules/workflows/lib/activity-executor.js +39 -3
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/api/people/[id]/route.ts +107 -2
- package/src/modules/planner/commands/shared.ts +2 -11
- package/src/modules/resources/commands/shared.ts +2 -10
- package/src/modules/shipping_carriers/api/cancel/route.ts +2 -2
- package/src/modules/shipping_carriers/api/points/route.ts +2 -2
- package/src/modules/shipping_carriers/api/rates/route.ts +2 -2
- package/src/modules/shipping_carriers/api/shipments/route.ts +2 -2
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +2 -2
- package/src/modules/shipping_carriers/api/tracking/route.ts +2 -2
- package/src/modules/shipping_carriers/i18n/de.json +1 -0
- package/src/modules/shipping_carriers/i18n/en.json +1 -0
- package/src/modules/shipping_carriers/i18n/es.json +1 -0
- package/src/modules/shipping_carriers/i18n/pl.json +1 -0
- package/src/modules/shipping_carriers/lib/upstream-error-response.ts +11 -0
- package/src/modules/staff/commands/activities.ts +46 -8
- package/src/modules/staff/commands/addresses.ts +40 -7
- package/src/modules/staff/commands/comments.ts +46 -8
- package/src/modules/staff/commands/job-histories.ts +40 -7
- package/src/modules/staff/commands/leave-requests.ts +36 -9
- package/src/modules/staff/commands/shared.ts +71 -8
- package/src/modules/staff/commands/tag-assignments.ts +18 -7
- package/src/modules/staff/commands/team-members.ts +16 -5
- package/src/modules/staff/commands/team-roles.ts +16 -5
- package/src/modules/staff/commands/teams.ts +16 -5
- package/src/modules/staff/commands/timesheets-entries.ts +17 -5
- package/src/modules/staff/commands/timesheets-projects.ts +22 -9
- package/src/modules/sync_excel/lib/upload-storage.ts +8 -6
- package/src/modules/translations/api/get/locales.ts +1 -0
- package/src/modules/translations/api/put/locales.ts +1 -1
- package/src/modules/workflows/AGENTS.md +2 -1
- package/src/modules/workflows/examples/sales-pipeline-definition.json +8 -8
- package/src/modules/workflows/lib/activity-executor.ts +49 -5
|
@@ -22,7 +22,15 @@ import {
|
|
|
22
22
|
type StaffTimeEntryUpdateInput,
|
|
23
23
|
} from '../data/validators'
|
|
24
24
|
import { staffTimeEntryCrudEvents } from '../lib/crud'
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
applyScopeToWhere,
|
|
27
|
+
commandActorScope,
|
|
28
|
+
commandInputScope,
|
|
29
|
+
ensureOrganizationScope,
|
|
30
|
+
ensureTenantScope,
|
|
31
|
+
extractUndoPayload,
|
|
32
|
+
scopeForDecryption,
|
|
33
|
+
} from './shared'
|
|
26
34
|
import { getStaffMemberByUserId } from '../lib/staffMemberResolver'
|
|
27
35
|
|
|
28
36
|
type RbacServiceLike = {
|
|
@@ -174,6 +182,7 @@ const createTimeEntryCommand: CommandHandler<StaffTimeEntryCreateInput, { timeEn
|
|
|
174
182
|
const parsed = staffTimeEntryCreateSchema.parse(rawInput)
|
|
175
183
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
176
184
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
185
|
+
commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
|
|
177
186
|
|
|
178
187
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
179
188
|
|
|
@@ -290,6 +299,7 @@ const startTimerCommand: CommandHandler<StaffTimeEntryStartTimerInput, { timeEnt
|
|
|
290
299
|
const parsed = staffTimeEntryStartTimerSchema.parse(rawInput)
|
|
291
300
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
292
301
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
302
|
+
commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
|
|
293
303
|
|
|
294
304
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
295
305
|
|
|
@@ -457,12 +467,13 @@ const updateTimeEntryCommand: CommandHandler<StaffTimeEntryUpdateInput, { timeEn
|
|
|
457
467
|
async execute(rawInput, ctx) {
|
|
458
468
|
const parsed = staffTimeEntryUpdateSchema.parse(rawInput)
|
|
459
469
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
470
|
+
const scope = commandActorScope(ctx)
|
|
460
471
|
const entry = await findOneWithDecryption(
|
|
461
472
|
em,
|
|
462
473
|
StaffTimeEntry,
|
|
463
|
-
{ id: parsed.id, deletedAt: null },
|
|
474
|
+
applyScopeToWhere<StaffTimeEntry>({ id: parsed.id, deletedAt: null }, scope),
|
|
464
475
|
undefined,
|
|
465
|
-
|
|
476
|
+
scopeForDecryption(scope),
|
|
466
477
|
)
|
|
467
478
|
if (!entry) throw new CrudHttpError(404, { error: 'Time entry not found.' })
|
|
468
479
|
ensureTenantScope(ctx, entry.tenantId)
|
|
@@ -583,12 +594,13 @@ const deleteTimeEntryCommand: CommandHandler<{ id?: string }, { timeEntryId: str
|
|
|
583
594
|
const id = input?.id
|
|
584
595
|
if (!id) throw new CrudHttpError(400, { error: 'Time entry id is required.' })
|
|
585
596
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
597
|
+
const scope = commandActorScope(ctx)
|
|
586
598
|
const entry = await findOneWithDecryption(
|
|
587
599
|
em,
|
|
588
600
|
StaffTimeEntry,
|
|
589
|
-
{ id, deletedAt: null },
|
|
601
|
+
applyScopeToWhere<StaffTimeEntry>({ id, deletedAt: null }, scope),
|
|
590
602
|
undefined,
|
|
591
|
-
|
|
603
|
+
scopeForDecryption(scope),
|
|
592
604
|
)
|
|
593
605
|
if (!entry) throw new CrudHttpError(404, { error: 'Time entry not found.' })
|
|
594
606
|
ensureTenantScope(ctx, entry.tenantId)
|
|
@@ -27,7 +27,15 @@ import {
|
|
|
27
27
|
type StaffTimeProjectMemberUpdateInput,
|
|
28
28
|
} from '../data/validators'
|
|
29
29
|
import { staffTimeProjectCrudEvents, staffTimeProjectMemberCrudEvents } from '../lib/crud'
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
applyScopeToWhere,
|
|
32
|
+
commandActorScope,
|
|
33
|
+
commandInputScope,
|
|
34
|
+
ensureOrganizationScope,
|
|
35
|
+
ensureTenantScope,
|
|
36
|
+
extractUndoPayload,
|
|
37
|
+
scopeForDecryption,
|
|
38
|
+
} from './shared'
|
|
31
39
|
|
|
32
40
|
function isUniqueViolation(error: unknown): boolean {
|
|
33
41
|
if (error instanceof UniqueConstraintViolationException) return true
|
|
@@ -163,6 +171,7 @@ const createTimeProjectCommand: CommandHandler<StaffTimeProjectCreateInput, { ti
|
|
|
163
171
|
const parsed = staffTimeProjectCreateSchema.parse(rawInput)
|
|
164
172
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
165
173
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
174
|
+
commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
|
|
166
175
|
|
|
167
176
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
168
177
|
const now = new Date()
|
|
@@ -282,12 +291,13 @@ const updateTimeProjectCommand: CommandHandler<StaffTimeProjectUpdateInput, { ti
|
|
|
282
291
|
async execute(rawInput, ctx) {
|
|
283
292
|
const parsed = staffTimeProjectUpdateSchema.parse(rawInput)
|
|
284
293
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
294
|
+
const scope = commandActorScope(ctx)
|
|
285
295
|
const project = await findOneWithDecryption(
|
|
286
296
|
em,
|
|
287
297
|
StaffTimeProject,
|
|
288
|
-
{ id: parsed.id, deletedAt: null },
|
|
298
|
+
applyScopeToWhere<StaffTimeProject>({ id: parsed.id, deletedAt: null }, scope),
|
|
289
299
|
undefined,
|
|
290
|
-
|
|
300
|
+
scopeForDecryption(scope),
|
|
291
301
|
)
|
|
292
302
|
if (!project) throw new CrudHttpError(404, { error: 'Time project not found.' })
|
|
293
303
|
ensureTenantScope(ctx, project.tenantId)
|
|
@@ -419,12 +429,13 @@ const deleteTimeProjectCommand: CommandHandler<{ id?: string }, { timeProjectId:
|
|
|
419
429
|
const id = input?.id
|
|
420
430
|
if (!id) throw new CrudHttpError(400, { error: 'Time project id is required.' })
|
|
421
431
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
432
|
+
const scope = commandActorScope(ctx)
|
|
422
433
|
const project = await findOneWithDecryption(
|
|
423
434
|
em,
|
|
424
435
|
StaffTimeProject,
|
|
425
|
-
{ id, deletedAt: null },
|
|
436
|
+
applyScopeToWhere<StaffTimeProject>({ id, deletedAt: null }, scope),
|
|
426
437
|
undefined,
|
|
427
|
-
|
|
438
|
+
scopeForDecryption(scope),
|
|
428
439
|
)
|
|
429
440
|
if (!project) throw new CrudHttpError(404, { error: 'Time project not found.' })
|
|
430
441
|
ensureTenantScope(ctx, project.tenantId)
|
|
@@ -527,6 +538,7 @@ const assignTimeProjectMemberCommand: CommandHandler<StaffTimeProjectMemberAssig
|
|
|
527
538
|
const parsed = staffTimeProjectMemberAssignSchema.parse(rawInput)
|
|
528
539
|
ensureTenantScope(ctx, parsed.tenantId)
|
|
529
540
|
ensureOrganizationScope(ctx, parsed.organizationId)
|
|
541
|
+
const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
|
|
530
542
|
|
|
531
543
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
532
544
|
|
|
@@ -534,7 +546,7 @@ const assignTimeProjectMemberCommand: CommandHandler<StaffTimeProjectMemberAssig
|
|
|
534
546
|
// Without this check a foreign or stale UUID would produce a dangling reference.
|
|
535
547
|
const projectExists = await em.findOne(
|
|
536
548
|
StaffTimeProject,
|
|
537
|
-
{ id: parsed.timeProjectId,
|
|
549
|
+
applyScopeToWhere<StaffTimeProject>({ id: parsed.timeProjectId, deletedAt: null }, scope),
|
|
538
550
|
{ fields: ['id'] },
|
|
539
551
|
)
|
|
540
552
|
if (!projectExists) {
|
|
@@ -548,7 +560,7 @@ const assignTimeProjectMemberCommand: CommandHandler<StaffTimeProjectMemberAssig
|
|
|
548
560
|
}
|
|
549
561
|
const memberExists = await em.findOne(
|
|
550
562
|
StaffTeamMember,
|
|
551
|
-
{ id: parsed.staffMemberId,
|
|
563
|
+
applyScopeToWhere<StaffTeamMember>({ id: parsed.staffMemberId, deletedAt: null }, scope),
|
|
552
564
|
{ fields: ['id'] },
|
|
553
565
|
)
|
|
554
566
|
if (!memberExists) {
|
|
@@ -658,12 +670,13 @@ const unassignTimeProjectMemberCommand: CommandHandler<{ id?: string }, { timePr
|
|
|
658
670
|
const id = input?.id
|
|
659
671
|
if (!id) throw new CrudHttpError(400, { error: 'Time project member id is required.' })
|
|
660
672
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
673
|
+
const scope = commandActorScope(ctx)
|
|
661
674
|
const member = await findOneWithDecryption(
|
|
662
675
|
em,
|
|
663
676
|
StaffTimeProjectMember,
|
|
664
|
-
{ id, deletedAt: null },
|
|
677
|
+
applyScopeToWhere<StaffTimeProjectMember>({ id, deletedAt: null }, scope),
|
|
665
678
|
undefined,
|
|
666
|
-
|
|
679
|
+
scopeForDecryption(scope),
|
|
667
680
|
)
|
|
668
681
|
if (!member) throw new CrudHttpError(404, { error: 'Time project member not found.' })
|
|
669
682
|
ensureTenantScope(ctx, member.tenantId)
|
|
@@ -52,7 +52,6 @@ export async function createSyncExcelUploadAttachment(input: {
|
|
|
52
52
|
module: 'sync_excel',
|
|
53
53
|
temporary: true,
|
|
54
54
|
uploadId: input.uploadId,
|
|
55
|
-
inlineCsvBase64: input.buffer.toString('base64'),
|
|
56
55
|
},
|
|
57
56
|
})
|
|
58
57
|
|
|
@@ -66,14 +65,17 @@ export async function readSyncExcelUploadBuffer(
|
|
|
66
65
|
attachment: Pick<Attachment, 'partitionCode' | 'storagePath' | 'storageDriver' | 'storageMetadata'>,
|
|
67
66
|
): Promise<Buffer> {
|
|
68
67
|
const inlineCsvBase64 = attachment.storageMetadata?.inlineCsvBase64
|
|
69
|
-
if (typeof inlineCsvBase64 === 'string' && inlineCsvBase64.length > 0) {
|
|
70
|
-
return Buffer.from(inlineCsvBase64, 'base64')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
68
|
const absolutePath = resolveAttachmentAbsolutePath(
|
|
74
69
|
attachment.partitionCode,
|
|
75
70
|
attachment.storagePath,
|
|
76
71
|
attachment.storageDriver,
|
|
77
72
|
)
|
|
78
|
-
|
|
73
|
+
try {
|
|
74
|
+
return await fs.readFile(absolutePath)
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (typeof inlineCsvBase64 === 'string' && inlineCsvBase64.length > 0) {
|
|
77
|
+
return Buffer.from(inlineCsvBase64, 'base64')
|
|
78
|
+
}
|
|
79
|
+
throw error
|
|
80
|
+
}
|
|
79
81
|
}
|
|
@@ -18,6 +18,7 @@ async function GET(req: Request) {
|
|
|
18
18
|
const configService = context.container.resolve('moduleConfigService') as ModuleConfigService
|
|
19
19
|
const locales = await configService.getValue<string[]>('translations', 'supported_locales', {
|
|
20
20
|
defaultValue: [...defaultLocales],
|
|
21
|
+
scope: { tenantId: context.tenantId },
|
|
21
22
|
})
|
|
22
23
|
|
|
23
24
|
return NextResponse.json({ locales: Array.isArray(locales) ? locales : [...defaultLocales] })
|
|
@@ -44,7 +44,7 @@ async function PUT(req: Request) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const configService = context.container.resolve('moduleConfigService') as ModuleConfigService
|
|
47
|
-
await configService.setValue('translations', 'supported_locales', uniqueLocales)
|
|
47
|
+
await configService.setValue('translations', 'supported_locales', uniqueLocales, { tenantId: context.tenantId })
|
|
48
48
|
|
|
49
49
|
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
50
50
|
await runCrudMutationGuardAfterSuccess(context.container, {
|
|
@@ -10,7 +10,7 @@ Use the workflows module for business process automation: defining step-based wo
|
|
|
10
10
|
4. **MUST follow the instance state machine** — instances transition `RUNNING → COMPLETED|FAILED|CANCELLED`; intermediate states include `PAUSED`, `WAITING_FOR_ACTIVITIES`, `COMPENSATING`
|
|
11
11
|
5. **MUST keep activity handlers idempotent** — check state before mutating; activities may be retried on failure
|
|
12
12
|
6. **MUST use event sourcing** — log all workflow events via `eventLogger.logWorkflowEvent()`; never mutate instance state without a corresponding event
|
|
13
|
-
7. **MUST use variable interpolation** for dynamic activity config — use `{{context.*}}`, `{{workflow.*}}`, `{{env.*}}`, `{{now}}`; never hardcode values
|
|
13
|
+
7. **MUST use variable interpolation** for dynamic activity config — use `{{context.*}}`, `{{workflow.*}}`, server-allowlisted non-secret `{{env.*}}` keys such as `{{env.APP_URL}}`, and `{{now}}`; never hardcode values or read secrets from `{{env.*}}`
|
|
14
14
|
8. **MUST use event triggers, signals, and widget injection** for cross-module integration.
|
|
15
15
|
9. **MUST declare new events in `events.ts`** with `as const` — undeclared events trigger TypeScript errors and runtime warnings
|
|
16
16
|
10. **MUST scope all queries by `organization_id`** — workflow data is tenant-scoped; never expose cross-tenant instances or tasks
|
|
@@ -99,6 +99,7 @@ Definition → startWorkflow() → Instance → executeWorkflow() loop
|
|
|
99
99
|
| Variable | Effect | Default |
|
|
100
100
|
|----------|--------|---------|
|
|
101
101
|
| `OM_WORKFLOWS_ALLOW_PRIVATE_URLS` | When `1`/`true`/`yes`, bypasses the SSRF guard in `CALL_WEBHOOK` so workflow authors can hit `localhost`, RFC1918, and `.internal` targets. For dev only — MUST remain unset in production. | unset (guard enforced) |
|
|
102
|
+
| `OM_WORKFLOWS_ENV_INTERPOLATION_ALLOWLIST` | Comma-separated non-secret process env keys allowed for `{{env.*}}` interpolation in workflow activity config. `APP_URL` is always allowed. Never include secrets. | unset (`APP_URL` only) |
|
|
102
103
|
|
|
103
104
|
## DI Services
|
|
104
105
|
|
|
@@ -262,10 +262,10 @@
|
|
|
262
262
|
"activityType": "CALL_API",
|
|
263
263
|
"config": {
|
|
264
264
|
"method": "POST",
|
|
265
|
-
"url": "{{
|
|
265
|
+
"url": "{{context.integration.contractServiceUrl}}/api/contracts/generate",
|
|
266
266
|
"headers": {
|
|
267
267
|
"Content-Type": "application/json",
|
|
268
|
-
"Authorization": "Bearer {{
|
|
268
|
+
"Authorization": "Bearer {{context.integration.serviceToken}}"
|
|
269
269
|
},
|
|
270
270
|
"body": {
|
|
271
271
|
"orderId": "{{context.order.id}}",
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
"activityType": "CALL_API",
|
|
332
332
|
"config": {
|
|
333
333
|
"method": "POST",
|
|
334
|
-
"url": "{{
|
|
334
|
+
"url": "{{context.integration.contractServiceUrl}}/api/contracts/verify",
|
|
335
335
|
"body": {
|
|
336
336
|
"contractId": "{{context.contract.id}}",
|
|
337
337
|
"signatureId": "{{task.result.signatureId}}"
|
|
@@ -393,10 +393,10 @@
|
|
|
393
393
|
"activityType": "CALL_API",
|
|
394
394
|
"config": {
|
|
395
395
|
"method": "POST",
|
|
396
|
-
"url": "{{
|
|
396
|
+
"url": "{{context.integration.billingServiceUrl}}/api/invoices",
|
|
397
397
|
"headers": {
|
|
398
398
|
"Content-Type": "application/json",
|
|
399
|
-
"Authorization": "Bearer {{
|
|
399
|
+
"Authorization": "Bearer {{context.integration.serviceToken}}"
|
|
400
400
|
},
|
|
401
401
|
"body": {
|
|
402
402
|
"orderId": "{{context.order.id}}",
|
|
@@ -486,10 +486,10 @@
|
|
|
486
486
|
"activityType": "CALL_API",
|
|
487
487
|
"config": {
|
|
488
488
|
"method": "POST",
|
|
489
|
-
"url": "{{
|
|
489
|
+
"url": "{{context.integration.warehouseServiceUrl}}/api/fulfillment/orders",
|
|
490
490
|
"headers": {
|
|
491
491
|
"Content-Type": "application/json",
|
|
492
|
-
"Authorization": "Bearer {{
|
|
492
|
+
"Authorization": "Bearer {{context.integration.serviceToken}}"
|
|
493
493
|
},
|
|
494
494
|
"body": {
|
|
495
495
|
"orderId": "{{context.order.id}}",
|
|
@@ -607,4 +607,4 @@
|
|
|
607
607
|
}
|
|
608
608
|
]
|
|
609
609
|
}
|
|
610
|
-
}
|
|
610
|
+
}
|
|
@@ -31,10 +31,27 @@ export { isPrivateUrl } from '@open-mercato/shared/lib/network'
|
|
|
31
31
|
|
|
32
32
|
function isAllowPrivateWorkflowWebhookUrlsEnabled(): boolean {
|
|
33
33
|
if (parseBooleanWithDefault(process.env.OM_WORKFLOWS_ALLOW_PRIVATE_URLS, false)) {
|
|
34
|
+
if (process.env.NODE_ENV === 'production') {
|
|
35
|
+
console.warn(
|
|
36
|
+
'[CALL_WEBHOOK] OM_WORKFLOWS_ALLOW_PRIVATE_URLS is set but ignored in production. SSRF protection remains enabled.'
|
|
37
|
+
)
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.warn(
|
|
42
|
+
'[CALL_WEBHOOK] OM_WORKFLOWS_ALLOW_PRIVATE_URLS is enabled. SSRF protection is bypassed for workflow webhooks; use only in development.'
|
|
43
|
+
)
|
|
34
44
|
return true
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
if (parseBooleanWithDefault(process.env.WORKFLOW_WEBHOOK_ALLOW_PRIVATE_URLS, false)) {
|
|
48
|
+
if (process.env.NODE_ENV === 'production') {
|
|
49
|
+
console.warn(
|
|
50
|
+
'[CALL_WEBHOOK] WORKFLOW_WEBHOOK_ALLOW_PRIVATE_URLS is deprecated and ignored in production. Use OM_WORKFLOWS_ALLOW_PRIVATE_URLS for development only. SSRF protection remains enabled.'
|
|
51
|
+
)
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
console.warn(
|
|
39
56
|
'[CALL_WEBHOOK] WORKFLOW_WEBHOOK_ALLOW_PRIVATE_URLS is deprecated. Use OM_WORKFLOWS_ALLOW_PRIVATE_URLS instead. SSRF protection is bypassed.'
|
|
40
57
|
)
|
|
@@ -44,6 +61,34 @@ function isAllowPrivateWorkflowWebhookUrlsEnabled(): boolean {
|
|
|
44
61
|
return false
|
|
45
62
|
}
|
|
46
63
|
|
|
64
|
+
const DEFAULT_WORKFLOW_ENV_INTERPOLATION_ALLOWLIST = new Set(['APP_URL'])
|
|
65
|
+
const WORKFLOW_ENV_INTERPOLATION_ALLOWLIST_KEY = 'OM_WORKFLOWS_ENV_INTERPOLATION_ALLOWLIST'
|
|
66
|
+
|
|
67
|
+
function getWorkflowEnvInterpolationAllowlist(): Set<string> {
|
|
68
|
+
const allowlist = new Set(DEFAULT_WORKFLOW_ENV_INTERPOLATION_ALLOWLIST)
|
|
69
|
+
const configuredKeys = process.env[WORKFLOW_ENV_INTERPOLATION_ALLOWLIST_KEY]
|
|
70
|
+
if (!configuredKeys) {
|
|
71
|
+
return allowlist
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const key of configuredKeys.split(',')) {
|
|
75
|
+
const trimmedKey = key.trim()
|
|
76
|
+
if (trimmedKey) {
|
|
77
|
+
allowlist.add(trimmedKey)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return allowlist
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveWorkflowEnvInterpolation(envKey: string): string {
|
|
85
|
+
if (!getWorkflowEnvInterpolationAllowlist().has(envKey)) {
|
|
86
|
+
return ''
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return process.env[envKey] ?? ''
|
|
90
|
+
}
|
|
91
|
+
|
|
47
92
|
// ============================================================================
|
|
48
93
|
// Types and Interfaces
|
|
49
94
|
// ============================================================================
|
|
@@ -857,7 +902,7 @@ export async function executeCallApi(
|
|
|
857
902
|
container: AwilixContainer,
|
|
858
903
|
signal?: AbortSignal
|
|
859
904
|
): Promise<any> {
|
|
860
|
-
// 1. Interpolate variables in config (including {{workflow.*}}, {{context.*}}, {{env.*}}, {{now}})
|
|
905
|
+
// 1. Interpolate variables in config (including {{workflow.*}}, {{context.*}}, allowlisted {{env.*}}, {{now}})
|
|
861
906
|
const interpolatedConfig = interpolateVariables(config, context.workflowContext, context.workflowInstance)
|
|
862
907
|
|
|
863
908
|
const {
|
|
@@ -1145,7 +1190,7 @@ function classifyAndThrowError(status: number, body: any, url: string): never {
|
|
|
1145
1190
|
* - {{workflow.tenantId}} - tenant ID
|
|
1146
1191
|
* - {{workflow.organizationId}} - organization ID
|
|
1147
1192
|
* - {{workflow.currentStepId}} - current step ID
|
|
1148
|
-
* - {{env.VAR_NAME}} - environment variables
|
|
1193
|
+
* - {{env.VAR_NAME}} - server-allowlisted environment variables
|
|
1149
1194
|
* - {{now}} - current ISO timestamp
|
|
1150
1195
|
*/
|
|
1151
1196
|
function interpolateVariables(
|
|
@@ -1185,7 +1230,7 @@ function interpolateVariables(
|
|
|
1185
1230
|
// Handle {{env.*}} variables
|
|
1186
1231
|
if (trimmedPath.startsWith('env.')) {
|
|
1187
1232
|
const envKey = trimmedPath.substring('env.'.length)
|
|
1188
|
-
return
|
|
1233
|
+
return resolveWorkflowEnvInterpolation(envKey)
|
|
1189
1234
|
}
|
|
1190
1235
|
|
|
1191
1236
|
// Handle {{now}} - current timestamp
|
|
@@ -1230,8 +1275,7 @@ function interpolateVariables(
|
|
|
1230
1275
|
// Handle {{env.*}} variables
|
|
1231
1276
|
if (trimmedPath.startsWith('env.')) {
|
|
1232
1277
|
const envKey = trimmedPath.substring('env.'.length)
|
|
1233
|
-
|
|
1234
|
-
return envValue !== undefined ? envValue : match
|
|
1278
|
+
return resolveWorkflowEnvInterpolation(envKey)
|
|
1235
1279
|
}
|
|
1236
1280
|
|
|
1237
1281
|
// Handle {{now}} - current timestamp
|